SuperNodeStorage/src/main.js
2022-07-26 03:07:54 +05:30

264 lines
11 KiB
JavaScript

const config = require('../args/config.json');
global.floGlobals = require("./floGlobals");
require('./set_globals');
require('./lib');
global.cloud = require('./cloud');
global.floCrypto = require('./floCrypto');
global.floBlockchainAPI = require('./floBlockchainAPI');
const Database = require("./database");
const intra = require('./intra');
const client = require('./client');
const Server = require('./server');
var DB; //Container for Database object
const INTERVAL_REFRESH_TIME = 1 * 60 * 60 * 1000; //1 hr
function startNode() {
//Set myPrivKey, myPubKey, myFloID
global.myPrivKey = config["privateKey"];
global.myPubKey = floCrypto.getPubKeyHex(config["privateKey"]);
global.myFloID = floCrypto.getFloID(config["privateKey"]);
console.info("Logged In as " + myFloID);
//DB connect
Database(config["sql_user"], config["sql_pwd"], config["sql_db"], config["sql_host"]).then(db => {
console.info("Connected to Database");
DB = db;
//Set DB to client and intra scripts
intra.DB = DB;
client.DB = DB;
client._list = intra._list;
loadBase().then(base => {
console.log("Load Database successful");
//Set base data from DB to floGlobals
floGlobals.supernodes = base.supernodes;
floGlobals.sn_config = base.sn_config;
floGlobals.appList = base.appList;
floGlobals.appSubAdmins = base.appSubAdmins;
refreshData.base = base;
refreshData.invoke(null)
.then(_ => intra.reconnectNextNode()).catch(_ => null);
//Start Server
const server = new Server(config["port"], client, intra);
server.refresher = refreshData;
intra.refresher = refreshData;
}).catch(error => console.error(error));
}).catch(error => console.error(error));
};
function loadBase() {
return new Promise((resolve, reject) => {
DB.createBase().then(result => {
DB.getBase(DB)
.then(result => resolve(result))
.catch(error => reject(error));
}).catch(error => reject(error));
});
};
const refreshData = {
count: null,
base: null,
refresh_instance: null,
invoke(flag = true) {
return new Promise((resolve, reject) => {
const self = this;
console.info("Refresher processor has started at " + Date());
if (self.refresh_instance !== null) {
clearInterval(self.refresh_instance);
self.refresh_instance = null;
}
refreshBlockchainData(self.base, flag).then(result => {
console.log(result);
self.count = floGlobals.sn_config.refreshDelay;
diskCleanUp(self.base)
.then(result => console.log(result))
.catch(warn => console.warn(warn))
.finally(_ => {
console.info("Refresher processor has finished at " + Date());
resolve(true);
});
}).catch(error => {
console.error(error);
reject(false);
}).finally(_ => {
self.refresh_instance = setInterval(() => refreshBlockchainData(self.base, true), INTERVAL_REFRESH_TIME)
});
});
},
get countdown() {
this.count--;
if (this.count <= 0)
this.invoke().then(_ => null).catch(_ => null);
}
};
function refreshBlockchainData(base, flag) {
return new Promise((resolve, reject) => {
readSupernodeConfigFromAPI(base, flag).then(result => {
console.log(result);
cloud(floGlobals.SNStorageID, Object.keys(floGlobals.supernodes));
console.log("NodeList (ordered):", cloud.order);
readAppSubAdminListFromAPI(base)
.then(result => console.log(result))
.catch(warn => console.warn(warn))
.finally(_ => resolve("Refreshed Data from blockchain"));
}).catch(error => reject(error));
});
};
function readSupernodeConfigFromAPI(base, flag) {
return new Promise((resolve, reject) => {
floBlockchainAPI.readData(floGlobals.SNStorageID, {
ignoreOld: base.lastTx[floGlobals.SNStorageID],
sentOnly: true,
pattern: "SuperNodeStorage"
}).then(result => {
let promises = [],
node_change = {};
result.data.reverse().forEach(data => {
var content = JSON.parse(data).SuperNodeStorage;
if (content.removeNodes)
for (let sn of content.removeNodes) {
promises.push(DB.rmSuperNode(sn));
delete base.supernodes[sn];
if (node_change[sn] === true)
delete node_change[sn];
else
node_change[sn] = false;
};
if (content.newNodes)
for (let sn in content.newNodes) {
promises.push(DB.addSuperNode(sn, content.newNodes[sn].pubKey, content.newNodes[sn].uri));
base.supernodes[sn] = {
pubKey: content.newNodes[sn].pubKey,
uri: content.newNodes[sn].uri
};
if (node_change[sn] === false)
delete node_change[sn];
else
node_change[sn] = true;
};
if (content.config)
for (let c in content.config) {
promises.push(DB.setConfig(c, content.config[c]));
base.sn_config[c] = content.config[c];
};
if (content.removeApps)
for (let app of content.removeApps) {
promises.push(DB.rmApp(app));
delete base.appList;
};
if (content.addApps)
for (let app in content.addApps) {
promises.push(DB.addApp(app, content.addApps[app]));
base.appList[app] = content.addApps[app];
};
});
promises.push(DB.setLastTx(floGlobals.SNStorageID, result.totalTxs));
//Check if all save process were successful
Promise.allSettled(promises).then(results => {
if (results.reduce((a, r) => r.status === "rejected" ? ++a : a, 0))
console.warn("Some data might not have been saved in database correctly");
});
//Process data migration if nodes are changed
if (Object.keys(node_change).length) {
if (flag === null)
selfDiskMigration(node_change); //When node starts for the 1st time after been inactive, but migration has already taken place.
else
intra.dataMigration(node_change, flag);
}
resolve('Updated Supernode Configuration');
}).catch(error => reject(error));
});
};
function readAppSubAdminListFromAPI(base) {
var promises = [];
//Load for each apps
for (let app in base.appList) {
promises.push(new Promise((resolve, reject) => {
floBlockchainAPI.readData(base.appList[app], {
ignoreOld: base.lastTx[base.appList[app]] || 0,
sentOnly: true,
pattern: app
}).then(result => {
let subAdmins = new Set(base.appSubAdmins[app]);
result.data.reverse().forEach(data => {
let content = JSON.parse(data)[app];
if (Array.isArray(content.removeSubAdmin))
content.removeSubAdmin.forEach(sa => subAdmins.delete(sa));
if (Array.isArray(content.addSubAdmin))
content.addSubAdmin.forEach(sa => subAdmins.add(sa));
});
base.appSubAdmins[app] = Array.from(subAdmins);
Promise.allSettled([
DB.setLastTx(base.appList[app], result.totalTxs),
DB.setSubAdmin(app, base.appSubAdmins[app])
]).then(results => {
if (results.reduce((a, r) => r.status === "rejected" ? ++a : a, 0))
console.warn(`SubAdmin list for app(${app}) might not have been saved in database`);
});
resolve("Loaded subAdmin List for APP:" + app);
}).catch(error => reject([app, error]));
}));
};
return new Promise((resolve, reject) => {
Promise.allSettled(promises).then(results => {
if (results.reduce((a, r) => r.status === "rejected" ? ++a : a, 0)) {
let error = Object.fromEntries(results.filter(r => r.status === "rejected").map(r => r.reason));
console.debug(error);
reject(`subAdmin List for APPS(${Object.keys(error)}) might not have loaded correctly`);
} else
resolve("Loaded subAdmin List for all APPs successfully");
});
});
};
function diskCleanUp(base) {
return new Promise((resolve, reject) => {
let time = Date.now() - base.sn_config.deleteDelay,
promises = [];
intra._list.serving.forEach(sn => {
//delete all when app is not authorised.
promises.push(DB.clearUnauthorisedAppData(sn, Object.keys(base.appList), time));
//for each authorised app: delete unofficial data (untaged, unknown sender/receiver)
for (let app in base.appList)
promises.push(DB.clearAuthorisedAppData(sn, app, base.appList[app], base.subAdmins[app], time));
});
Promise.allSettled(promises).then(results => {
let failed = results.filter(r => r.status === "rejected").map(r => r.reason);
if (failed.length) {
console.error(JSON.stringify(failed));
let success = results.length - failed.length;
reject(`Disk clean-up process has failed at ${100 * success/results.length}%. (Success:${success}|Failed:${failed.count})`);
} else
resolve("Disk clean-up process finished successfully (100%)");
}).catch(error => reject(error));
});
};
function selfDiskMigration(node_change) {
DB.query("SHOW TABLES").then(result => {
const disks = [];
for (let i in result)
for (let j in result[i])
if (result[i][j].startsWith("_"))
disks.push(result[i][j].split("_")[1]);
disks.forEach(n => {
if (node_change[n] === false)
DB.dropTable(n).then(_ => null).catch(e => console.error(e));
DB.readAllData(n, 0).then(result => {
result.forEach(d => {
let closest = cloud.closestNode(d.receiverID);
if (closest !== n)
DB.deleteData(n, d.vectorClock).then(_ => null).catch(e => console.error(e));
});
}).catch(error => console.error(error));
});
}).catch(error => console.error(error));
};
module.exports = startNode;