diff --git a/.config b/.config new file mode 100644 index 0000000..a66b0d8 --- /dev/null +++ b/.config @@ -0,0 +1,3 @@ +SERVER_PWD= +BROWSER=firefox +PORT=7130 diff --git a/README.md b/README.md index 0115093..f823965 100644 --- a/README.md +++ b/README.md @@ -1,80 +1,23 @@ -# Standard_Operations - Standard operations required for FLO Crypto, Blockchain API, Supernode WS, IndexedDB -This template contains standard operations that can be used for the following: -1. FLO Globals -2. FLO Crypto Operations -3. FLO Blockchain API Operations -4. FLO SuperNode Websocket Operations -5. compact IndexedDB Operations +# SuperNodeStorage +FLO Supernode Storage is a Cloud Storage progam for FLO Dapps -## FLO Globals -`floGlobals` object contains the global variables and constants required for the operations. Make sure to add this object before any other scripts. -`floGlobals` contains the following properties : -1. `blockchain` : Indicates the blockchain (`"FLO"` or `"FLO_TEST"`). -2. `apiURL` : Indicates the URL for blockchain API calls. -3. `adminID` : Indicates the master admin FLO ID for the project. -4. `sendAmt` : Indicates the default flo amount to be sent while sending transactions into the blockchain -5. `fee` : Indicates the default fee amount to be deduced while sending transactions into the blockchain -6. `supernodes` : Holder for the supernode list. Can be updated in runtime while retriving data from blockchain using API. Stored in the Object format, +## Installation +1. Download or clone the [repo](https://github.com/ranchimall/SuperNodeStorage): - { - : { - uri : - ...(otherProperties) - } - ...(Other Supernodes) - } + git clone https://github.com/ranchimall/SuperNodeStorage +2. Add a strong in `.config` file +3. Change other configurations (if needed) +4. Host and publish the domain name or IP with port -## FLO Crypto Operations -`floCrypto` operations can be used to perform blockchain-cryptography methods. `floCrypto` operations are synchronized and return a value. Contains the following Operations. -#### Generate New FLO ID pair - floCrypto.generateNewID() - `generateNewID` generates a new flo ID and returns private-key, public-key and floID +## Usage +1. Start the app using the following command in terminal. The server WSS will be started and the supernode html-js will be opened in the browser. -#### Calculate Public Key Hex -`getPubKeyHex` returns public-key from given private-key - floCrypto.getPubKeyHex(privateKey) -1. privateKey - private key in WIF format (Hex) + ./start_supernode.sh +2. (Only for first time login) Enter the and when prompted -#### Calculate FLO ID - floCrypto.getFloIDfromPubkeyHex(publicKey) -`getFloIDfromPubkeyHex` returns flo-ID from public-key -1. publicKey - public key hex value +The Supernode storage will automatically start -#### Verify Private Key - floCrypto.verifyPrivKey(privateKey, pubKey_floID, *isfloID) -`verifyPrivKey` verify the private-key for the given public-key or flo-ID -1. privateKey - private key in WIF format (Hex) -2. pubKey_floID - public Key or flo ID -3. isfloID - boolean value (true - compare as flo ID, false - compare as public key) (optional, default is true) +NOTE: The and will be stored securedly in IndexedDB of the browser -#### Validate FLO ID - floCrypto.validateAddr(floID) -`validateAddr` check if the given Address is valid or not -1. floID - flo ID to validate - -#### Data Encryption - floCrypto.encryptData(data, publicKey) -`encryptData` encrypts the given data using public-key -1. data - data to encrypt -2. publicKey - public key of the recipient - -#### Data Decryption - floCrypto.decryptData(data, privateKey) -`decryptData` decrypts the given data using private-key -1. data - encrypted data to decrypt (Object that was returned from encryptData) -2. privateKey - private key of the recipient - -#### Sign Data - floCrypto.signData(data, privateKey) -`signData` signs the data using the private key -1. data - data to sign -2. privateKey - private key of the signer - -#### Verify Signature - floCrypto.decryptData(data, signature, publicKey) -`decryptData` verifies signatue of the data using public-key -1. data - data of the given signature -2. signature - signature of the data -3. publicKey - public key of the signer +NOTE: Users may add `start_supernode` to bootup process to automatically start the supernode during boot up diff --git a/index.html b/app/index.html similarity index 77% rename from index.html rename to app/index.html index 20dbaa2..203cb36 100644 --- a/index.html +++ b/app/index.html @@ -22,17 +22,24 @@ //Required for blockchain API operators apiURL: { - FLO: 'https://livenet.flocha.in', - FLO_TEST: 'https://testnet-flosight.duckdns.org' + FLO: ['https://explorer.mediciland.com/', 'https://livenet.flocha.in/', 'https://flosight.duckdns.org/', 'http://livenet-explorer.floexperiments.com'], + FLO_TEST: ['https://testnet-flosight.duckdns.org', 'https://testnet.flocha.in/'] }, adminID: "FEzk75EGMPEQMrCuPosGiwuK162hcEu49E", + refreshDelay: 3600000, //sendAmt: 0.001, //fee: 0.0005, //Required for Supernode operations supernodes: {}, //each supnernode must be stored as floID : {uri:,pubKey:} - storageList : ["General"], - defaultStorage : "General" + diskList : ["General"], + defaultDisk : "General", + applicationList:{}, + appSubAdmins:{}, + serveList : [], + storedList : [], + supernodeConfig : {}, + backupNodes : [] } @@ -40,6 +47,8 @@

SuperNode Storage

@@ -4984,42 +5615,66 @@ /* FLO Blockchain Operator to send/receive data from blockchain using API calls*/ const floBlockchainAPI = { - //Promised AJAX function to get data from API - promisedAJAX: function (method, uri) { + util: { + serverList: floGlobals.apiURL[floGlobals.blockchain].slice(0), + curPos: floCrypto.randInt(0, floGlobals.apiURL[floGlobals.blockchain].length), + fetch_retry: function(apicall){ + return new Promise((resolve,reject) => { + this.serverList.splice(this.curPos, 1); + this.curPos = floCrypto.randInt(0, this.serverList.length) + this.fetch_api(apicall) + .then(result => resolve(result)) + .catch(error => reject(error)); + }) + }, + fetch_api: function(apicall){ + return new Promise((resolve, reject) => { + if(this.serverList.length === 0) + reject("No floSight server working") + else{ + fetch(this.serverList[this.curPos] + apicall).then(response => { + if(response.ok) + response.json().then(data => resolve(data)); + else{ + this.fetch_retry(apicall) + .then(result => resolve(result)) + .catch(error => reject(error)); + } + }).catch(error => { + this.fetch_retry(apicall) + .then(result => resolve(result)) + .catch(error => reject(error)); + }) + } + }) + } + }, + + //Promised function to get data from API + promisedAPI: function (apicall) { return new Promise((resolve, reject) => { - var request = new XMLHttpRequest(); - var url = `${floGlobals.apiURL[floGlobals.blockchain]}/${uri}`; - console.log(url) - request.open(method, url, true); - request.onload = (evt) => { - if (request.readyState == 4 && request.status == 200) - resolve(request.response); - else - reject(request.response); - }; - request.send(); + console.log(apicall) + this.util.fetch_api(apicall) + .then(result => resolve(result)) + .catch(error => reject(error)); }); }, //Get balance for the given Address getBalance: function (addr) { return new Promise((resolve, reject) => { - this.promisedAJAX("GET", `api/addr/${addr}/balance`).then(balance => { - resolve(parseFloat(balance)); - }).catch(error => { - reject(error); - }); + this.promisedAPI(`api/addr/${addr}/balance`) + .then(balance => resolve(parseFloat(balance))) + .catch(error => reject(error)); }); }, //Write Data into blockchain writeData: function (senderAddr, Data, PrivKey, receiverAddr = floGlobals.adminID) { return new Promise((resolve, reject) => { - this.sendTx(senderAddr, receiverAddr, floGlobals.sendAmt, PrivKey, Data).then(txid => { - resolve(txid); - }).catch(error => { - reject(error); - }); + this.sendTx(senderAddr, receiverAddr, floGlobals.sendAmt, PrivKey, Data) + .then(txid => resolve(txid)) + .catch(error => reject(error)) }); }, @@ -5038,8 +5693,7 @@ var trx = bitjs.transaction(); var utxoAmt = 0.0; var fee = floGlobals.fee; - this.promisedAJAX("GET", `api/addr/${senderAddr}/utxo`).then(response => { - var utxos = JSON.parse(response); + this.promisedAPI(`api/addr/${senderAddr}/utxo`).then(utxos => { for (var i = utxos.length - 1; (i >= 0) && (utxoAmt < sendAmt + fee); i--) { if (utxos[i].confirmations) { @@ -5056,15 +5710,11 @@ trx.addoutput(senderAddr, change); trx.addflodata(floData); var signedTxHash = trx.sign(PrivKey, 1); - this.broadcastTx(signedTxHash).then(txid => { - resolve(txid) - }).catch(error => { - reject(error); - }); + this.broadcastTx(signedTxHash) + .then(txid => resolve(txid)) + .catch(error => reject(error)) } - }).catch(error => { - reject(error); - }); + }).catch(error => reject(error)) } }); }, @@ -5073,7 +5723,7 @@ broadcastTx: function (signedTxHash) { return new Promise((resolve, reject) => { var request = new XMLHttpRequest(); - var url = `${floGlobals.apiURL[floGlobals.blockchain]}/api/tx/send`; + var url = this.util.serverList[this.util.curPos] + 'api/tx/send'; if (signedTxHash.length < 1) reject("Empty Signature"); else { @@ -5096,27 +5746,20 @@ //Read Txs of Address between from and to readTxs: function (addr, from, to) { return new Promise((resolve, reject) => { - this.promisedAJAX("GET", `api/addrs/${addr}/txs?from=${from}&to=${to}`).then(response => { - resolve(JSON.parse(response)); - }).catch(error => { - reject(error); - }); + this.promisedAPI(`api/addrs/${addr}/txs?from=${from}&to=${to}`) + .then(response => resolve(response)) + .catch(error => reject(error)) }); }, //Read All Txs of Address (newest first) readAllTxs: function (addr) { return new Promise((resolve, reject) => { - this.promisedAJAX("GET", `api/addrs/${addr}/txs?from=0&to=1`).then(response => { - var totalItems = JSON.parse(response).totalItems; - this.promisedAJAX("GET", `api/addrs/${addr}/txs?from=0&to=${totalItems}0`).then(response => { - resolve(JSON.parse(response).items); - }).catch(error => { - reject(error); - }); - }).catch(error => { - reject(error); - }); + this.promisedAPI(`api/addrs/${addr}/txs?from=0&to=1`).then(response => { + this.promisedAPI(`api/addrs/${addr}/txs?from=0&to=${response.totalItems}0`) + .then(response => resolve(response.items)) + .catch(error => reject(error)); + }).catch(error => reject(error)) }); }, @@ -5130,13 +5773,12 @@ filter : custom filter funtion for floData (eg . filter: d => {return d[0] == '$'}) */ readData: function(addr,options = {}){ - options.limit = options.limit || 1000 - options.ignoreOld = options.ignoreOld || 0 + options.limit = options.limit | 1000 + options.ignoreOld = options.ignoreOld | 0 return new Promise((resolve, reject) => { - this.promisedAJAX("GET", `api/addrs/${addr}/txs?from=0&to=1`).then(response => { - var newItems = JSON.parse(response).totalItems - options.ignoreOld; - this.promisedAJAX("GET", `api/addrs/${addr}/txs?from=0&to=${newItems*2}`).then(response => { - response = JSON.parse(response) + this.promisedAPI(`api/addrs/${addr}/txs?from=0&to=1`).then(response => { + var newItems = response.totalItems - options.ignoreOld; + this.promisedAPI(`api/addrs/${addr}/txs?from=0&to=${newItems*2}`).then(response => { if (options.limit <= 0) options.limit = response.items.length; var filteredData = []; @@ -5216,16 +5858,94 @@ this.addContact(decodedId, address, KB); }, isNodePresent: function (flo_id, KB = this.supernodeKBucket) { + let kArray = KB.toArray(); + let kArrayFloIds = kArray.map(k => k.floID); + if (kArrayFloIds.includes(flo_id)) + return true; + else + return false; + }, + + getInnerNodes: function(flo_addr1, flo_addr2, KB = this.supernodeKBucket){ return new Promise((resolve, reject) => { - let kArray = KB.toArray(); - let kArrayFloIds = kArray.map(k => k.data.id); - if (kArrayFloIds.includes(flo_id)) { - resolve(true); - } else { - reject(false); + let kArrayFloIds = KB.toArray().map(k => k.floID); + var innerNodes = [] + if(kArrayFloIds.includes(flo_addr1) && kArrayFloIds.includes(flo_addr2)){ + for(var i = kArrayFloIds.indexOf(flo_addr1); i!= flo_addr2; i++){ + if(i >= kArrayFloIds.length) + i = -1 + else + innerNodes.push(kArrayFloIds[i]) + } + resolve(innerNodes) + }else + reject('Given nodes are not in KBucket') + }); + }, + + getOuterNodes: function(flo_addr1, flo_addr2, KB = this.supernodeKBucket){ + return new Promise((resolve, reject) => { + let kArrayFloIds = KB.toArray().map(k => k.floID); + var outterNodes = [] + if(kArrayFloIds.includes(flo_addr1) && kArrayFloIds.includes(flo_addr2)){ + for(var i = kArrayFloIds.indexOf(flo_addr2); i!= flo_addr1; i++){ + if(i >= kArrayFloIds.length) + i = -1 + else + outterNodes.push(kArrayFloIds[i]) + } + resolve(outterNodes) + }else + reject('Given nodes are not in KBucket') + }); + }, + + getPrevSupernode: function(flo_addr, n = 1, KB = this.supernodeKBucket){ + return new Promise((resolve, reject) => { + try { + let isFloIdUint8 = flo_addr instanceof Uint8Array; + if (!isFloIdUint8) + flo_addr = this.floIdToKbucketId(flo_addr); + const KA = KB.toArray(); + let pos = KB._indexOf(KB.root,flo_addr) + var prevSupernode = [] + for(var i = 1; i <= n; i++){ + if(pos - i < 0) + var prev = pos - i + KA.length + else + var prev = pos - i + prevSupernode.push(KA[prev]) + } + resolve(prevSupernode); + } catch (error) { + reject(error); } }); }, + + getNextSupernode: function(flo_addr, n = 1, KB = this.supernodeKBucket){ + return new Promise((resolve, reject) => { + try { + let isFloIdUint8 = flo_addr instanceof Uint8Array; + if (!isFloIdUint8) + flo_addr = this.floIdToKbucketId(flo_addr); + const KA = KB.toArray(); + let pos = KB._indexOf(KB.root,flo_addr) + var nextSupernode = [] + for(var i = 1; i <= n; i++){ + if(pos + i >= KA.length) + var next = pos + i - KA.length + else + var next = pos + i + nextSupernode.push(KA[next]) + } + resolve(nextSupernode); + } catch (error) { + reject(error); + } + }); + }, + determineClosestSupernode: function (flo_addr, n = 1, KB = this.supernodeKBucket) { return new Promise((resolve, reject) => { try { @@ -5354,16 +6074,20 @@ request = request.split(" "); requestor = request[0]; request = JSON.parse(request[1]); - var filterOptions = { - lowerKey: request.lowerVectorClock, - upperKey: request.upperVectorClock, - lastOnly: request.mostRecent, - atKey: request.atVectorClock, - patternEval: (k, v) => { return (v.application == request.application && (!request.receiverID || v.receiverID == request.receiverID) && (!request.comment || v.comment == request.comment) && (!request.type || v.type == request.type) && (!request.senderIDs || request.senderIDs.includes(v.senderID))) } - } - compactIDB.searchData( floGlobals.storageList.includes(request.application) ? request.application : floGlobals.defaultStorage, filterOptions) - .then(result => floSupernode.supernodeClientWS.send(`${requestor} ${JSON.stringify(result)}`)) - .catch(error => console.log(error)) + floSupernode.kBucket.determineClosestSupernode(request.receiverID).then(result => { + if(floGlobals.serveList.includes(result[0].floID)){ + var filterOptions = { + lowerKey: request.lowerVectorClock, + upperKey: request.upperVectorClock, + lastOnly: request.mostRecent, + atKey: request.atVectorClock, + patternEval: (k, v) => { return (v.application == request.application && v.receiverID == request.receiverID && (!request.comment || v.comment == request.comment) && (!request.type || v.type == request.type) && (!request.senderIDs || request.senderIDs.includes(v.senderID))) } + } + compactIDB.searchData( floGlobals.diskList.includes(request.application) ? request.application : floGlobals.defaultDisk, filterOptions, `SN_${result[0].floID}`) + .then(result => floSupernode.supernodeClientWS.send(`${requestor} ${JSON.stringify(result)}`)) + .catch(error => console.log(error)) + } + }).catch(error => console.log(error)) } catch (error) { console.log(error.message) } @@ -5375,27 +6099,28 @@ console.log('Data :', data); try { data = JSON.parse(data) - floSupernode.kBucket.determineClosestSupernode(data.receiverID).then(result => { - if (result[0].floID != myFloID) - return; - if (data.senderID == floCrypto.getFloIDfromPubkeyHex(data.pubKey) && floCrypto.verifySign( - JSON.stringify(data.message), data.sign, data.pubKey)) { - if (floGlobals.storageList.includes(data.application)) - var table = data.application; - else - var table = floGlobals.defaultStorage; - compactIDB.addData(table, { - senderID: data.senderID, - receiverID: data.receiverID, - pubKey: data.pubKey, - message: data.message, - sign: data.sign, - application: data.application, - type: data.type, - comment: data.comment - }) - } - }) + if(!data.backupMsg){ //Serving Users + floSupernode.kBucket.determineClosestSupernode(data.receiverID).then(result => { + if (floGlobals.serveList.includes(result[0].floID) + && data.senderID == floCrypto.getFloIDfromPubkeyHex(data.pubKey) + && floCrypto.verifySign(JSON.stringify(data.message), data.sign, data.pubKey)){ + var key = `${Date.now()}_${data.senderID}` + var value = { + senderID: data.senderID, + receiverID: data.receiverID, + pubKey: data.pubKey, + message: data.message, + sign: data.sign, + application: data.application, + type: data.type, + comment: data.comment + } + compactIDB.addData(floGlobals.diskList.includes(value.application) ? value.application:floGlobals.defaultDisk , value ,key,`SN_${result[0].floID}`) + reactor.dispatchEvent("send_backup",{ key:key, value:value, snfloID: result[0].floID}) + } + }).catch(error => console.log(error)) + }else if(data.from in floGlobals.supernodes) + reactor.dispatchEvent("backup_message_event", data) } catch (error) { console.log(error.message); } @@ -5419,7 +6144,7 @@ initDB: function (dbName, objectStores = {}) { return new Promise((resolve, reject) => { - this.dbName = this.dbName || dbName; + //this.dbName = this.dbName || dbName; var idb = indexedDB.open(dbName); idb.onerror = (event) => { reject("Error in opening IndexedDB!"); @@ -5535,7 +6260,7 @@ }, searchData: function (obsName, options = {}, dbName = this.dbName) { - options.lowerKey = options.atKey || options.lowerKey || 1 + options.lowerKey = options.atKey || options.lowerKey || 0 options.upperKey = options.atKey || options.upperKey || false options.patternEval = options.patternEval || ((k,v) => {return true}) options.lastOnly = options.lastOnly || false @@ -5970,64 +6695,250 @@ + diff --git a/app/supernodeWSS.bin b/app/supernodeWSS.bin new file mode 100755 index 0000000..d620c0a Binary files /dev/null and b/app/supernodeWSS.bin differ diff --git a/start_supernode.sh b/start_supernode.sh new file mode 100755 index 0000000..c1b126e --- /dev/null +++ b/start_supernode.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +current_date=$(date) +echo "$current_date : Starting Supernode" >> logs/app.log + +#Read configurations +IFS="=" +while read -r var value +do +export "$var"="${value}" +done < .config + +#Start the app +echo $current_date >> logs/server.log +app/supernodeWSS.bin $PORT $SERVER_PWD >> logs/server.log & +echo $current_date >> logs/browser.log +$BROWSER app/index.html >> logs/browser.log & +wait + diff --git a/supernodeWSS b/supernodeWSS deleted file mode 100755 index c2cc4a2..0000000 Binary files a/supernodeWSS and /dev/null differ diff --git a/util/websocket.c b/util/websocket.c index c5b1c2c..db6f7b2 100644 --- a/util/websocket.c +++ b/util/websocket.c @@ -6,9 +6,8 @@ #include "mongoose.h" static sig_atomic_t s_signal_received = 0; -static const char *s_http_port = "7130"; static struct mg_serve_http_opts s_http_server_opts; -static char server_pwd[100]; +static char *s_http_port, *server_pwd; static struct mg_connection *supernode_client = NULL; static void signal_handler(int sig_num) { @@ -94,6 +93,10 @@ static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) { case MG_EV_WEBSOCKET_HANDSHAKE_DONE: { /*New websocket connection*/ display(nc, "+Connected+"); + if(supernode_client!=NULL) + unicast(nc, mg_mk_str("$+")); + else + unicast(nc, mg_mk_str("$-")); break; } case MG_EV_WEBSOCKET_FRAME: { @@ -106,6 +109,8 @@ static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) { if(!strcmp(pass,server_pwd)){ if(supernode_client!=NULL) unicast(supernode_client,mg_mk_str("$Another login is encountered! Please close/refresh this window")); + else + broadcast(nc, mg_mk_str("$+")); supernode_client = nc; unicast(supernode_client,mg_mk_str("$Access Granted!")); display(nc, "*Became SuperNode*"); @@ -129,6 +134,7 @@ static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) { if(nc == supernode_client){ supernode_client = NULL; display(nc,"!SuperNode Disconnected!"); + broadcast(nc, mg_mk_str("$-")); }else display(nc, "-Disconnected-"); break; @@ -138,12 +144,8 @@ static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) { int main(int argc, char** argv) { - if(argc<=1){ - printf("Enter server password : "); - scanf("%s",server_pwd); - } - else - strcpy(server_pwd,argv[1]); + s_http_port = argv[1]; + server_pwd = argv[2]; struct mg_mgr mgr; struct mg_connection *nc; @@ -157,7 +159,7 @@ int main(int argc, char** argv) { nc = mg_bind(&mgr, s_http_port, ev_handler); mg_set_protocol_http_websocket(nc); - s_http_server_opts.document_root = "."; // Serve current directory + s_http_server_opts.document_root = "app/"; // Serve current directory s_http_server_opts.enable_directory_listing = "no"; printf("Started on port %s\n", s_http_port);