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;