From 7b04532f4bed37c17e360e812a93bb5c46d080eb Mon Sep 17 00:00:00 2001 From: sairajzero Date: Sun, 7 Jul 2019 20:40:30 +0530 Subject: [PATCH] Added Supernode feature Senders can send msg to receiver through supernodes when receiver is offline --- app/app.js | 323 +++++++++++++++------ app/index.html | 1 + app/kBucket.js | 725 ++++++++++++++++++++++++++++++++++++++++++++++ app/registerID.js | 7 +- 4 files changed, 971 insertions(+), 85 deletions(-) create mode 100644 app/kBucket.js diff --git a/app/app.js b/app/app.js index 918b9e9..d57d5c5 100644 --- a/app/app.js +++ b/app/app.js @@ -8,8 +8,8 @@ if (!window.indexedDB) { } var contacts = []; -var receiverID,senderID,recStat; -var selfwebsocket,receiverWebSocket; +var receiverID,selfID,recStat; +var selfwebsocket,receiverWebSocket,receiverSuperNodeWS; var privKey; var encrypt = { @@ -221,24 +221,38 @@ function userDataStartUp(){ getDatafromAPI().then(function (result) { console.log(result); - getDatafromIDB().then(function(result){ + getContactsfromIDB().then(function(result){ contacts = arrayToObject(result); console.log(contacts); - getuserID().then(function(result){ - console.log(result); - senderID = result; - alert(`${senderID}\nWelcome ${contacts[senderID].name}`) - readMsgfromIDB().then(function(result){ - console.log(result); - initselfWebSocket(); - displayContacts(); - const createClock = setInterval(checkStatusInterval, 30000); + getSuperNodeListfromIDB().then(function(result){ + console.log(result) + superNodeList = result; + kBucketObj.launchKBucket().then(function(result){ + console.log(result) + getuserID().then(function(result){ + console.log(result); + selfID = result; + if(superNodeList.includes(selfID)) + modSuperNode = true; + alert(`${selfID}\nWelcome ${contacts[selfID].name}`) + readMsgfromIDB().then(function(result){ + console.log(result); + initselfWebSocket(); + pingSuperNodeForMessages(); + displayContacts(); + const createClock = setInterval(checkStatusInterval, 30000); + }).catch(function(error){ + console.log(error); + }); + //startChats(); + }).catch(function (error) { + console.log(error.message); + }); }).catch(function(error){ console.log(error.message); - }); - //startChats(); + }); }).catch(function (error) { - console.log(error.message); + console.log(error.message); }); }).catch(function (error) { console.log(error.message); @@ -256,7 +270,7 @@ function userDataStartUp(){ return obj; } - function storedata(data){ + function storeContact(data){ return new Promise( function(resolve, reject) { var idb = indexedDB.open("FLO_Chat"); @@ -280,20 +294,48 @@ function userDataStartUp(){ ); } + function storeSuperNodeData(data){ + return new Promise( + function(resolve, reject) { + var idb = indexedDB.open("FLO_Chat"); + idb.onerror = function(event) { + reject("Error in opening IndexedDB!"); + }; + idb.onsuccess = function(event) { + var db = event.target.result; + var obs = db.transaction('superNodes', "readwrite").objectStore('superNodes'); + if(data.addNodes) + for(var i=0; i -
- - ${msg} - - - ${getTime(time)} - -
- `; - disp.appendChild(msgdiv); - storeMsg({time:time,floID:data.from,text:msg,type:"R"}); + if(data.to == selfID){ + console.log('Incoming self') + var msg = encrypt.decryptMessage(data.secret,data.pubVal) + if(!encrypt.verify(msg,data.sign,contacts[data.from].pubKey)) + return + var time = Date.now(); + var disp = document.getElementById(data.from); + var msgdiv = document.createElement('div'); + msgdiv.setAttribute("class", "row message-body"); + msgdiv.innerHTML = `
+
+ + ${msg} + + + ${getTime(time)} + +
+
`; + disp.appendChild(msgdiv); + storeMsg({time:time,floID:data.from,text:msg,type:"R"}); + }else if(modSuperNode){ + + if(data.pingNewMsg) + sendStoredSuperNodeMsgs(data.floID) + + else{ + kBucketObj.determineClosestSupernode(data.to).then(result=>{ + console.log(result) + if(result[0].floID == selfID) + storeSuperNodeMsg(evt.data); + }).catch(e => { + console.log(e.message); + }); + } + } }catch(err){ if(evt.data[0]=='$') alert(evt.data); @@ -565,6 +663,21 @@ function initselfWebSocket(){ }; } +function pingSuperNodeForMessages(){ + kBucketObj.determineClosestSupernode(selfID).then(result => { + var selfSuperNodeWS = new WebSocket("ws://"+contacts[result[0].floID].onionAddr+"/ws"); + selfSuperNodeWS.onopen = function(evt){ + var data = JSON.stringify({pingNewMsg:true,floID:selfID}); + selfSuperNodeWS.send(data) + console.log('Pinged selfSupernode for new messages') + }; + selfSuperNodeWS.onerror = function(ev) { console.log('Unable to ping superNode for new messages'); }; + selfSuperNodeWS.onclose = function(ev) { console.log('Connection with selfSupernode is closed') }; + }).catch(err => { + console.log(err.message); + }); +} + function checkStatusInterval(){ try{ if(receiverWebSocket !== undefined && receiverWebSocket.readyState !== WebSocket.OPEN){ @@ -574,14 +687,14 @@ function checkStatusInterval(){ receiverWebSocket.onerror = function(ev) { receiverStatus(false); }; receiverWebSocket.onclose = function(ev) { receiverStatus(false); }; receiverWebSocket.onmessage = function(evt){ - console.log(evt.data); - if(evt.data[0]=='#'){ - if (evt.data[1]=='+') - receiverStatus(true); - else if(evt.data[1]=='-') - receiverStatus(false); + console.log(evt.data); + if(evt.data[0]=='#'){ + if (evt.data[1]=='+') + receiverStatus(true); + else if(evt.data[1]=='-') + receiverStatus(false); + } } - } } }catch(e){ console.log(e); @@ -596,6 +709,14 @@ function changeReceiver(param){ document.getElementById('recipient-floID').innerHTML = receiverID; receiverStatus(false) document.getElementById(receiverID).style.display = 'block'; + + kBucketObj.determineClosestSupernode(receiverID).then(result => { + console.log(result) + receiverSuperNodeWS = new WebSocket("ws://"+contacts[result[0].floID].onionAddr+"/ws"); + }).catch(e => { + console.log(e) + }); + try{ if(receiverWebSocket !== undefined && receiverWebSocket.readyState === WebSocket.OPEN) receiverWebSocket.close() @@ -643,20 +764,23 @@ function sendMsg(){ alert("Select a contact and send message"); return; } - if(!recStat){ - alert("Recipient is offline! Try again later") - return - } + var inp = document.getElementById('sendMsgInput') var msg = inp.value; inp.value = ""; console.log(msg); + var time = Date.now(); var sign = encrypt.sign(msg,privKey) var msgEncrypt = encrypt.encryptMessage(msg,contacts[receiverID].pubKey) - var data = JSON.stringify({from:senderID,secret:msgEncrypt.secret,sign:sign,pubVal:msgEncrypt.senderPublicKeyString}); - receiverWebSocket.send(data); + var data = JSON.stringify({from:selfID,to:receiverID,time:time,secret:msgEncrypt.secret,sign:sign,pubVal:msgEncrypt.senderPublicKeyString}); + + if(recStat){ + receiverWebSocket.send(data); + }else{ + receiverSuperNodeWS.send(data); + } + console.log(`sentMsg : ${data}`); - time = Date.now(); var disp = document.getElementById(receiverID); var msgdiv = document.createElement('div'); msgdiv.setAttribute("class", "row message-body"); @@ -671,4 +795,39 @@ function sendMsg(){ `; disp.appendChild(msgdiv); storeMsg({time:time,floID:receiverID,text:msg,type:"S"}); +} + +function sendStoredSuperNodeMsgs(floID){ + var receiverWS = new WebSocket("ws://"+contacts[floID].onionAddr+"/ws"); + receiverWS.onopen = function(ev){ + var idb = indexedDB.open("FLO_Chat",2); + idb.onerror = function(event) { + console.log("Error in opening IndexedDB!"); + }; + idb.onupgradeneeded = function(event) { + var objectStore = event.target.result.createObjectStore("superNodeMsg",{ keyPath: 'id' }); + objectStore.createIndex('from', 'from', { unique: false }); + objectStore.createIndex('to', 'to', { unique: false }); + objectStore.createIndex('data', 'data', { unique: false }); + }; + idb.onsuccess = function(event) { + var db = event.target.result; + var obs = db.transaction("superNodeMsg", "readwrite").objectStore("superNodeMsg"); + obs.openCursor().onsuccess = function(event) { + var cursor = event.target.result; + if(cursor) { + if(cursor.value.to == floID){ + receiverWS.send(cursor.value.data); + cursor.delete(); + } + cursor.continue(); + }else{ + console.log('Sent All messages to '+floID) + } + } + db.close(); + }; + }; + receiverWS.onerror = function(ev) { console.log('Connection Error to '+floID) }; + receiverWS.onclose = function(ev) { console.log('Disconnected from '+floID) }; } \ No newline at end of file diff --git a/app/index.html b/app/index.html index 5005027..d02fb3a 100644 --- a/app/index.html +++ b/app/index.html @@ -10,6 +10,7 @@ + diff --git a/app/kBucket.js b/app/kBucket.js new file mode 100644 index 0000000..32b9880 --- /dev/null +++ b/app/kBucket.js @@ -0,0 +1,725 @@ +var supernodeKBucket; +var superNodeList; +/*Kademlia DHT K-bucket implementation as a binary tree.*/ + + if (typeof reactor == "undefined" || !reactor) { + (function () { + + function Event(name) { + this.name = name; + this.callbacks = []; + } + Event.prototype.registerCallback = function (callback) { + this.callbacks.push(callback); + }; + + function Reactor() { + this.events = {}; + } + + Reactor.prototype.registerEvent = function (eventName) { + var event = new Event(eventName); + this.events[eventName] = event; + }; + + Reactor.prototype.dispatchEvent = function (eventName, eventArgs) { + this.events[eventName].callbacks.forEach(function (callback) { + callback(eventArgs); + }); + }; + + Reactor.prototype.addEventListener = function (eventName, callback) { + this.events[eventName].registerCallback(callback); + }; + + window.reactor = new Reactor(); + + })(); +} + +reactor.registerEvent('added'); +reactor.addEventListener('added', function (someObject) { + console.log('Added fired with data ' + someObject); +}); + +reactor.registerEvent('removed'); +reactor.addEventListener('removed', function (someObject) { + console.log('Removed fired with data ' + someObject); +}); + +reactor.registerEvent('updated'); +reactor.addEventListener('updated', function (someObject) { + console.log('Updated fired with data ' + someObject); +}); + +reactor.registerEvent('bucket_full'); +reactor.addEventListener('bucket_full', function (someObject) { + console.log('Bucket full ' + someObject); +}); + +/* +//Sample Usage +//Creating and defining the event +reactor.registerEvent('big bang'); +reactor.addEventListener('big bang', function(someObject){ +console.log('This is big bang listener yo!'+ someObject.a); +}); + +//Firing the event +reactor.dispatchEvent('big bang'); +reactor.dispatchEvent('big bang',{a:1}); +reactor.dispatchEvent('big bang',{a:55}); + +*/ + + +//Checking if existing NodeID can be used +//This first block of if will initialize the configuration of KBucket +//Add Events, Messaging between different K-Buckets, and attach relevant distributed data + + /** + * @param {Uint8Array} array1 + * @param {Uint8Array} array2 + * @return {Boolean} + */ + function arrayEquals(array1, array2) { + if (array1 === array2) { + return true + } + if (array1.length !== array2.length) { + return false + } + for (let i = 0, length = array1.length; i < length; ++i) { + if (array1[i] !== array2[i]) { + return false + } + } + return true + } + + function createNode() { + return { + contacts: [], + dontSplit: false, + left: null, + right: null + } + } + + function ensureInt8(name, val) { + if (!(val instanceof Uint8Array)) { + throw new TypeError(name + ' is not a Uint8Array') + } + } + + /** + * Implementation of a Kademlia DHT k-bucket used for storing + * contact (peer node) information. + * + * @extends EventEmitter + */ + function BuildKBucket(options = {}) { + /** + * `options`: + * `distance`: Function + * `function (firstId, secondId) { return distance }` An optional + * `distance` function that gets two `id` Uint8Arrays + * and return distance (as number) between them. + * `arbiter`: Function (Default: vectorClock arbiter) + * `function (incumbent, candidate) { return contact; }` An optional + * `arbiter` function that givent two `contact` objects with the same `id` + * returns the desired object to be used for updating the k-bucket. For + * more details, see [arbiter function](#arbiter-function). + * `localNodeId`: Uint8Array An optional Uint8Array representing the local node id. + * If not provided, a local node id will be created via `randomBytes(20)`. + * `metadata`: Object (Default: {}) Optional satellite data to include + * with the k-bucket. `metadata` property is guaranteed not be altered by, + * it is provided as an explicit container for users of k-bucket to store + * implementation-specific data. + * `numberOfNodesPerKBucket`: Integer (Default: 20) The number of nodes + * that a k-bucket can contain before being full or split. + * `numberOfNodesToPing`: Integer (Default: 3) The number of nodes to + * ping when a bucket that should not be split becomes full. KBucket will + * emit a `ping` event that contains `numberOfNodesToPing` nodes that have + * not been contacted the longest. + * + * @param {Object=} options optional + */ + + this.localNodeId = options.localNodeId || window.crypto.getRandomValues(new Uint8Array(20)) + this.numberOfNodesPerKBucket = options.numberOfNodesPerKBucket || 20 + this.numberOfNodesToPing = options.numberOfNodesToPing || 3 + this.distance = options.distance || this.distance + // use an arbiter from options or vectorClock arbiter by default + this.arbiter = options.arbiter || this.arbiter + this.metadata = Object.assign({}, options.metadata) + + ensureInt8('option.localNodeId as parameter 1', this.localNodeId) + + this.root = createNode() + + + /** + * Default arbiter function for contacts with the same id. Uses + * contact.vectorClock to select which contact to update the k-bucket with. + * Contact with larger vectorClock field will be selected. If vectorClock is + * the same, candidat will be selected. + * + * @param {Object} incumbent Contact currently stored in the k-bucket. + * @param {Object} candidate Contact being added to the k-bucket. + * @return {Object} Contact to updated the k-bucket with. + */ + this.arbiter = function (incumbent, candidate) { + return incumbent.vectorClock > candidate.vectorClock ? incumbent : candidate + } + + /** + * Default distance function. Finds the XOR + * distance between firstId and secondId. + * + * @param {Uint8Array} firstId Uint8Array containing first id. + * @param {Uint8Array} secondId Uint8Array containing second id. + * @return {Number} Integer The XOR distance between firstId + * and secondId. + */ + this.distance = function (firstId, secondId) { + let distance = 0 + let i = 0 + const min = Math.min(firstId.length, secondId.length) + const max = Math.max(firstId.length, secondId.length) + for (; i < min; ++i) { + distance = distance * 256 + (firstId[i] ^ secondId[i]) + } + for (; i < max; ++i) distance = distance * 256 + 255 + return distance + } + + /** + * Adds a contact to the k-bucket. + * + * @param {Object} contact the contact object to add + */ + this.add = function (contact) { + ensureInt8('contact.id', (contact || {}).id) + + let bitIndex = 0 + let node = this.root + + while (node.contacts === null) { + // this is not a leaf node but an inner node with 'low' and 'high' + // branches; we will check the appropriate bit of the identifier and + // delegate to the appropriate node for further processing + node = this._determineNode(node, contact.id, bitIndex++) + } + + // check if the contact already exists + const index = this._indexOf(node, contact.id) + if (index >= 0) { + this._update(node, index, contact) + return this + } + + if (node.contacts.length < this.numberOfNodesPerKBucket) { + node.contacts.push(contact) + reactor.dispatchEvent('added', contact) + return this + } + + // the bucket is full + if (node.dontSplit) { + // we are not allowed to split the bucket + // we need to ping the first this.numberOfNodesToPing + // in order to determine if they are alive + // only if one of the pinged nodes does not respond, can the new contact + // be added (this prevents DoS flodding with new invalid contacts) + reactor.dispatchEvent('bucket_full', {1: node.contacts.slice(0, this.numberOfNodesToPing),2: contact}) + return this + } + + this._split(node, bitIndex) + return this.add(contact) + } + + /** + * Get the n closest contacts to the provided node id. "Closest" here means: + * closest according to the XOR metric of the contact node id. + * + * @param {Uint8Array} id Contact node id + * @param {Number=} n Integer (Default: Infinity) The maximum number of + * closest contacts to return + * @return {Array} Array Maximum of n closest contacts to the node id + */ + this.closest = function (id, n = Infinity) { + ensureInt8('id', id) + + if ((!Number.isInteger(n) && n !== Infinity) || n <= 0) { + throw new TypeError('n is not positive number') + } + + let contacts = [] + + for (let nodes = [this.root], bitIndex = 0; nodes.length > 0 && contacts.length < n;) { + const node = nodes.pop() + if (node.contacts === null) { + const detNode = this._determineNode(node, id, bitIndex++) + nodes.push(node.left === detNode ? node.right : node.left) + nodes.push(detNode) + } else { + contacts = contacts.concat(node.contacts) + } + } + + return contacts + .map(a => [this.distance(a.id, id), a]) + .sort((a, b) => a[0] - b[0]) + .slice(0, n) + .map(a => a[1]) + } + + /** + * Counts the total number of contacts in the tree. + * + * @return {Number} The number of contacts held in the tree + */ + this.count = function () { + // return this.toArray().length + let count = 0 + for (const nodes = [this.root]; nodes.length > 0;) { + const node = nodes.pop() + if (node.contacts === null) nodes.push(node.right, node.left) + else count += node.contacts.length + } + return count + } + + /** + * Determines whether the id at the bitIndex is 0 or 1. + * Return left leaf if `id` at `bitIndex` is 0, right leaf otherwise + * + * @param {Object} node internal object that has 2 leafs: left and right + * @param {Uint8Array} id Id to compare localNodeId with. + * @param {Number} bitIndex Integer (Default: 0) The bit index to which bit + * to check in the id Uint8Array. + * @return {Object} left leaf if id at bitIndex is 0, right leaf otherwise. + */ + this._determineNode = function (node, id, bitIndex) { + // *NOTE* remember that id is a Uint8Array and has granularity of + // bytes (8 bits), whereas the bitIndex is the bit index (not byte) + + // id's that are too short are put in low bucket (1 byte = 8 bits) + // (bitIndex >> 3) finds how many bytes the bitIndex describes + // bitIndex % 8 checks if we have extra bits beyond byte multiples + // if number of bytes is <= no. of bytes described by bitIndex and there + // are extra bits to consider, this means id has less bits than what + // bitIndex describes, id therefore is too short, and will be put in low + // bucket + const bytesDescribedByBitIndex = bitIndex >> 3 + const bitIndexWithinByte = bitIndex % 8 + if ((id.length <= bytesDescribedByBitIndex) && (bitIndexWithinByte !== 0)) { + return node.left + } + + const byteUnderConsideration = id[bytesDescribedByBitIndex] + + // byteUnderConsideration is an integer from 0 to 255 represented by 8 bits + // where 255 is 11111111 and 0 is 00000000 + // in order to find out whether the bit at bitIndexWithinByte is set + // we construct (1 << (7 - bitIndexWithinByte)) which will consist + // of all bits being 0, with only one bit set to 1 + // for example, if bitIndexWithinByte is 3, we will construct 00010000 by + // (1 << (7 - 3)) -> (1 << 4) -> 16 + if (byteUnderConsideration & (1 << (7 - bitIndexWithinByte))) { + return node.right + } + + return node.left + } + + /** + * Get a contact by its exact ID. + * If this is a leaf, loop through the bucket contents and return the correct + * contact if we have it or null if not. If this is an inner node, determine + * which branch of the tree to traverse and repeat. + * + * @param {Uint8Array} id The ID of the contact to fetch. + * @return {Object|Null} The contact if available, otherwise null + */ + this.get = function (id) { + ensureInt8('id', id) + + let bitIndex = 0 + + let node = this.root + while (node.contacts === null) { + node = this._determineNode(node, id, bitIndex++) + } + + // index of uses contact id for matching + const index = this._indexOf(node, id) + return index >= 0 ? node.contacts[index] : null + } + + /** + * Returns the index of the contact with provided + * id if it exists, returns -1 otherwise. + * + * @param {Object} node internal object that has 2 leafs: left and right + * @param {Uint8Array} id Contact node id. + * @return {Number} Integer Index of contact with provided id if it + * exists, -1 otherwise. + */ + this._indexOf = function (node, id) { + for (let i = 0; i < node.contacts.length; ++i) { + if (arrayEquals(node.contacts[i].id, id)) return i + } + + return -1 + } + + /** + * Removes contact with the provided id. + * + * @param {Uint8Array} id The ID of the contact to remove. + * @return {Object} The k-bucket itself. + */ + this.remove = function (id) { + ensureInt8('the id as parameter 1', id) + + let bitIndex = 0 + let node = this.root + + while (node.contacts === null) { + node = this._determineNode(node, id, bitIndex++) + } + + const index = this._indexOf(node, id) + if (index >= 0) { + const contact = node.contacts.splice(index, 1)[0] + reactor.dispatchEvent('removed', contact) + } + + return this + } + + /** + * Splits the node, redistributes contacts to the new nodes, and marks the + * node that was split as an inner node of the binary tree of nodes by + * setting this.root.contacts = null + * + * @param {Object} node node for splitting + * @param {Number} bitIndex the bitIndex to which byte to check in the + * Uint8Array for navigating the binary tree + */ + this._split = function (node, bitIndex) { + node.left = createNode() + node.right = createNode() + + // redistribute existing contacts amongst the two newly created nodes + for (const contact of node.contacts) { + this._determineNode(node, contact.id, bitIndex).contacts.push(contact) + } + + node.contacts = null // mark as inner tree node + + // don't split the "far away" node + // we check where the local node would end up and mark the other one as + // "dontSplit" (i.e. "far away") + const detNode = this._determineNode(node, this.localNodeId, bitIndex) + const otherNode = node.left === detNode ? node.right : node.left + otherNode.dontSplit = true + } + + /** + * Returns all the contacts contained in the tree as an array. + * If this is a leaf, return a copy of the bucket. `slice` is used so that we + * don't accidentally leak an internal reference out that might be + * accidentally misused. If this is not a leaf, return the union of the low + * and high branches (themselves also as arrays). + * + * @return {Array} All of the contacts in the tree, as an array + */ + this.toArray = function () { + let result = [] + for (const nodes = [this.root]; nodes.length > 0;) { + const node = nodes.pop() + if (node.contacts === null) nodes.push(node.right, node.left) + else result = result.concat(node.contacts) + } + return result + } + + /** + * Updates the contact selected by the arbiter. + * If the selection is our old contact and the candidate is some new contact + * then the new contact is abandoned (not added). + * If the selection is our old contact and the candidate is our old contact + * then we are refreshing the contact and it is marked as most recently + * contacted (by being moved to the right/end of the bucket array). + * If the selection is our new contact, the old contact is removed and the new + * contact is marked as most recently contacted. + * + * @param {Object} node internal object that has 2 leafs: left and right + * @param {Number} index the index in the bucket where contact exists + * (index has already been computed in a previous + * calculation) + * @param {Object} contact The contact object to update. + */ + this._update = function (node, index, contact) { + // sanity check + if (!arrayEquals(node.contacts[index].id, contact.id)) { + throw new Error('wrong index for _update') + } + + const incumbent = node.contacts[index] + + /***************Change made by Abhishek*************/ + const selection = this.arbiter(incumbent, contact) + //const selection = localbitcoinplusplus.kademlia.arbiter(incumbent, contact); + // if the selection is our old contact and the candidate is some new + // contact, then there is nothing to do + if (selection === incumbent && incumbent !== contact) return + + node.contacts.splice(index, 1) // remove old contact + node.contacts.push(selection) // add more recent contact version + /***************Change made by Abhishek*************/ + reactor.dispatchEvent('updated', { + ...incumbent, + ...selection + }) + //reactor.dispatchEvent('updated', incumbent.concat(selection)) + } + } + + +kBucketObj = { + + decodeBase58Address: function (blockchain, address) { + let k = bitjs.Base58.decode(address) + k.shift() + k.splice(-4, 4) + return Crypto.util.bytesToHex(k) + }, + launchKBucket: function() { + return new Promise((resolve, reject)=>{ + try { + //const master_flo_pubKey = localbitcoinplusplus.master_configurations.masterFLOPubKey; + const master_flo_addr = adminID; + const SuKBucketId = this.floIdToKbucketId(crypto, master_flo_addr); + const SukbOptions = { localNodeId: SuKBucketId } + supernodeKBucket = new BuildKBucket(SukbOptions); + for(var i=0; i{ + return new Promise((resolve, reject)=>{ + try { + let flo_id = bitjs.pubkey2address(pubKey); + let kname = `SKBucket_${pubKey}`; + const KBucketId = this.floIdToKbucketId(crypto, flo_id) + const kbOptions = { localNodeId: KBucketId } + window[kname] = new BuildKBucket(kbOptions); + resolve(true); + } catch (error) { + reject(error); + } + }) + }) + }, + addContact: function (id, floID, KB=supernodeKBucket) { + const contact = { + id: id, + floID: floID + }; + KB.add(contact) + }, + addNewUserNodeInKbucket: function(blockchain, address, KB=supernodeKBucket) { + let decodedId = address; + try { + decodedId = this.floIdToKbucketId(blockchain, address); + } catch(e) { + decodedId = address; + } + const addNewUserNode = this.addContact(decodedId, address, KB); + return {decodedId:decodedId, address:address}; + }, + floIdToKbucketId: function (blockchain, address) { + const decodedId = this.decodeBase58Address(blockchain, address); + const nodeIdBigInt = new BigInteger(decodedId, 16); + const nodeIdBytes = nodeIdBigInt.toByteArrayUnsigned(); + const nodeIdNewInt8Array = new Uint8Array(nodeIdBytes); + return nodeIdNewInt8Array; + }, + arbiter: function (incumbent, candidate) { + // we create a new object so that our selection is guaranteed to replace + // the incumbent + const merged = { + id: incumbent.id, // incumbent.id === candidate.id within an arbiter + data: incumbent.data + } + + Object.keys(candidate.data).forEach(workerNodeId => { + merged.data[workerNodeId] = candidate.data[workerNodeId]; + }) + + return merged; + }, + newBase64DiscoverId: function (pubKey) { + let pubKeyBytes = Crypto.util.hexToBytes(pubKey); + return Crypto.util.bytesToBase64(pubKeyBytes); + }, + restoreKbucket: function(flo_addr, blockchain="FLO_TEST", KB=KBucket) { + return new Promise((resolve, reject)=>{ + readAllDB('kBucketStore') + .then(dbObject => { + if (typeof dbObject=="object") { + let su_flo_addr_array = localbitcoinplusplus.master_configurations.supernodesPubKeys + .map(pubk=>bitjs.pubkey2address(pubk)); + // Prevent supernode to re-added in kbucket + dbObject + .filter(f=>!su_flo_addr_array.includes(f.data.id)) + .map(dbObj=>{ + this.addNewUserNodeInKbucket(blockchain, flo_addr, dbObj.data, KB); + }); + } else { + reject(`Failed to restore kBucket.`); + } + resolve(dbObject); + }); + }) + }, + restoreSupernodeKBucket: function() { + return new Promise((resolve, reject)=>{ + const supernodeSeeds = localbitcoinplusplus.master_configurations.supernodeSeeds; + if (typeof supernodeSeeds !== "object") reject("Failed to get supernode seeds."); + let supernodeSeedsObj = JSON.parse(supernodeSeeds); + + Object.entries(supernodeSeedsObj).map(seedObj=>{ + let kbuck = this.addNewUserNodeInKbucket(crypto, seedObj[1].kbucketId, + { id: seedObj[1].kbucketId }, supernodeKBucket); + }); + + resolve(true); + }) + }, + updateClosestSupernodeSeeds: function(flo_addr) { + return new Promise(async (resolve, reject) => { + await removeAllinDB('myClosestSupernodes'); + let nearestSupernodeAddresslist = await this.addClosestSupernodeInDB(flo_addr); + nearestSupernodeAddresslist.map((nearestSupernodeAddress, index)=>{ + updateinDB('myClosestSupernodes', { + id: index+1, + ip: nearestSupernodeAddress.ip, + port: nearestSupernodeAddress.port, + trader_flo_address: nearestSupernodeAddress.kbucketId, + is_live: null + }).then(updatedClosestSupernodes=>{ + readAllDB('myClosestSupernodes').then(nearestSupernodeAddresslist=>{ + showMessage(`INFO: Updated closest supernodes list successfully.`); + resolve(nearestSupernodeAddresslist); + }); + }); + }); + }); + }, + getSupernodeSeed: function (flo_addr) { + return new Promise(async (resolve, reject) => { + let nearestSupernodeAddresslist = await readAllDB('myClosestSupernodes'); + if (nearestSupernodeAddresslist.length<1) { + nearestSupernodeAddresslist = await this.updateClosestSupernodeSeeds(flo_addr); + } + resolve(nearestSupernodeAddresslist); + }); + }, + isNodePresentInMyKbucket: function(flo_id, KB=KBucket) { + 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); + } + }); + }, + determineClosestSupernode: function(flo_addr="", n=1, KB=supernodeKBucket, su="") { + return new Promise((resolve, reject)=>{ + let msg = ``; + if (typeof supernodeKBucket !== "object") { + msg = `ERROR: Supernode KBucket not found.`; + showMessage(msg); + reject(msg); + return false; + } + + if (su.length>0) { + try { + let closestSupernodeMasterList = supernodeKBucket.closest(supernodeKBucket.localNodeId); + const index = closestSupernodeMasterList.findIndex(f=>f.data.id==su); + let tail = closestSupernodeMasterList.splice(0, index); + const newClosestSupernodeMasterList = closestSupernodeMasterList.concat(tail); + resolve(newClosestSupernodeMasterList); + return true; + } catch (error) { + reject(error); + } + return false; + } + + try { + if(flo_addr.length < 0) { + showMessage(`WARNING: No Flo Id provided to determine closest Supenode.`); + return; + } + let isFloIdUint8 = flo_addr instanceof Uint8Array; + if (!isFloIdUint8) { + flo_addr = this.floIdToKbucketId(crypto, flo_addr); + } + const closestSupernode = supernodeKBucket.closest(flo_addr, n); + resolve(closestSupernode); + return true; + } catch (error) { + showMessage(error); + reject(error); + return false; + } + }) + }, + + addClosestSupernodeInDB: function(flo_addr, KB=KBucket) { + return new Promise(async (resolve, reject)=>{ + const supernodeSeeds = localbitcoinplusplus.master_configurations.supernodeSeeds; + if (typeof supernodeSeeds !== "object") reject("Failed to get supernode seeds."); + let supernodeSeedsObj = JSON.parse(supernodeSeeds); + + Object.entries(supernodeSeedsObj).map(seedObj=>{ + console.log(seedObj); + this.addNewUserNodeInKbucketAndDB( + crypto, seedObj[1].kbucketId, + { id: seedObj[1].kbucketId }); + }); + + let primarySu = await this.determineClosestSupernode(flo_addr); + let nearestSupernode = await this.determineClosestSupernode(flo_addr="", n=1, supernodeKBucket, primarySu[0].data.id); + let nearestSupernodeIds = nearestSupernode.map(f=>f.data.id); + let supernodeSeedsArray = Object.values(supernodeSeedsObj) + .filter(seed=>nearestSupernodeIds.includes(seed.kbucketId)) + .sort(function(a, b){ + return nearestSupernodeIds.indexOf(a.kbucketId) - nearestSupernodeIds.indexOf(b.kbucketId); + }); + + if (supernodeSeedsArray.length>0) { + resolve(supernodeSeedsArray); + } else { + reject(false); + } + }) + } +} diff --git a/app/registerID.js b/app/registerID.js index ef1c363..62f5e91 100755 --- a/app/registerID.js +++ b/app/registerID.js @@ -1,11 +1,12 @@ const crypto = "FLO" - const mainnet = `https://livenet.flocha.in`; - const testnet = `https://testnet.flocha.in`; + const mainnet = `https://flosight.duckdns.org`; + const testnet = `https://testnet-flosight.duckdns.org`; if(crypto == "FLO") var server = mainnet; else if(crypto == "FLO_TEST") var server = testnet; +const adminID = "FRR3Zz5Nod6oZTHE18seMpMYLzuLGuBWz4"; const sendAmt = 0.001 ; const fee = 0.0005; @@ -6132,7 +6133,7 @@ function ajax(method, uri){ function registerID(sender,onionAddr,wif,pubkey,username) { - var receiver = "F6LUnwRRjFuEW97Y4av31eLqqVMK9FrgE2"; + var receiver = adminID; var trx = bitjs.transaction(); var utxoAmt = 0.0;