diff --git a/.gitignore b/.gitignore index 42e6204b0ab36b01dd9511b4e93422a01aebb6d4..19ee4e98b8e1c4b3fe259ccf4ab9a1e8ec9dc94f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,7 @@ jake/ **.hex **.lss **.map -**.srec \ No newline at end of file +**.srec + +sim/js_code.txt +sim/TinyNets/nbproject/ \ No newline at end of file diff --git a/document/worst-case-ethernet.xlsx b/document/worst-case-ethernet.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..4419950a1d69def0299447f5b96ec01efecd67b3 Binary files /dev/null and b/document/worst-case-ethernet.xlsx differ diff --git a/sim/README.md b/sim/README.md index 40d6ecccae3c4de27ef940e25e603ef3ff0ec34b..ce830c9827f334af30e380407660a48a9cedeb38 100644 --- a/sim/README.md +++ b/sim/README.md @@ -1,3 +1,5 @@ +EDIT: The important files are now located in sim/TinyNets/public_html/ + # TinyNets Simulator A modified version of [simbit](https://github.com/ebfull/simbit). diff --git a/sim/TinyNets/.bowerrc b/sim/TinyNets/.bowerrc new file mode 100644 index 0000000000000000000000000000000000000000..0ba26f2cf68ff39eaeb45ee56543cc08fb61acb0 --- /dev/null +++ b/sim/TinyNets/.bowerrc @@ -0,0 +1,3 @@ +{ + "directory": "public_html/bower_components" +} diff --git a/sim/TinyNets/Gruntfile.js b/sim/TinyNets/Gruntfile.js new file mode 100644 index 0000000000000000000000000000000000000000..37d6aa7627e3a6b75e1cc2c644f9032419539b07 --- /dev/null +++ b/sim/TinyNets/Gruntfile.js @@ -0,0 +1,10 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +module.exports = function (grunt) { + // Project configuration. + grunt.initConfig({ + }); +}; diff --git a/sim/TinyNets/bower.json b/sim/TinyNets/bower.json new file mode 100644 index 0000000000000000000000000000000000000000..cd4d9884bb1da95319ac12ab88ac253a314cb1f8 --- /dev/null +++ b/sim/TinyNets/bower.json @@ -0,0 +1,12 @@ +{ + "name": "TinyNets", + "version": "1.0.0", + "main": "path/to/main.css", + "ignore": [ + ".jshintrc", "**/*.txt" + ], + "dependencies": { + }, + "devDependencies": { + } +} diff --git a/sim/TinyNets/gulpfile.js b/sim/TinyNets/gulpfile.js new file mode 100644 index 0000000000000000000000000000000000000000..18b3733bfd1853fbfda3af9d4d2889c0af3fd005 --- /dev/null +++ b/sim/TinyNets/gulpfile.js @@ -0,0 +1,11 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ + +var gulp = require('gulp'); + +gulp.task('default', function () { + // place code for your default task here +}); diff --git a/sim/TinyNets/package.json b/sim/TinyNets/package.json new file mode 100644 index 0000000000000000000000000000000000000000..50a05b0c376ab6e3a41d9ab1b2779719f7c1634d --- /dev/null +++ b/sim/TinyNets/package.json @@ -0,0 +1,8 @@ +{ + "name": "TinyNets", + "version": "1.0.0", + "keywords": ["util", "functional", "server", "client", "browser"], + "author": "rupum", + "contributors": [], + "dependencies": {} +} diff --git a/sim/bit-array.js b/sim/TinyNets/public_html/bit-array.js old mode 100755 new mode 100644 similarity index 96% rename from sim/bit-array.js rename to sim/TinyNets/public_html/bit-array.js index b9498c365805b23cb6853509cb9da5ea8e7b08f8..ec85209a27d24808b9eb7db9cce2d72fa2eda6d2 --- a/sim/bit-array.js +++ b/sim/TinyNets/public_html/bit-array.js @@ -1,225 +1,225 @@ -/** - * JavaScript BitArray - v0.2.0 - * - * Licensed under the revised BSD License. - * Copyright 2010-2012 Bram Stein - * All rights reserved. - */ - -/** - * Creates a new empty BitArray with the given length or initialises the BitArray with the given hex representation. - */ -var BitArray = function (size, hex) { - this.values = []; - - if (hex) { - hex = hex.slice(/^0x/.exec(hex) ? 2 : 0); - - if (hex.length * 4 > this.length) { - throw 'Hex value is too large for this bit array.' - } else if (hex.length * 4 < this.length) { - // pad it - while(hex.length * 4 < this.length) { - hex = '0' + hex; - } - } - - for (var i = 0; i < (hex.length / 8); i++) { - var slice = hex.slice(i * 8, i * 8 + 8); - this.values[i] = parseInt(slice, 16); - } - } else { - for (var i = 0; i < Math.ceil(size / 32); i += 1) { - this.values[i] = 0; - } - } -}; - -/** - * Returns the total number of bits in this BitArray. - */ -BitArray.prototype.size = function () { - return this.values.length * 32; -}; - -/** - * Sets the bit at index to a value (boolean.) - */ -BitArray.prototype.set = function (index, value) { - var i = Math.floor(index / 32); - // Since "undefined | 1 << index" is equivalent to "0 | 1 << index" we do not need to initialise the array explicitly here. - if (value) { - this.values[i] |= 1 << index - i * 32; - } else { - this.values[i] &= ~(1 << index - i * 32); - } - return this; -}; - -/** - * Toggles the bit at index. If the bit is on, it is turned off. Likewise, if the bit is off it is turned on. - */ -BitArray.prototype.toggle = function (index) { - var i = Math.floor(index / 32); - this.values[i] ^= 1 << index - i * 32; - return this; -}; - -/** - * Returns the value of the bit at index (boolean.) - */ -BitArray.prototype.get = function (index) { - var i = Math.floor(index / 32); - return !!(this.values[i] & (1 << index - i * 32)); -}; - -/** - * Resets the BitArray so that it is empty and can be re-used. - */ -BitArray.prototype.reset = function () { - this.values = []; - return this; -}; - -/** - * Returns a copy of this BitArray. - */ -BitArray.prototype.copy = function () { - var cp = new BitArray(); - cp.length = this.length; - cp.values = [].concat(this.values); - return cp; -}; - -/** - * Returns true if this BitArray equals another. Two BitArrays are considered - * equal if both have the same length and bit pattern. - */ -BitArray.prototype.equals = function (x) { - return this.values.length === x.values.length && - this.values.every(function (value, index) { - return value === x.values[index]; - }); -}; - -/** - * Returns the JSON representation of this BitArray. - */ -BitArray.prototype.toJSON = function () { - return JSON.stringify(this.values); -}; - -/** - * Returns a string representation of the BitArray with bits - * in logical order. - */ -BitArray.prototype.toString = function () { - return this.toArray().map(function (value) { - return value ? '1' : '0'; - }).join(''); -}; - -/** - * Returns a string representation of the BitArray with bits - * in mathemetical order. - */ -BitArray.prototype.toBinaryString = function () { - return this.toArray().map(function (value) { - return value ? '1' : '0'; - }).reverse().join(''); -}; - -/** - * Returns a hexadecimal string representation of the BitArray - * with bits in logical order. - */ -BitArray.prototype.toHexString = function () { - var result = []; - - for (var i = 0; i < this.values.length; i += 1) { - result.push(('00000000' + (this.values[i] >>> 0).toString(16)).slice(-8)); - } - return result.join(''); -}; - -/** - * Convert the BitArray to an Array of boolean values. - */ -BitArray.prototype.toArray = function () { - var result = []; - - for (var i = 0; i < this.values.length * 32; i += 1) { - result.push(this.get(i)); - } - return result; -}; - -/** - * Returns the total number of bits set to one in this BitArray. - */ -BitArray.prototype.count = function () { - var total = 0; - - // If we remove the toggle method we could efficiently cache the number of bits without calculating it on the fly. - this.values.forEach(function (x) { - // See: http://bits.stephan-brumme.com/countBits.html for an explanation - x = x - ((x >> 1) & 0x55555555); - x = (x & 0x33333333) + ((x >> 2) & 0x33333333); - x = x + (x >> 4); - x &= 0xF0F0F0F; - - total += (x * 0x01010101) >> 24; - }); - return total; -}; - -/** - * Inverts this BitArray. - */ -BitArray.prototype.not = function () { - this.values = this.values.map(function (v) { - return ~v; - }); - return this; -}; - -/** - * Bitwise OR on the values of this BitArray using BitArray x. - */ -BitArray.prototype.or = function (x) { - if (this.values.length !== x.values.length) { - throw 'Arguments must be of the same length.'; - } - this.values = this.values.map(function (v, i) { - return v | x.values[i]; - }); - return this; -}; - -/** - * Bitwise AND on the values of this BitArray using BitArray x. - */ -BitArray.prototype.and = function (x) { - if (this.values.length !== x.values.length) { - throw 'Arguments must be of the same length.'; - } - this.values = this.values.map(function (v, i) { - return v & x.values[i]; - }); - return this; -}; - -/** - * Bitwise XOR on the values of this BitArray using BitArray x. - */ -BitArray.prototype.xor = function (x) { - if (this.values.length !== x.values.length) { - throw 'Arguments must be of the same length.'; - } - this.values = this.values.map(function (v, i) { - return v ^ x.values[i]; - }); - return this; -}; - +/** + * JavaScript BitArray - v0.2.0 + * + * Licensed under the revised BSD License. + * Copyright 2010-2012 Bram Stein + * All rights reserved. + */ + +/** + * Creates a new empty BitArray with the given length or initialises the BitArray with the given hex representation. + */ +var BitArray = function (size, hex) { + this.values = []; + + if (hex) { + hex = hex.slice(/^0x/.exec(hex) ? 2 : 0); + + if (hex.length * 4 > this.length) { + throw 'Hex value is too large for this bit array.' + } else if (hex.length * 4 < this.length) { + // pad it + while(hex.length * 4 < this.length) { + hex = '0' + hex; + } + } + + for (var i = 0; i < (hex.length / 8); i++) { + var slice = hex.slice(i * 8, i * 8 + 8); + this.values[i] = parseInt(slice, 16); + } + } else { + for (var i = 0; i < Math.ceil(size / 32); i += 1) { + this.values[i] = 0; + } + } +}; + +/** + * Returns the total number of bits in this BitArray. + */ +BitArray.prototype.size = function () { + return this.values.length * 32; +}; + +/** + * Sets the bit at index to a value (boolean.) + */ +BitArray.prototype.set = function (index, value) { + var i = Math.floor(index / 32); + // Since "undefined | 1 << index" is equivalent to "0 | 1 << index" we do not need to initialise the array explicitly here. + if (value) { + this.values[i] |= 1 << index - i * 32; + } else { + this.values[i] &= ~(1 << index - i * 32); + } + return this; +}; + +/** + * Toggles the bit at index. If the bit is on, it is turned off. Likewise, if the bit is off it is turned on. + */ +BitArray.prototype.toggle = function (index) { + var i = Math.floor(index / 32); + this.values[i] ^= 1 << index - i * 32; + return this; +}; + +/** + * Returns the value of the bit at index (boolean.) + */ +BitArray.prototype.get = function (index) { + var i = Math.floor(index / 32); + return !!(this.values[i] & (1 << index - i * 32)); +}; + +/** + * Resets the BitArray so that it is empty and can be re-used. + */ +BitArray.prototype.reset = function () { + this.values = []; + return this; +}; + +/** + * Returns a copy of this BitArray. + */ +BitArray.prototype.copy = function () { + var cp = new BitArray(); + cp.length = this.length; + cp.values = [].concat(this.values); + return cp; +}; + +/** + * Returns true if this BitArray equals another. Two BitArrays are considered + * equal if both have the same length and bit pattern. + */ +BitArray.prototype.equals = function (x) { + return this.values.length === x.values.length && + this.values.every(function (value, index) { + return value === x.values[index]; + }); +}; + +/** + * Returns the JSON representation of this BitArray. + */ +BitArray.prototype.toJSON = function () { + return JSON.stringify(this.values); +}; + +/** + * Returns a string representation of the BitArray with bits + * in logical order. + */ +BitArray.prototype.toString = function () { + return this.toArray().map(function (value) { + return value ? '1' : '0'; + }).join(''); +}; + +/** + * Returns a string representation of the BitArray with bits + * in mathemetical order. + */ +BitArray.prototype.toBinaryString = function () { + return this.toArray().map(function (value) { + return value ? '1' : '0'; + }).reverse().join(''); +}; + +/** + * Returns a hexadecimal string representation of the BitArray + * with bits in logical order. + */ +BitArray.prototype.toHexString = function () { + var result = []; + + for (var i = 0; i < this.values.length; i += 1) { + result.push(('00000000' + (this.values[i] >>> 0).toString(16)).slice(-8)); + } + return result.join(''); +}; + +/** + * Convert the BitArray to an Array of boolean values. + */ +BitArray.prototype.toArray = function () { + var result = []; + + for (var i = 0; i < this.values.length * 32; i += 1) { + result.push(this.get(i)); + } + return result; +}; + +/** + * Returns the total number of bits set to one in this BitArray. + */ +BitArray.prototype.count = function () { + var total = 0; + + // If we remove the toggle method we could efficiently cache the number of bits without calculating it on the fly. + this.values.forEach(function (x) { + // See: http://bits.stephan-brumme.com/countBits.html for an explanation + x = x - ((x >> 1) & 0x55555555); + x = (x & 0x33333333) + ((x >> 2) & 0x33333333); + x = x + (x >> 4); + x &= 0xF0F0F0F; + + total += (x * 0x01010101) >> 24; + }); + return total; +}; + +/** + * Inverts this BitArray. + */ +BitArray.prototype.not = function () { + this.values = this.values.map(function (v) { + return ~v; + }); + return this; +}; + +/** + * Bitwise OR on the values of this BitArray using BitArray x. + */ +BitArray.prototype.or = function (x) { + if (this.values.length !== x.values.length) { + throw 'Arguments must be of the same length.'; + } + this.values = this.values.map(function (v, i) { + return v | x.values[i]; + }); + return this; +}; + +/** + * Bitwise AND on the values of this BitArray using BitArray x. + */ +BitArray.prototype.and = function (x) { + if (this.values.length !== x.values.length) { + throw 'Arguments must be of the same length.'; + } + this.values = this.values.map(function (v, i) { + return v & x.values[i]; + }); + return this; +}; + +/** + * Bitwise XOR on the values of this BitArray using BitArray x. + */ +BitArray.prototype.xor = function (x) { + if (this.values.length !== x.values.length) { + throw 'Arguments must be of the same length.'; + } + this.values = this.values.map(function (v, i) { + return v ^ x.values[i]; + }); + return this; +}; + module.exports = BitArray; \ No newline at end of file diff --git a/sim/btc.js b/sim/TinyNets/public_html/btc.js old mode 100755 new mode 100644 similarity index 96% rename from sim/btc.js rename to sim/TinyNets/public_html/btc.js index a4c64e4f3c19b702ea03ac7b4e8e0c64f715e16d..9068407d58bd810ba6727e7626362376bfd9fb01 --- a/sim/btc.js +++ b/sim/TinyNets/public_html/btc.js @@ -1,18 +1,18 @@ -var Inventory = require("./btc/inventory.js") -var Mempool = require("./btc/mempool.js") -var Transactions = require("./btc/transactions.js") -var Blockchain = require("./btc/blockchain.js") -var Miner = require("./btc/miner.js") - -var btc = function(self) { - new Inventory(self); - new Mempool(self); - new Transactions(self); - new Blockchain(self); - new Miner(self); -}; - - -btc.Blockchain = Blockchain.prototype; - +var Inventory = require("./btc/inventory.js") +var Mempool = require("./btc/mempool.js") +var Transactions = require("./btc/transactions.js") +var Blockchain = require("./btc/blockchain.js") +var Miner = require("./btc/miner.js") + +var btc = function(self) { + new Inventory(self); + new Mempool(self); + new Transactions(self); + new Blockchain(self); + new Miner(self); +}; + + +btc.Blockchain = Blockchain.prototype; + module.exports = btc; \ No newline at end of file diff --git a/sim/btc/blockchain.js b/sim/TinyNets/public_html/btc/blockchain.js old mode 100755 new mode 100644 similarity index 95% rename from sim/btc/blockchain.js rename to sim/TinyNets/public_html/btc/blockchain.js index 920f8b7faf86635c0f77c87efc48c73c635c9af5..2eead6a79fe686792e8a58df8808b8629ef52449 --- a/sim/btc/blockchain.js +++ b/sim/TinyNets/public_html/btc/blockchain.js @@ -1,339 +1,339 @@ -/* - btc-blockchain -*/ - -var colors = ["green", "orange", "blue", "purple", "brown", "steelblue", "red"] -var color_i = 0; - -function Block(prev, time, miner) { - this.__prev = prev; - - this.transactions = []; - - if (miner) { - this.credit = miner.id; - this.transactions = miner.mempool.getList(); - } - else - this.credit = false; - - this.time = time; - this.color = colors[++color_i % colors.length] - - if (prev) { - this.id = miner.network.rand(); - this.h = prev.h + 1; - this.prev = prev.id; - this.difficulty = prev.difficulty; - this.work = prev.work + prev.difficulty; - - if (!(this.h % this.difficulty_adjustment_period)) { - this.difficultyAdjustment() - } - } - else { - //this.id = String.fromCharCode(252, 124, 195, 233, 126, 94, 182, 200, 23, 107, 236, 43, 77, 137); - this.id = 'xxxxxxxxxxxxxx'.replace(/[xy]/g, function(c) { - var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8); - return v.toString(16); - }); - this.h = 0; - this.prev = false; - this.difficulty = 600000; - this.work = 0; - } -} - -function PrevState(status) { - this.status = status; - - this.equals = function(v) { - return (v.status == this.status) - } -} - -var GenesisBlock = new Block(false, 0); - -Block.prototype = { - target_avg_between_blocks: 10 * 60 * 1000, - difficulty_adjustment_period: 2016, - - difficultyAdjustment: function() { - var total = 0; - var last = this.time; - var cur = this._prev(); - - for (var i=0;i<this.difficulty_adjustment_period;i++) { - total += last - cur.time; - last = cur.time; - cur = cur._prev() - } - var avg = total / this.difficulty_adjustment_period; - - var old = this.difficulty; - this.difficulty *= this.target_avg_between_blocks / avg; - - console.log("(h=" + this.h + ") difficulty adjustment " + (this.target_avg_between_blocks / avg) + "x") - }, - _prev: function() { - return this.__prev; - }, - - addPrev: function(me) { - me.set(this.id, new PrevState("set")) - }, - - rmPrev: function(me) { - me.set(this.id, new PrevState("none")) - } -} - -function MapOrphanBlocks(self) { - this.mapOrphans = []; - this.mapOrphansByPrev = {}; -} - -MapOrphanBlocks.prototype = { - add: function(b) { - if (this.mapOrphans.indexOf(b) != -1) - return false; - - if (this.mapOrphans.length == 100) { - this.delete(this.mapOrphans[0]); - } - - this.mapOrphans.push(b); - - if (!(b._prev().id in this.mapOrphansByPrev)) { - this.mapOrphansByPrev[b._prev().id] = []; - } - this.mapOrphansByPrev[b._prev().id].push(b); - - return true; - }, - - delete: function(b) { - if (this.mapOrphans.indexOf(b) == -1) - return false; - - var removed = this.mapOrphans.splice(this.mapOrphans.indexOf(b), 1) - var m = this.mapOrphansByPrev[b._prev().id]; - - m.splice(m.indexOf(b), 1); - - if (m.length == 0) { - delete this.mapOrphansByPrev[b._prev().id] - } - - return true; - }, - - // returns boolean whether the block is an orphan already - is: function(b) { - if (this.mapOrphans.indexOf(b) == -1) - return false; - - return true; - }, - - // finds any blocks that depended on this block within this maporphans - getForPrev: function(prev) { - if (prev.id in this.mapOrphansByPrev) { - return this.mapOrphansByPrev[prev.id]; - } - - return []; - } -} - -var GenesisBlock = new Block(false, 0); - -function Chainstate(head, self) { - this.self = self; - - this.prevs = self.network.shared("chainstate_prevs"); - - this.mapOrphans = new MapOrphanBlocks(self); - - this.forward(head) -} - -Chainstate.prototype = { - forward: function(b) { - this.self.setColor(b.color) - this.head = b - - this.head.transactions.forEach(function(tx) { - // transaction _must_ be entered into UTXO to advance to this state - this.self.transactions.enter(tx, true); - - // transaction must be removed from mempool - this.self.mempool.remove(tx); - }, this); - - b.addPrev(this.prevs); - - if (this.mapOrphans.delete(this.head)) - this.self.inventory.relay(this.head.id); - }, - reverse: function() { - this.head.transactions.forEach(function(tx) { - // transaction must be added back to mempool - this.self.mempool.add(tx); - }, this); - - this.mapOrphans.add(this.head) - - this.head.rmPrev(this.prevs); - - this.head = this.head._prev() - }, - // (recursively) resolves the best orphan branch for comparison with the chainstate head - getOrphanWorkPath: function(block) { - var works = []; - - this.mapOrphans.getForPrev(block).forEach(function(sub) { - works.push(this.getOrphanWorkPath(sub)) - }, this) - - if (works.length == 0) { - // there aren't any subworks - return {end:block,work:block.work} - } else { - // pick the largest one - var largestWork = {end:false,work:Number.NEGATIVE_INFINITY}; - - works.forEach(function(subwork) { - if (subwork.work > largestWork.work) { - largestWork = subwork; - } - }) - - // return it - return largestWork; - } - }, - // this function helps identify orphan blocks - reorg: function(block, numorphan, force) { - var ourorphans = 0; - if (numorphan == -1) { - // This block couldn't be entered into the chainstate, so it's an orphan. - if (!this.mapOrphans.is(block)) { - this.mapOrphans.add(block) - } else { - return numorphan; - } - } - - // maybe it completes a chain though - var cur = block; - - while(true) { - var curprev = this.prevs.get(cur.id); - if (curprev.status == "set") { - var bestOrphanPath = this.getOrphanWorkPath(cur) - if ((force && bestOrphanPath.work >= this.head.work) || bestOrphanPath.work > this.head.work) { - //console.log(this.self.id + ": adopting orphan chain of (w=" + bestOrphanPath.work + " vs. local " + this.head.work + ")") - ourorphans += this.enter(bestOrphanPath.end, true, true) - } - - break; - } else { - cur = cur._prev(); - } - } - if (numorphan == -1) { - if (ourorphans == 0) - return numorphan; - else - return ourorphans - } - else - return numorphan + ourorphans; - }, - // enter a block into the chainstate, perhaps resulting in a reorg, and also perhaps resulting in its inclusion within maporphans - enter: function(block, force, doingReorg) { - //console.log((doingReorg ? "(reorg) " : "") + "entering new block at height " + block.h) - if (block == this.head) - return -1 - - var bprev = this.prevs.get(block._prev().id) - - if (bprev.status == "none") { - // this block's prev doesn't exist, it's an orphan! - if (!doingReorg) - return this.reorg(block, -1, force) - } - - if (typeof force == "undefined") - force = false; - //else if (force) - // this.self.log("\tchainstate forcefully entering branch") - - var numorphan = -1; - - if ((this.head.work < block.work) || force) { - // the current head is now obsolete - - numorphan = 0; - var forwards = [] - var cur = block - - reorg: - while(true) { - if (cur.h > this.head.h) { - forwards.push(cur) - cur = cur._prev() - } else if (cur == this.head) { - while(true) { - if (forwards.length > 0) { - this.forward(forwards.pop()) - } else { - break reorg; - } - } - } else { - numorphan++; - this.reverse() - } - } - } else if (this.head.work == block.work) { - //this.self.log("\tblock rejected; already seen one at this chainlength") - } - - if (!doingReorg) - numorphan = this.reorg(block, numorphan) - - return numorphan - } -} - -function Blockchain(self) { - self.blockchain = this; - - this.chainstate = new Chainstate(GenesisBlock, self); - - this.newChainstate = function() { - return new Chainstate(this.chainstate.head, self); - } - - // When we receive a new block over the wire, process it here. - this.onBlock = function(b) { - if (this.chainstate.enter(b) != -1) { - self.inventory.relay(b.id); - return true; - }; - } - - self.on("obj:block", function(from, o) { - if (this.onBlock(o)) - self.handle(from, "blockchain:block", o); - }, this) -} - -Blockchain.prototype = { - Block: Block, - GenesisBlock: GenesisBlock -} - +/* + btc-blockchain +*/ + +var colors = ["green", "orange", "blue", "purple", "brown", "steelblue", "red"] +var color_i = 0; + +function Block(prev, time, miner) { + this.__prev = prev; + + this.transactions = []; + + if (miner) { + this.credit = miner.id; + this.transactions = miner.mempool.getList(); + } + else + this.credit = false; + + this.time = time; + this.color = colors[++color_i % colors.length] + + if (prev) { + this.id = miner.network.rand(); + this.h = prev.h + 1; + this.prev = prev.id; + this.difficulty = prev.difficulty; + this.work = prev.work + prev.difficulty; + + if (!(this.h % this.difficulty_adjustment_period)) { + this.difficultyAdjustment() + } + } + else { + //this.id = String.fromCharCode(252, 124, 195, 233, 126, 94, 182, 200, 23, 107, 236, 43, 77, 137); + this.id = 'xxxxxxxxxxxxxx'.replace(/[xy]/g, function(c) { + var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8); + return v.toString(16); + }); + this.h = 0; + this.prev = false; + this.difficulty = 600000; + this.work = 0; + } +} + +function PrevState(status) { + this.status = status; + + this.equals = function(v) { + return (v.status == this.status) + } +} + +var GenesisBlock = new Block(false, 0); + +Block.prototype = { + target_avg_between_blocks: 10 * 60 * 1000, + difficulty_adjustment_period: 2016, + + difficultyAdjustment: function() { + var total = 0; + var last = this.time; + var cur = this._prev(); + + for (var i=0;i<this.difficulty_adjustment_period;i++) { + total += last - cur.time; + last = cur.time; + cur = cur._prev() + } + var avg = total / this.difficulty_adjustment_period; + + var old = this.difficulty; + this.difficulty *= this.target_avg_between_blocks / avg; + + console.log("(h=" + this.h + ") difficulty adjustment " + (this.target_avg_between_blocks / avg) + "x") + }, + _prev: function() { + return this.__prev; + }, + + addPrev: function(me) { + me.set(this.id, new PrevState("set")) + }, + + rmPrev: function(me) { + me.set(this.id, new PrevState("none")) + } +} + +function MapOrphanBlocks(self) { + this.mapOrphans = []; + this.mapOrphansByPrev = {}; +} + +MapOrphanBlocks.prototype = { + add: function(b) { + if (this.mapOrphans.indexOf(b) != -1) + return false; + + if (this.mapOrphans.length == 100) { + this.delete(this.mapOrphans[0]); + } + + this.mapOrphans.push(b); + + if (!(b._prev().id in this.mapOrphansByPrev)) { + this.mapOrphansByPrev[b._prev().id] = []; + } + this.mapOrphansByPrev[b._prev().id].push(b); + + return true; + }, + + delete: function(b) { + if (this.mapOrphans.indexOf(b) == -1) + return false; + + var removed = this.mapOrphans.splice(this.mapOrphans.indexOf(b), 1) + var m = this.mapOrphansByPrev[b._prev().id]; + + m.splice(m.indexOf(b), 1); + + if (m.length == 0) { + delete this.mapOrphansByPrev[b._prev().id] + } + + return true; + }, + + // returns boolean whether the block is an orphan already + is: function(b) { + if (this.mapOrphans.indexOf(b) == -1) + return false; + + return true; + }, + + // finds any blocks that depended on this block within this maporphans + getForPrev: function(prev) { + if (prev.id in this.mapOrphansByPrev) { + return this.mapOrphansByPrev[prev.id]; + } + + return []; + } +} + +var GenesisBlock = new Block(false, 0); + +function Chainstate(head, self) { + this.self = self; + + this.prevs = self.network.shared("chainstate_prevs"); + + this.mapOrphans = new MapOrphanBlocks(self); + + this.forward(head) +} + +Chainstate.prototype = { + forward: function(b) { + this.self.setColor(b.color) + this.head = b + + this.head.transactions.forEach(function(tx) { + // transaction _must_ be entered into UTXO to advance to this state + this.self.transactions.enter(tx, true); + + // transaction must be removed from mempool + this.self.mempool.remove(tx); + }, this); + + b.addPrev(this.prevs); + + if (this.mapOrphans.delete(this.head)) + this.self.inventory.relay(this.head.id); + }, + reverse: function() { + this.head.transactions.forEach(function(tx) { + // transaction must be added back to mempool + this.self.mempool.add(tx); + }, this); + + this.mapOrphans.add(this.head) + + this.head.rmPrev(this.prevs); + + this.head = this.head._prev() + }, + // (recursively) resolves the best orphan branch for comparison with the chainstate head + getOrphanWorkPath: function(block) { + var works = []; + + this.mapOrphans.getForPrev(block).forEach(function(sub) { + works.push(this.getOrphanWorkPath(sub)) + }, this) + + if (works.length == 0) { + // there aren't any subworks + return {end:block,work:block.work} + } else { + // pick the largest one + var largestWork = {end:false,work:Number.NEGATIVE_INFINITY}; + + works.forEach(function(subwork) { + if (subwork.work > largestWork.work) { + largestWork = subwork; + } + }) + + // return it + return largestWork; + } + }, + // this function helps identify orphan blocks + reorg: function(block, numorphan, force) { + var ourorphans = 0; + if (numorphan == -1) { + // This block couldn't be entered into the chainstate, so it's an orphan. + if (!this.mapOrphans.is(block)) { + this.mapOrphans.add(block) + } else { + return numorphan; + } + } + + // maybe it completes a chain though + var cur = block; + + while(true) { + var curprev = this.prevs.get(cur.id); + if (curprev.status == "set") { + var bestOrphanPath = this.getOrphanWorkPath(cur) + if ((force && bestOrphanPath.work >= this.head.work) || bestOrphanPath.work > this.head.work) { + //console.log(this.self.id + ": adopting orphan chain of (w=" + bestOrphanPath.work + " vs. local " + this.head.work + ")") + ourorphans += this.enter(bestOrphanPath.end, true, true) + } + + break; + } else { + cur = cur._prev(); + } + } + if (numorphan == -1) { + if (ourorphans == 0) + return numorphan; + else + return ourorphans + } + else + return numorphan + ourorphans; + }, + // enter a block into the chainstate, perhaps resulting in a reorg, and also perhaps resulting in its inclusion within maporphans + enter: function(block, force, doingReorg) { + //console.log((doingReorg ? "(reorg) " : "") + "entering new block at height " + block.h) + if (block == this.head) + return -1 + + var bprev = this.prevs.get(block._prev().id) + + if (bprev.status == "none") { + // this block's prev doesn't exist, it's an orphan! + if (!doingReorg) + return this.reorg(block, -1, force) + } + + if (typeof force == "undefined") + force = false; + //else if (force) + // this.self.log("\tchainstate forcefully entering branch") + + var numorphan = -1; + + if ((this.head.work < block.work) || force) { + // the current head is now obsolete + + numorphan = 0; + var forwards = [] + var cur = block + + reorg: + while(true) { + if (cur.h > this.head.h) { + forwards.push(cur) + cur = cur._prev() + } else if (cur == this.head) { + while(true) { + if (forwards.length > 0) { + this.forward(forwards.pop()) + } else { + break reorg; + } + } + } else { + numorphan++; + this.reverse() + } + } + } else if (this.head.work == block.work) { + //this.self.log("\tblock rejected; already seen one at this chainlength") + } + + if (!doingReorg) + numorphan = this.reorg(block, numorphan) + + return numorphan + } +} + +function Blockchain(self) { + self.blockchain = this; + + this.chainstate = new Chainstate(GenesisBlock, self); + + this.newChainstate = function() { + return new Chainstate(this.chainstate.head, self); + } + + // When we receive a new block over the wire, process it here. + this.onBlock = function(b) { + if (this.chainstate.enter(b) != -1) { + self.inventory.relay(b.id); + return true; + }; + } + + self.on("obj:block", function(from, o) { + if (this.onBlock(o)) + self.handle(from, "blockchain:block", o); + }, this) +} + +Blockchain.prototype = { + Block: Block, + GenesisBlock: GenesisBlock +} + module.exports = Blockchain; \ No newline at end of file diff --git a/sim/btc/inventory.js b/sim/TinyNets/public_html/btc/inventory.js old mode 100755 new mode 100644 similarity index 95% rename from sim/btc/inventory.js rename to sim/TinyNets/public_html/btc/inventory.js index aa268f79c7a88b059dadf6c599f71722560f2b0a..affb45bd93367b8fcef37cc2e3fe7880fe617e9b --- a/sim/btc/inventory.js +++ b/sim/TinyNets/public_html/btc/inventory.js @@ -1,254 +1,254 @@ -/* - btc-inventory - - Mimics the Bitcoin inventory system. -*/ - -function InventoryObject(type, obj) { - this.type = type; - this.obj = obj; - this.id = obj.id; -} - -function InventoryState(status) { - this.status = status; - - this.equals = function(v) { - return this.status == v.status; - } -} - -InventoryObject.prototype = { - init: function(consensus) { - consensus.add(this.id, this); - }, - - seen: function(me) { - var ir = me.get(this.id); - - if (ir.status == "none") { - me.set(this.id, new InventoryState("seen")) - - return true; - } - - return false; - }, - - relay: function(me) { - var ir = me.get(this.id); - - if (ir.status == "seen") { - me.set(this.id, new InventoryState("relay")) - - return true; - } - - return false; - } -} - -function Inventory(self) { - self.inventory = this; - - this.polling = false; - - this.objects = self.network.shared("inventory"); - - this.peerHas = {}; - this.tellPeer = {}; - this.mapAskFor = {}; - this.mapAlreadyAskedFor = {}; - - this.addTick = function() { - if (!this.polling) { - this.polling = true; - - self.tick(1000, this.invTick, this) - } - } - - this.invTick = function() { - var doneSomething = false; - - for (var p in this.tellPeer) { - var invPacket = this.tellPeer[p]; - - if (Object.keys(invPacket).length != 0) { - doneSomething = true; - this.__send_inv(p, invPacket) - - this.tellPeer[p] = {}; // don't need to tell the peer that anymore - } - } - - var askMap = {}; - for (var p in this.peerHas) { - askMap[p] = []; - } - - for (var p in this.mapAskFor) { - for (name in this.mapAskFor[p]) { - if (this.mapAskFor[p][name] <= self.now()) { - askMap[p].push(name); - delete this.mapAskFor[p][name] - } - } - } - - for (var p in askMap) { - if (askMap[p].length == 0) - continue; - - doneSomething = true; - - this.__send_getdata(p, askMap[p]) - } - - if (!doneSomething) { - this.polling = false; // we don't need to poll again - return false; // don't tick again - } - } - - /* - p, {name1: type1, name2: type2, ...} - */ - this.__send_inv = function(p, mapNameTypes) { - self.peermgr.send(p, "inv", mapNameTypes); - } - - /* - p, [name1, name2, name3] - */ - this.__send_getdata = function(p, askList) { - self.peermgr.send(p, "getdata", askList); - } - - /* - p, (InventoryObject) o - */ - this.__send_invobj = function(p, o) { - self.peermgr.send(p, "invobj", o); - } - - this.relay = function(name, now) { - var ir = this.objects.get(name); - - if ('relay' in ir) { - if (ir.relay(this.objects)) { - for (var p in this.tellPeer) { - if (now) { - this.__send_invobj(p, ir); - } else { - this.addTick(); - this.tellPeer[p][name] = ir.type; - } - } - } - } - } - - this.getObj = function(name, mustRelay) { - var ir = this.objects.get(name); - - if (ir.status == "none") - return false; - - if (mustRelay && ir.status == "seen") - return false; - - return ir.__proto__; - } - - this.onGetdata = function(from, msg) { - msg.forEach(function(name) { - if (o = this.getObj(name, true)) { - this.__send_invobj(from, o); - } - }, this) - } - - this.onInv = function(from, msg) { - for (var name in msg) { - // do we already have it? then we don't care - if (this.getObj(name)) { - // we already have it, so who cares - } else { - // start asking for it - // and record who has it - this.peerHas[from][name] = msg[name] - - if (!(name in this.mapAlreadyAskedFor)) { - this.mapAlreadyAskedFor[name] = self.now(); - } else { - this.mapAlreadyAskedFor[name] += 2 * 60 * 1000; - } - this.mapAskFor[from][name] = this.mapAlreadyAskedFor[name]; - this.addTick() - } - } - } - - this.onInvobj = function(from, o) { - // add it - if (this.addObj(o)) { - // stop asking other peers for it (if we are) - delete this.mapAlreadyAskedFor[o.id] - - for (var p in this.mapAskFor) { - delete this.mapAskFor[p][o.id] - } - - // we no longer care that our peers have this object - for (var p in this.peerHas) { - delete this.peerHas[p][o.id] - } - - // now run a handler - self.handle(from, "obj:" + o.type, o.obj) - } - } - - this.addObj = function(obj) { - return obj.seen(this.objects); - } - - // obj must have `name` property - this.createObj = function(type, obj) { - var o = new InventoryObject(type, obj); - - this.objects.create(o); - this.addObj(o); - - return o; - } - - self.on("peermgr:connect", function(from) { - var relaySet = this.objects.find({status:"relay"}); - - this.peerHas[from] = {}; - this.tellPeer[from] = {}; - this.mapAskFor[from] = {}; - - relaySet.forEach(function(o) { - this.tellPeer[from][o.id] = o.type; - }, this) - - this.addTick(); - }, this) - - self.on("peermgr:disconnect", function(from) { - delete this.peerHas[from] - delete this.tellPeer[from] - delete this.mapAlreadyAskedFor[from] - delete this.mapAskFor[from]; - }, this) - - self.on("inv", this.onInv, this) - self.on("getdata", this.onGetdata, this) - self.on("invobj", this.onInvobj, this) - this.addTick(); -} - +/* + btc-inventory + + Mimics the Bitcoin inventory system. +*/ + +function InventoryObject(type, obj) { + this.type = type; + this.obj = obj; + this.id = obj.id; +} + +function InventoryState(status) { + this.status = status; + + this.equals = function(v) { + return this.status == v.status; + } +} + +InventoryObject.prototype = { + init: function(consensus) { + consensus.add(this.id, this); + }, + + seen: function(me) { + var ir = me.get(this.id); + + if (ir.status == "none") { + me.set(this.id, new InventoryState("seen")) + + return true; + } + + return false; + }, + + relay: function(me) { + var ir = me.get(this.id); + + if (ir.status == "seen") { + me.set(this.id, new InventoryState("relay")) + + return true; + } + + return false; + } +} + +function Inventory(self) { + self.inventory = this; + + this.polling = false; + + this.objects = self.network.shared("inventory"); + + this.peerHas = {}; + this.tellPeer = {}; + this.mapAskFor = {}; + this.mapAlreadyAskedFor = {}; + + this.addTick = function() { + if (!this.polling) { + this.polling = true; + + self.tick(1000, this.invTick, this) + } + } + + this.invTick = function() { + var doneSomething = false; + + for (var p in this.tellPeer) { + var invPacket = this.tellPeer[p]; + + if (Object.keys(invPacket).length != 0) { + doneSomething = true; + this.__send_inv(p, invPacket) + + this.tellPeer[p] = {}; // don't need to tell the peer that anymore + } + } + + var askMap = {}; + for (var p in this.peerHas) { + askMap[p] = []; + } + + for (var p in this.mapAskFor) { + for (name in this.mapAskFor[p]) { + if (this.mapAskFor[p][name] <= self.now()) { + askMap[p].push(name); + delete this.mapAskFor[p][name] + } + } + } + + for (var p in askMap) { + if (askMap[p].length == 0) + continue; + + doneSomething = true; + + this.__send_getdata(p, askMap[p]) + } + + if (!doneSomething) { + this.polling = false; // we don't need to poll again + return false; // don't tick again + } + } + + /* + p, {name1: type1, name2: type2, ...} + */ + this.__send_inv = function(p, mapNameTypes) { + self.peermgr.send(p, "inv", mapNameTypes); + } + + /* + p, [name1, name2, name3] + */ + this.__send_getdata = function(p, askList) { + self.peermgr.send(p, "getdata", askList); + } + + /* + p, (InventoryObject) o + */ + this.__send_invobj = function(p, o) { + self.peermgr.send(p, "invobj", o); + } + + this.relay = function(name, now) { + var ir = this.objects.get(name); + + if ('relay' in ir) { + if (ir.relay(this.objects)) { + for (var p in this.tellPeer) { + if (now) { + this.__send_invobj(p, ir); + } else { + this.addTick(); + this.tellPeer[p][name] = ir.type; + } + } + } + } + } + + this.getObj = function(name, mustRelay) { + var ir = this.objects.get(name); + + if (ir.status == "none") + return false; + + if (mustRelay && ir.status == "seen") + return false; + + return ir.__proto__; + } + + this.onGetdata = function(from, msg) { + msg.forEach(function(name) { + if (o = this.getObj(name, true)) { + this.__send_invobj(from, o); + } + }, this) + } + + this.onInv = function(from, msg) { + for (var name in msg) { + // do we already have it? then we don't care + if (this.getObj(name)) { + // we already have it, so who cares + } else { + // start asking for it + // and record who has it + this.peerHas[from][name] = msg[name] + + if (!(name in this.mapAlreadyAskedFor)) { + this.mapAlreadyAskedFor[name] = self.now(); + } else { + this.mapAlreadyAskedFor[name] += 2 * 60 * 1000; + } + this.mapAskFor[from][name] = this.mapAlreadyAskedFor[name]; + this.addTick() + } + } + } + + this.onInvobj = function(from, o) { + // add it + if (this.addObj(o)) { + // stop asking other peers for it (if we are) + delete this.mapAlreadyAskedFor[o.id] + + for (var p in this.mapAskFor) { + delete this.mapAskFor[p][o.id] + } + + // we no longer care that our peers have this object + for (var p in this.peerHas) { + delete this.peerHas[p][o.id] + } + + // now run a handler + self.handle(from, "obj:" + o.type, o.obj) + } + } + + this.addObj = function(obj) { + return obj.seen(this.objects); + } + + // obj must have `name` property + this.createObj = function(type, obj) { + var o = new InventoryObject(type, obj); + + this.objects.create(o); + this.addObj(o); + + return o; + } + + self.on("peermgr:connect", function(from) { + var relaySet = this.objects.find({status:"relay"}); + + this.peerHas[from] = {}; + this.tellPeer[from] = {}; + this.mapAskFor[from] = {}; + + relaySet.forEach(function(o) { + this.tellPeer[from][o.id] = o.type; + }, this) + + this.addTick(); + }, this) + + self.on("peermgr:disconnect", function(from) { + delete this.peerHas[from] + delete this.tellPeer[from] + delete this.mapAlreadyAskedFor[from] + delete this.mapAskFor[from]; + }, this) + + self.on("inv", this.onInv, this) + self.on("getdata", this.onGetdata, this) + self.on("invobj", this.onInvobj, this) + this.addTick(); +} + module.exports = Inventory; \ No newline at end of file diff --git a/sim/btc/mempool.js b/sim/TinyNets/public_html/btc/mempool.js similarity index 100% rename from sim/btc/mempool.js rename to sim/TinyNets/public_html/btc/mempool.js diff --git a/sim/btc/miner.js b/sim/TinyNets/public_html/btc/miner.js old mode 100755 new mode 100644 similarity index 95% rename from sim/btc/miner.js rename to sim/TinyNets/public_html/btc/miner.js index 71fdbfe0ba76e6daec6315c33fe8ea0abb9ce7be..ac023aa4799fb9f22c6564d097397e811c680622 --- a/sim/btc/miner.js +++ b/sim/TinyNets/public_html/btc/miner.js @@ -1,70 +1,70 @@ -function Miner(self) { - self.miner = this; - - this.restage = function () { - self.miner.difficulty = self.miner.staged.difficulty; - if (self.miner.enabled) { - self.miner.stopMining() - self.miner.startMining() - } - } - - self.mprob = 0; - this.mcb = function() { - return new self.blockchain.Block(self.blockchain.chainstate.head, self.now(), self); - } - this.staged = false; - this.enabled = false; - this.difficulty = false; - - // expose mine() to NodeState - self.mine = function(amt, cb) { - this.mprob = amt; - if (cb) - self.miner.mcb = cb; - - self.miner.startMining(); - } - - this.stopMining = function() { - if (!this.enabled) - return; - - this.enabled = false; - self.deprob("mining") - } - - this.startMining = function() { - if (this.enabled) - return; - - this.staged = this.mcb.call(self); // restage next block - this.difficulty = this.staged.difficulty; - this.enabled = true; - - if (self.mprob) { - self.prob("mining", self.mprob / this.staged.difficulty, function() { - self.handle(-1, "miner:success", this.staged); - - this.restage(); - }, this) - } - } - - self.on("miner:success", function(from, b) { - b.time = self.now(); - b.transactions = self.mempool.getList(); - - self.inventory.createObj("block", b) - - if (self.blockchain.chainstate.enter(b) != -1) { - self.inventory.relay(b.id, true); - } - }, this) - - self.on("blockchain:block", function(from, b) { - this.restage(); - }, this) -} - +function Miner(self) { + self.miner = this; + + this.restage = function () { + self.miner.difficulty = self.miner.staged.difficulty; + if (self.miner.enabled) { + self.miner.stopMining() + self.miner.startMining() + } + } + + self.mprob = 0; + this.mcb = function() { + return new self.blockchain.Block(self.blockchain.chainstate.head, self.now(), self); + } + this.staged = false; + this.enabled = false; + this.difficulty = false; + + // expose mine() to NodeState + self.mine = function(amt, cb) { + this.mprob = amt; + if (cb) + self.miner.mcb = cb; + + self.miner.startMining(); + } + + this.stopMining = function() { + if (!this.enabled) + return; + + this.enabled = false; + self.deprob("mining") + } + + this.startMining = function() { + if (this.enabled) + return; + + this.staged = this.mcb.call(self); // restage next block + this.difficulty = this.staged.difficulty; + this.enabled = true; + + if (self.mprob) { + self.prob("mining", self.mprob / this.staged.difficulty, function() { + self.handle(-1, "miner:success", this.staged); + + this.restage(); + }, this) + } + } + + self.on("miner:success", function(from, b) { + b.time = self.now(); + b.transactions = self.mempool.getList(); + + self.inventory.createObj("block", b) + + if (self.blockchain.chainstate.enter(b) != -1) { + self.inventory.relay(b.id, true); + } + }, this) + + self.on("blockchain:block", function(from, b) { + this.restage(); + }, this) +} + module.exports = Miner; \ No newline at end of file diff --git a/sim/btc/transactions.js b/sim/TinyNets/public_html/btc/transactions.js old mode 100755 new mode 100644 similarity index 95% rename from sim/btc/transactions.js rename to sim/TinyNets/public_html/btc/transactions.js index f956b10caaff31312611fac86d28c4105fc499e8..e2973bfbe3d69a3f3da48c3e6f9370d753ea7090 --- a/sim/btc/transactions.js +++ b/sim/TinyNets/public_html/btc/transactions.js @@ -1,389 +1,389 @@ -/* - btc-transactions -*/ - -function TxIn(ref, n) { - this.ref = ref; - this.n = n; - this.k = ref.id + ':' + n; - - this.isvalid = function() {return true;} -} - -function TxOut(txid, n) { - this.n = n; - this.k = txid + ':' + n; -} - -function Transaction(id, inputs, n) { - this.id = id; - this.vin = inputs; - this.vout = []; - - for (var i = 0;i<n;i++) { - this.vout.push(new TxOut(this.id, i)); - } -} - -function MapOrphanTransactions(self) { - this.mapOrphans = []; - this.mapOrphansByPrev = {}; -} - -MapOrphanTransactions.prototype = { - add: function(b) { - if (this.mapOrphans.indexOf(b) != -1) - return false; - - if (this.mapOrphans.length == 100) { - this.delete(this.mapOrphans[0]); - } - - this.mapOrphans.push(b); - - b.vin.forEach(function(input) { - if (!(input.k in this.mapOrphansByPrev)) - this.mapOrphansByPrev[input.k] = [] - - this.mapOrphansByPrev[input.k].push(b) - }, this) - - return true; - }, - - delete: function(b) { - if (this.mapOrphans.indexOf(b) == -1) - return false; - - var removed = this.mapOrphans.splice(this.mapOrphans.indexOf(b), 1) - - b.vin.forEach(function(input) { - var m = this.mapOrphansByPrev[input.k]; - - m.splice(m.indexOf(b), 1); - - if (m.length == 0) { - delete this.mapOrphansByPrev[input.k] - } - }, this) - - return true; - }, - - // returns boolean whether the block is an orphan already - is: function(b) { - if (this.mapOrphans.indexOf(b) == -1) - return false; - - return true; - }, - - // finds any blocks that depended on this block within this maporphans - getForPrev: function(prev) { - var ret = []; - - prev.vout.forEach(function(output) { - if (output.k in this.mapOrphansByPrev) { - ret = ret.concat(this.mapOrphansByPrev[output.k]); - } - }, this) - - return ret; - } -} - -function TransactionState(status, spentBy, spentByN) { - this.status = status; - this.spentBy = spentBy; - this.spentByN = spentByN; - - this.equals = function(v) { - switch (v.status) { - case "spent": - if (this.status == "spent") { - if ('spentBy' in v) { - if (this.spentBy == v.spentBy) { - if ('spentByN' in v) { - if (this.spentByN == v.spentByN) { - return true; - } - } - } - } - } - break; - default: - if (v.status == this.status) - return true; - break; - } - - return false; - } -} - -// Transaction validator: -function TransactionValidator(tx) { - this.tx = tx; - this.state = this.DEFAULT; - this.conflicts = []; -} - -TransactionValidator.prototype = { - DEFAULT: 0, - ORPHAN: 1, - CONFLICT: 2, - INVALID: 3, - VALID: 4, - - clean: function() { - var newConflicts = []; - - this.conflicts.forEach(function(c) { - if (newConflicts.indexOf(c) == -1) { - newConflicts.push(c); - } - }) - - this.conflicts = newConflicts; - }, - - // apply a transaction to the state - apply: function(me) { - if (this.state != this.VALID) { - return false; - } - - var removed = []; - var added = []; - - // remove all conflicting transactions - this.conflicts.forEach(function(ctx) { - ctx.remove(me); - removed.push(ctx); - }, this) - - // Now, spend our inputs... - var spentByN = 0; - - this.tx.vin.forEach(function(input) { - me.set(input.k, new TransactionState("spent", this.tx, spentByN)) - spentByN++; - }, this); - - // Now, set our outputs as unspent... - this.tx.vout.forEach(function(output) { - me.set(output.k, new TransactionState("unspent")) - }, this); - - added.push(this.tx); - - return {added:added, removed:removed}; - }, - - unapply: function(me) { - if (this.state != this.VALID) - return false; - - var removed = []; - - // remove child transactions - this.conflicts.forEach(function(ctx) { - ctx.remove(me); - removed.push(ctx); - }, this) - - this.tx.remove(me); - - removed.push(this.tx); - - return {added:[], removed:removed}; - } -}; - -Transaction.prototype = { - in: function(n) { - return new TxIn(this, n); - }, - - init: function(consensus) { - var n = 0; - - this.vout.forEach(function(output) { - consensus.add(this.id + ':' + output.n, output); - }, this); - }, - - validate: function(me) { - // Check if a transaction is valid given state `me`. - - var fin = new TransactionValidator(this); - - this.vin.forEach(function(input) { - // Check if we have the input in our UTXO - - var ir = me.get(input.k); - - switch (ir.status) { - case "none": - if (fin.state == fin.DEFAULT) { - fin.state = fin.ORPHAN; // This input is not in our UTXO. - } - break; - case "spent": - if (fin.state < fin.INVALID) { - fin.state = fin.CONFLICT; // This input has been spent, and so this tx conflicts with another. - - var sub = ir.spentBy.invalidate(me); - - fin.conflicts.concat(sub.conflicts); - fin.conflicts.push(ir.spentBy); - } - break; - } - - if (!input.isvalid()) { - fin.state = fin.INVALID; // This input is not valid. (Script failed?) - } - }, this) - - if (fin.state == fin.DEFAULT) - fin.state = fin.VALID; // If we didn't run into problems, tx is valid. - - fin.clean(); - - return fin; - }, - - invalidate: function(me) { - var fin = new TransactionValidator(this); - - var fault = false; - - this.vin.forEach(function(input) { - var ir = me.get(input.k); - - if ((ir.status != "spent") || (ir.spentBy != this)) { - fault = true; - } - }, this) - - if (fault) { - fin.state = fin.INVALID; - return fin; - } - - this.vout.forEach(function(output) { - var ir = me.get(output.k); - - if (ir.status == "spent") { - var sub = ir.spentBy.invalidate(me); - - fin.conflicts.concat(sub.conflicts); - fin.conflicts.push(ir.spentBy); - } - }, this) - - fin.state = fin.VALID; - - return fin; - }, - - remove: function(me) { - // delete all outputs - this.vout.forEach(function(output) { - me.set(output.k, new TransactionState("none")) - }, this); - - // set all inputs as unspent (if they weren't already purged) - this.vin.forEach(function(input) { - var cir = me.get(input.k) - - if (cir.status == "spent") - me.set(input.k, new TransactionState("unspent")); - - }, this); - }, -}; - -function Transactions(self) { - self.transactions = this; - - this.UTXO = self.network.shared("UTXO"); - this.mapOrphans = new MapOrphanTransactions(self); - - this.create = function(inputs, n) { - var nid = this.UTXO.rand(); - - var tx = new Transaction(nid, inputs, n); - - this.UTXO.create(tx); - - return tx; - } - - // updates the mempool given a delta of transactions added or removed from the UTXO - this.updateMempool = function(delta) { - delta.added.forEach(function(addTx) { - self.mempool.add(addTx); - }) - delta.removed.forEach(function(removeTx) { - self.mempool.remove(removeTx); - }) - } - - // tries to add transactions which this tx may have - this.processOrphans = function(tx) { - // find any tx in mapOrphans which spends from our TxOuts - - var descend = this.mapOrphans.getForPrev(tx); - - descend.forEach(function(sub) { - if (this.mapOrphans.is(sub) && this.enter(sub)) { - self.log("removed tx from mapOrphans") - self.inventory.relay(sub.id) - this.processOrphans(sub) - } - }, this) - } - - // attempts to enter tx into utxo/mempool/maporphans - // returns bool whether we accepted it, and it should be relayed - this.enter = function(tx, force) { - var val = tx.validate(this.UTXO); - - if (force && val.state == val.CONFLICT) - val.state = val.VALID; - - switch (val.state) { - case val.INVALID: - self.log("rejected tx, invalid") - return false; - break; - case val.ORPHAN: - this.mapOrphans.add(tx); - self.log("rejected tx, added to mapOrphans") - return false; - break; - case val.CONFLICT: - self.log("rejected tx, double-spend") - return false; - break; - case val.VALID: - var delta = val.apply(this.UTXO); // add to UTXO - this.updateMempool(delta); - this.processOrphans(tx); - return true; - break; - } - } - - self.on("obj:tx", function(from, tx) { - self.log("Transactions: received tx " + tx.id) - if (this.enter(tx)) { - self.inventory.relay(tx.id) - } - }, this) -} - +/* + btc-transactions +*/ + +function TxIn(ref, n) { + this.ref = ref; + this.n = n; + this.k = ref.id + ':' + n; + + this.isvalid = function() {return true;} +} + +function TxOut(txid, n) { + this.n = n; + this.k = txid + ':' + n; +} + +function Transaction(id, inputs, n) { + this.id = id; + this.vin = inputs; + this.vout = []; + + for (var i = 0;i<n;i++) { + this.vout.push(new TxOut(this.id, i)); + } +} + +function MapOrphanTransactions(self) { + this.mapOrphans = []; + this.mapOrphansByPrev = {}; +} + +MapOrphanTransactions.prototype = { + add: function(b) { + if (this.mapOrphans.indexOf(b) != -1) + return false; + + if (this.mapOrphans.length == 100) { + this.delete(this.mapOrphans[0]); + } + + this.mapOrphans.push(b); + + b.vin.forEach(function(input) { + if (!(input.k in this.mapOrphansByPrev)) + this.mapOrphansByPrev[input.k] = [] + + this.mapOrphansByPrev[input.k].push(b) + }, this) + + return true; + }, + + delete: function(b) { + if (this.mapOrphans.indexOf(b) == -1) + return false; + + var removed = this.mapOrphans.splice(this.mapOrphans.indexOf(b), 1) + + b.vin.forEach(function(input) { + var m = this.mapOrphansByPrev[input.k]; + + m.splice(m.indexOf(b), 1); + + if (m.length == 0) { + delete this.mapOrphansByPrev[input.k] + } + }, this) + + return true; + }, + + // returns boolean whether the block is an orphan already + is: function(b) { + if (this.mapOrphans.indexOf(b) == -1) + return false; + + return true; + }, + + // finds any blocks that depended on this block within this maporphans + getForPrev: function(prev) { + var ret = []; + + prev.vout.forEach(function(output) { + if (output.k in this.mapOrphansByPrev) { + ret = ret.concat(this.mapOrphansByPrev[output.k]); + } + }, this) + + return ret; + } +} + +function TransactionState(status, spentBy, spentByN) { + this.status = status; + this.spentBy = spentBy; + this.spentByN = spentByN; + + this.equals = function(v) { + switch (v.status) { + case "spent": + if (this.status == "spent") { + if ('spentBy' in v) { + if (this.spentBy == v.spentBy) { + if ('spentByN' in v) { + if (this.spentByN == v.spentByN) { + return true; + } + } + } + } + } + break; + default: + if (v.status == this.status) + return true; + break; + } + + return false; + } +} + +// Transaction validator: +function TransactionValidator(tx) { + this.tx = tx; + this.state = this.DEFAULT; + this.conflicts = []; +} + +TransactionValidator.prototype = { + DEFAULT: 0, + ORPHAN: 1, + CONFLICT: 2, + INVALID: 3, + VALID: 4, + + clean: function() { + var newConflicts = []; + + this.conflicts.forEach(function(c) { + if (newConflicts.indexOf(c) == -1) { + newConflicts.push(c); + } + }) + + this.conflicts = newConflicts; + }, + + // apply a transaction to the state + apply: function(me) { + if (this.state != this.VALID) { + return false; + } + + var removed = []; + var added = []; + + // remove all conflicting transactions + this.conflicts.forEach(function(ctx) { + ctx.remove(me); + removed.push(ctx); + }, this) + + // Now, spend our inputs... + var spentByN = 0; + + this.tx.vin.forEach(function(input) { + me.set(input.k, new TransactionState("spent", this.tx, spentByN)) + spentByN++; + }, this); + + // Now, set our outputs as unspent... + this.tx.vout.forEach(function(output) { + me.set(output.k, new TransactionState("unspent")) + }, this); + + added.push(this.tx); + + return {added:added, removed:removed}; + }, + + unapply: function(me) { + if (this.state != this.VALID) + return false; + + var removed = []; + + // remove child transactions + this.conflicts.forEach(function(ctx) { + ctx.remove(me); + removed.push(ctx); + }, this) + + this.tx.remove(me); + + removed.push(this.tx); + + return {added:[], removed:removed}; + } +}; + +Transaction.prototype = { + in: function(n) { + return new TxIn(this, n); + }, + + init: function(consensus) { + var n = 0; + + this.vout.forEach(function(output) { + consensus.add(this.id + ':' + output.n, output); + }, this); + }, + + validate: function(me) { + // Check if a transaction is valid given state `me`. + + var fin = new TransactionValidator(this); + + this.vin.forEach(function(input) { + // Check if we have the input in our UTXO + + var ir = me.get(input.k); + + switch (ir.status) { + case "none": + if (fin.state == fin.DEFAULT) { + fin.state = fin.ORPHAN; // This input is not in our UTXO. + } + break; + case "spent": + if (fin.state < fin.INVALID) { + fin.state = fin.CONFLICT; // This input has been spent, and so this tx conflicts with another. + + var sub = ir.spentBy.invalidate(me); + + fin.conflicts.concat(sub.conflicts); + fin.conflicts.push(ir.spentBy); + } + break; + } + + if (!input.isvalid()) { + fin.state = fin.INVALID; // This input is not valid. (Script failed?) + } + }, this) + + if (fin.state == fin.DEFAULT) + fin.state = fin.VALID; // If we didn't run into problems, tx is valid. + + fin.clean(); + + return fin; + }, + + invalidate: function(me) { + var fin = new TransactionValidator(this); + + var fault = false; + + this.vin.forEach(function(input) { + var ir = me.get(input.k); + + if ((ir.status != "spent") || (ir.spentBy != this)) { + fault = true; + } + }, this) + + if (fault) { + fin.state = fin.INVALID; + return fin; + } + + this.vout.forEach(function(output) { + var ir = me.get(output.k); + + if (ir.status == "spent") { + var sub = ir.spentBy.invalidate(me); + + fin.conflicts.concat(sub.conflicts); + fin.conflicts.push(ir.spentBy); + } + }, this) + + fin.state = fin.VALID; + + return fin; + }, + + remove: function(me) { + // delete all outputs + this.vout.forEach(function(output) { + me.set(output.k, new TransactionState("none")) + }, this); + + // set all inputs as unspent (if they weren't already purged) + this.vin.forEach(function(input) { + var cir = me.get(input.k) + + if (cir.status == "spent") + me.set(input.k, new TransactionState("unspent")); + + }, this); + }, +}; + +function Transactions(self) { + self.transactions = this; + + this.UTXO = self.network.shared("UTXO"); + this.mapOrphans = new MapOrphanTransactions(self); + + this.create = function(inputs, n) { + var nid = this.UTXO.rand(); + + var tx = new Transaction(nid, inputs, n); + + this.UTXO.create(tx); + + return tx; + } + + // updates the mempool given a delta of transactions added or removed from the UTXO + this.updateMempool = function(delta) { + delta.added.forEach(function(addTx) { + self.mempool.add(addTx); + }) + delta.removed.forEach(function(removeTx) { + self.mempool.remove(removeTx); + }) + } + + // tries to add transactions which this tx may have + this.processOrphans = function(tx) { + // find any tx in mapOrphans which spends from our TxOuts + + var descend = this.mapOrphans.getForPrev(tx); + + descend.forEach(function(sub) { + if (this.mapOrphans.is(sub) && this.enter(sub)) { + self.log("removed tx from mapOrphans") + self.inventory.relay(sub.id) + this.processOrphans(sub) + } + }, this) + } + + // attempts to enter tx into utxo/mempool/maporphans + // returns bool whether we accepted it, and it should be relayed + this.enter = function(tx, force) { + var val = tx.validate(this.UTXO); + + if (force && val.state == val.CONFLICT) + val.state = val.VALID; + + switch (val.state) { + case val.INVALID: + self.log("rejected tx, invalid") + return false; + break; + case val.ORPHAN: + this.mapOrphans.add(tx); + self.log("rejected tx, added to mapOrphans") + return false; + break; + case val.CONFLICT: + self.log("rejected tx, double-spend") + return false; + break; + case val.VALID: + var delta = val.apply(this.UTXO); // add to UTXO + this.updateMempool(delta); + this.processOrphans(tx); + return true; + break; + } + } + + self.on("obj:tx", function(from, tx) { + self.log("Transactions: received tx " + tx.id) + if (this.enter(tx)) { + self.inventory.relay(tx.id) + } + }, this) +} + module.exports = Transactions; \ No newline at end of file diff --git a/sim/d3.v3.min.js b/sim/TinyNets/public_html/d3.v3.min.js old mode 100755 new mode 100644 similarity index 100% rename from sim/d3.v3.min.js rename to sim/TinyNets/public_html/d3.v3.min.js diff --git a/sim/goog/array/array.js b/sim/TinyNets/public_html/goog/array/array.js old mode 100755 new mode 100644 similarity index 100% rename from sim/goog/array/array.js rename to sim/TinyNets/public_html/goog/array/array.js diff --git a/sim/goog/asserts/asserts.js b/sim/TinyNets/public_html/goog/asserts/asserts.js old mode 100755 new mode 100644 similarity index 100% rename from sim/goog/asserts/asserts.js rename to sim/TinyNets/public_html/goog/asserts/asserts.js diff --git a/sim/goog/base.js b/sim/TinyNets/public_html/goog/base.js old mode 100755 new mode 100644 similarity index 100% rename from sim/goog/base.js rename to sim/TinyNets/public_html/goog/base.js diff --git a/sim/goog/bootstrap/nodejs.js b/sim/TinyNets/public_html/goog/bootstrap/nodejs.js old mode 100755 new mode 100644 similarity index 100% rename from sim/goog/bootstrap/nodejs.js rename to sim/TinyNets/public_html/goog/bootstrap/nodejs.js diff --git a/sim/goog/bootstrap/webworkers.js b/sim/TinyNets/public_html/goog/bootstrap/webworkers.js old mode 100755 new mode 100644 similarity index 100% rename from sim/goog/bootstrap/webworkers.js rename to sim/TinyNets/public_html/goog/bootstrap/webworkers.js diff --git a/sim/goog/debug/error.js b/sim/TinyNets/public_html/goog/debug/error.js old mode 100755 new mode 100644 similarity index 100% rename from sim/goog/debug/error.js rename to sim/TinyNets/public_html/goog/debug/error.js diff --git a/sim/goog/deps.js b/sim/TinyNets/public_html/goog/deps.js old mode 100755 new mode 100644 similarity index 100% rename from sim/goog/deps.js rename to sim/TinyNets/public_html/goog/deps.js diff --git a/sim/goog/dom/nodetype.js b/sim/TinyNets/public_html/goog/dom/nodetype.js old mode 100755 new mode 100644 similarity index 100% rename from sim/goog/dom/nodetype.js rename to sim/TinyNets/public_html/goog/dom/nodetype.js diff --git a/sim/goog/object/object.js b/sim/TinyNets/public_html/goog/object/object.js old mode 100755 new mode 100644 similarity index 100% rename from sim/goog/object/object.js rename to sim/TinyNets/public_html/goog/object/object.js diff --git a/sim/goog/string/string.js b/sim/TinyNets/public_html/goog/string/string.js old mode 100755 new mode 100644 similarity index 100% rename from sim/goog/string/string.js rename to sim/TinyNets/public_html/goog/string/string.js diff --git a/sim/goog/structs/heap.js b/sim/TinyNets/public_html/goog/structs/heap.js old mode 100755 new mode 100644 similarity index 100% rename from sim/goog/structs/heap.js rename to sim/TinyNets/public_html/goog/structs/heap.js diff --git a/sim/goog/structs/node.js b/sim/TinyNets/public_html/goog/structs/node.js old mode 100755 new mode 100644 similarity index 100% rename from sim/goog/structs/node.js rename to sim/TinyNets/public_html/goog/structs/node.js diff --git a/sim/goog/structs/priorityqueue.js b/sim/TinyNets/public_html/goog/structs/priorityqueue.js old mode 100755 new mode 100644 similarity index 100% rename from sim/goog/structs/priorityqueue.js rename to sim/TinyNets/public_html/goog/structs/priorityqueue.js diff --git a/sim/graph.js b/sim/TinyNets/public_html/graph.js similarity index 100% rename from sim/graph.js rename to sim/TinyNets/public_html/graph.js diff --git a/sim/hub.js b/sim/TinyNets/public_html/hub.js old mode 100755 new mode 100644 similarity index 97% rename from sim/hub.js rename to sim/TinyNets/public_html/hub.js index 486eb3c72a54bc92e6720ed45b8418bbcb0be2d7..d6415866b75576c7a3a9d7266a160ef69b97535c --- a/sim/hub.js +++ b/sim/TinyNets/public_html/hub.js @@ -1,149 +1,149 @@ -// hub script for dispatching simulation tasks to lots of servers - -console.log("THIS IS NOT STABLE") -process.exit(1) - -var async = require('async') -var sys = require('sys') - -var exec = require('child_process').exec; - -// todo just in case -var escapeshell = function(cmd) { - return '"'+cmd+'"'; -}; - -function runRemoteCommand(host, cmd, out, cb, pr) { - var r = Math.floor(Math.random() * 100000000) - var f; - if (out) - f = "ssh -o \"StrictHostKeyChecking no\" ubuntu@" + host + " " + escapeshell(cmd) + " > " + (out+"-"+r) - else - f = "ssh -o \"StrictHostKeyChecking no\" ubuntu@" + host + " " + escapeshell(cmd); - - exec(f, function(err, stdout, stderr) { - if (err) - console.log(err) - - if (typeof pr != "undefined") - process.stderr.write(stdout.replace(/\s+$/, "")) - - cb(null, null) - }) -} - - -///////////////////////////////////////////////////////////// - -hosts = [ - ["ec2-23-20-147-173.compute-1.amazonaws.com", 8], - ["ec2-54-205-85-161.compute-1.amazonaws.com", 8], - ["ec2-54-204-121-80.compute-1.amazonaws.com", 8], - ["ec2-54-227-3-199.compute-1.amazonaws.com", 8], - ["ec2-54-205-28-168.compute-1.amazonaws.com", 8], - ["ec2-54-204-252-104.compute-1.amazonaws.com", 8], - ["ec2-54-221-142-38.compute-1.amazonaws.com", 8], - ["ec2-54-196-176-198.compute-1.amazonaws.com", 8], - ["ec2-54-196-157-128.compute-1.amazonaws.com", 8], - ["ec2-54-242-127-177.compute-1.amazonaws.com", 8], - ["ec2-54-227-221-158.compute-1.amazonaws.com", 8], - ["ec2-54-196-48-111.compute-1.amazonaws.com", 8], - ["ec2-67-202-55-118.compute-1.amazonaws.com", 8], - ["ec2-54-204-73-120.compute-1.amazonaws.com", 8], - ["ec2-23-20-85-175.compute-1.amazonaws.com", 8], - ["ec2-54-196-173-2.compute-1.amazonaws.com", 8], - ["ec2-54-211-248-182.compute-1.amazonaws.com", 8], - ["ec2-54-196-136-116.compute-1.amazonaws.com", 8], - ["ec2-54-234-230-115.compute-1.amazonaws.com", 8] -] - - -tasks = [] - -for (var i=0;i<50;i++) { - // every percent less than 50 but > 20 - for (var t=0;t<3;t++) { - // 3 trials of each - tasks.push(["cd ebfull.github.io && node sim.js " + (i/100).toFixed(2) + " normal", "/home/ubuntu/sim-"+i+"-normal-"+t]) - tasks.push(["cd ebfull.github.io && node sim.js " + (i/100).toFixed(2) + " sybil", "/home/ubuntu/sim"+i+"-sybil-"+t]) - tasks.push(["cd ebfull.github.io && node sim.js " + (i/100).toFixed(2) + " selfish", "/home/ubuntu/sim"+i+"-selfish-"+t]) - tasks.push(["cd ebfull.github.io && node sim.js " + (i/100).toFixed(2) + " both", "/home/ubuntu/sim"+i+"-both-"+t]) - } -} - -///////////////////////////////////////////////////////////// - -function doStuff() { - - var workers = async.queue(function(arg, cb) { - var server = arg.server; - - var q = async.queue(function(nope, doneWithTasks) { - var task; - - async.whilst(function() {return task = tasks.shift();}, function(taskDone) { - console.log("dispatch (" + server[0] + "): " + task[0]) - runRemoteCommand(server[0], task[0], task[1], function() { - console.log("completed (" + server[0] + "): " + task[0]) - taskDone() - }); - }, doneWithTasks); - }, server[1]) - - q.drain = function() { - host[3] = true; - cb(); - } - - for (var i=0;i<server[1];i++) { - q.push("nope") - } - }, hosts.length) - - workers.drain = function() { - process.exit(1) - } - - hosts.forEach(function(host) { - workers.push({server:host}) - }) - - setInterval(function() { - // get stats for our workers - process.stderr.write("-----------------------\n") - hosts.forEach(function(host) { - if (typeof host[3] == "undefined") { - runRemoteCommand(host[0], "uptime", false, function() { - process.stderr.write(" (" + host[0] + ", concurrency=" + host[1] + ")\n") - }, true) - } else { - process.stderr.write("DONE (" + host[0] + ", concurrency=" + host[1] + ")\n") - } - }) - }, 60 * 1000) -} - -///////////////////////////////////////////////////////////// - -var provision = async.queue(function(host, cb) { - console.log("(" + host + ") provisioning") - - runRemoteCommand(host, "echo -e '\\nMaxSessions 1000\\nMaxStartups 1000\\n' | sudo tee -a /etc/ssh/sshd_config; sudo service ssh restart", false, function() { - runRemoteCommand(host, "ps aux | grep -ie sim.js | awk '{print \\$2}' | xargs kill -9", false, function() { - runRemoteCommand(host, "rm -rf ebfull.github.io; git clone https://github.com/ebfull/ebfull.github.io.git", false, function() { - runRemoteCommand(host, "cd ebfull.github.io; node prep.js sim.js", false, function() { - console.log("(" + host + ") done provisioning"); - cb(); - }) - }) - }); - }); -}, hosts.length); - -provision.drain = function() { - doStuff(); -} - -hosts.forEach(function(h) { - provision.push(h[0]) -}) +// hub script for dispatching simulation tasks to lots of servers + +console.log("THIS IS NOT STABLE") +process.exit(1) + +var async = require('async') +var sys = require('sys') + +var exec = require('child_process').exec; + +// todo just in case +var escapeshell = function(cmd) { + return '"'+cmd+'"'; +}; + +function runRemoteCommand(host, cmd, out, cb, pr) { + var r = Math.floor(Math.random() * 100000000) + var f; + if (out) + f = "ssh -o \"StrictHostKeyChecking no\" ubuntu@" + host + " " + escapeshell(cmd) + " > " + (out+"-"+r) + else + f = "ssh -o \"StrictHostKeyChecking no\" ubuntu@" + host + " " + escapeshell(cmd); + + exec(f, function(err, stdout, stderr) { + if (err) + console.log(err) + + if (typeof pr != "undefined") + process.stderr.write(stdout.replace(/\s+$/, "")) + + cb(null, null) + }) +} + + +///////////////////////////////////////////////////////////// + +hosts = [ + ["ec2-23-20-147-173.compute-1.amazonaws.com", 8], + ["ec2-54-205-85-161.compute-1.amazonaws.com", 8], + ["ec2-54-204-121-80.compute-1.amazonaws.com", 8], + ["ec2-54-227-3-199.compute-1.amazonaws.com", 8], + ["ec2-54-205-28-168.compute-1.amazonaws.com", 8], + ["ec2-54-204-252-104.compute-1.amazonaws.com", 8], + ["ec2-54-221-142-38.compute-1.amazonaws.com", 8], + ["ec2-54-196-176-198.compute-1.amazonaws.com", 8], + ["ec2-54-196-157-128.compute-1.amazonaws.com", 8], + ["ec2-54-242-127-177.compute-1.amazonaws.com", 8], + ["ec2-54-227-221-158.compute-1.amazonaws.com", 8], + ["ec2-54-196-48-111.compute-1.amazonaws.com", 8], + ["ec2-67-202-55-118.compute-1.amazonaws.com", 8], + ["ec2-54-204-73-120.compute-1.amazonaws.com", 8], + ["ec2-23-20-85-175.compute-1.amazonaws.com", 8], + ["ec2-54-196-173-2.compute-1.amazonaws.com", 8], + ["ec2-54-211-248-182.compute-1.amazonaws.com", 8], + ["ec2-54-196-136-116.compute-1.amazonaws.com", 8], + ["ec2-54-234-230-115.compute-1.amazonaws.com", 8] +] + + +tasks = [] + +for (var i=0;i<50;i++) { + // every percent less than 50 but > 20 + for (var t=0;t<3;t++) { + // 3 trials of each + tasks.push(["cd ebfull.github.io && node sim.js " + (i/100).toFixed(2) + " normal", "/home/ubuntu/sim-"+i+"-normal-"+t]) + tasks.push(["cd ebfull.github.io && node sim.js " + (i/100).toFixed(2) + " sybil", "/home/ubuntu/sim"+i+"-sybil-"+t]) + tasks.push(["cd ebfull.github.io && node sim.js " + (i/100).toFixed(2) + " selfish", "/home/ubuntu/sim"+i+"-selfish-"+t]) + tasks.push(["cd ebfull.github.io && node sim.js " + (i/100).toFixed(2) + " both", "/home/ubuntu/sim"+i+"-both-"+t]) + } +} + +///////////////////////////////////////////////////////////// + +function doStuff() { + + var workers = async.queue(function(arg, cb) { + var server = arg.server; + + var q = async.queue(function(nope, doneWithTasks) { + var task; + + async.whilst(function() {return task = tasks.shift();}, function(taskDone) { + console.log("dispatch (" + server[0] + "): " + task[0]) + runRemoteCommand(server[0], task[0], task[1], function() { + console.log("completed (" + server[0] + "): " + task[0]) + taskDone() + }); + }, doneWithTasks); + }, server[1]) + + q.drain = function() { + host[3] = true; + cb(); + } + + for (var i=0;i<server[1];i++) { + q.push("nope") + } + }, hosts.length) + + workers.drain = function() { + process.exit(1) + } + + hosts.forEach(function(host) { + workers.push({server:host}) + }) + + setInterval(function() { + // get stats for our workers + process.stderr.write("-----------------------\n") + hosts.forEach(function(host) { + if (typeof host[3] == "undefined") { + runRemoteCommand(host[0], "uptime", false, function() { + process.stderr.write(" (" + host[0] + ", concurrency=" + host[1] + ")\n") + }, true) + } else { + process.stderr.write("DONE (" + host[0] + ", concurrency=" + host[1] + ")\n") + } + }) + }, 60 * 1000) +} + +///////////////////////////////////////////////////////////// + +var provision = async.queue(function(host, cb) { + console.log("(" + host + ") provisioning") + + runRemoteCommand(host, "echo -e '\\nMaxSessions 1000\\nMaxStartups 1000\\n' | sudo tee -a /etc/ssh/sshd_config; sudo service ssh restart", false, function() { + runRemoteCommand(host, "ps aux | grep -ie sim.js | awk '{print \\$2}' | xargs kill -9", false, function() { + runRemoteCommand(host, "rm -rf ebfull.github.io; git clone https://github.com/ebfull/ebfull.github.io.git", false, function() { + runRemoteCommand(host, "cd ebfull.github.io; node prep.js sim.js", false, function() { + console.log("(" + host + ") done provisioning"); + cb(); + }) + }) + }); + }); +}, hosts.length); + +provision.drain = function() { + doStuff(); +} + +hosts.forEach(function(h) { + provision.push(h[0]) +}) diff --git a/sim/index.html b/sim/TinyNets/public_html/index.html old mode 100755 new mode 100644 similarity index 96% rename from sim/index.html rename to sim/TinyNets/public_html/index.html index 5d1d21aef77df9415deeee8a6ce1de626b232ebd..be54b24e2a768821c6714ca2b78b7ff58fafce56 --- a/sim/index.html +++ b/sim/TinyNets/public_html/index.html @@ -1,583 +1,583 @@ -<!-- simbit visualizer frontend for in-browser simulation --> -<!doctype html> -<html> - <head> - <title>simbit</title> - <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> - <style> - body { - font: 10px sans-serif; - } - - .network { - border: 1px solid #DEDEDE; - display:block; - margin:auto; - } - - svg { - margin: auto; - display:block; - } - - rect { - fill: none; - pointer-events: all; - } - - .node { - fill: #000; - } - - .link { - stroke: #999; - } - - input[type="number"] { - width: 50px; - } - - path { - stroke: steelblue; - stroke-width: 2; - fill: none; - } - - path.diff { - stroke: red; - } - - path.avShort { - stroke: green; - } - - .axis path, - .axis line { - fill: none; - stroke: grey; - stroke-width: 1; - shape-rendering: crispEdges; - } - </style> - - <script type="text/javascript" src="d3.v3.min.js"></script> - <script type="text/javascript" src="jquery.min.js"></script> - <script type="text/javascript" src="require.js"></script> - <script src="graph.js"></script> - <script src="goog/base.js"></script> - <script> - goog.require("goog.structs.PriorityQueue") - </script> - </head> - <body> - <table> - <tr> - <td id="buttonparent"> - <div style="float:left"> - <input id="disable" type="button" value="Disable Visualizer"> - </div> - </td> - <td id="optionparent"> - <div id="graphwindows" style="display:none"> - Short window: - <input id="shortwindow" type="number" min=1 value=10> - Long window: - <input id="longwindow" type="number" min=10 step=10 value=100> - </div> - </td> - </tr> - <tr> - <td id="networkparent"> - <div class="network" style="width: 700px; height: 500px"></div> - </td> - <td id="graphparent" style="display:none"> - <div class="graph" style="width: 700px; height: 500px"></div> - </td> - <td id="logs" valign="top"> - - </td> - </tr> - <tr> - <td valign="top"> - <div style="float:right"> - Elapsed: <span id="elapsed">0 seconds</span> - <input id="play" type="button" value="Play"> - <input id="pause" type="button" value="Pause"> - </div> - - <div style="float:left"> - Speed: <input id="slower" type="button" value="⇠"><input id="defaultspeed" type="button" value="Default"><input id="faster" type="button" value="⇢"> - <span id="relspeed">(1.00x)</span> - </div> - </td> - <td id="timeBetweenBlocks" style="display:none" valign="top"> - <center> - <h1>time between each block</h1> - </center> - <div class='scatter'> - <!-- /the chart goes here --> - </div> - </td> - </tr> - </table> - - - <script type="text/javascript"> - // make it so that net.run() doesn't actually run - var DELAY_RUN = {net:false}; - - function Visualizer(div) { - this.nindex = 0; - this.svg = null; - this.divname = div; - this.force = null; - this.nodes = null; - this.links = null; - this.slink = null; - this.snode = null; - this.edges = {}; - this.inodes = []; - this.lastmsgs = []; - this.updated = false; - this.colormap = {}; - this.colormap_u = false; - this.link_colormap = {}; - this.link_colormap_last = 0; - } - - Visualizer.prototype = { - width: 700, - height: 500, - linkDistance: 30, - charge: -150, - gravity: .5, - - drawGraph: function(data) { - var maxx = d3.max(data, function(d) { return d[0]; }) - var maxdiff = d3.max(data, function(d) { return d[1]; }) - var mintime = d3.min(data, function(d) { return d[2]; }) - var maxtime = d3.max(data, function(d) { return d[2]; }) - - var margin = {top: 20, right: 60, bottom: 60, left: 60}, - width = 700 - margin.left - margin.right, - height = 500 - margin.top - margin.bottom; - - var x = d3.scale.linear() - .domain([0, maxx]) - .range([ 0, width]); - - var diff = d3.scale.linear() - .domain([0, maxdiff]) - .range([ height, 0 ]); - - var time = d3.scale.linear() - .domain([mintime, maxtime]) - .range([ height, 0 ]); - - $("#graphparent").css('display', 'block') - $("#graphwindows").css('display', 'inline') - $(".graph").html("") - - var chart = d3.select('.graph') - .append('svg:svg') - .attr('width', width + margin.right + margin.left) - .attr('height', height + margin.top + margin.bottom) - .attr('class', 'chart') - - var main = chart.append('g') - .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')') - .attr('width', width) - .attr('height', height) - .attr('class', 'main') - - // draw the x axis - var xAxis = d3.svg.axis() - .scale(x) - .orient('bottom'); - - main.append('g') - .attr('transform', 'translate(0,' + height + ')') - .attr('class', 'main axis date') - .call(xAxis) - .append("text") - .attr("dx", ".71em") - .attr("x", 420) - .attr("y", -6) - .style("text-anchor", "end") - .text("Block"); - - // draw the d axis - var diffAxis = d3.svg.axis() - .scale(diff) - .orient('left'); - - main.append('g') - .attr('transform', 'translate(0,0)') - .attr('class', 'main axis date') - .call(diffAxis) - .append("text") - .attr("transform", "rotate(-90)") - .attr("y", 6) - .attr("dy", ".71em") - .style("text-anchor", "end") - .text("Difficulty"); - - // draw the t axis - var timeAxis = d3.svg.axis() - .scale(time) - .orient('right'); - - main.append('g') - .attr('transform', 'translate(' + width + ',0)') - .attr('class', 'main axis date') - .call(timeAxis) - .append("text") - .attr("transform", "rotate(-90)") - .attr("y", -10) - .attr("dy", ".71em") - .style("text-anchor", "end") - .text("Block time"); - - var dline = d3.svg.line() - .x(function(d) { return x(d[0]); }) - .y(function(d) { return diff(d[1]); }); - - var tsline = d3.svg.line() - .x(function(d) { return x(d[0]); }) - .y(function(d) { return time(d[2]); }); - - var tlline = d3.svg.line() - .x(function(d) { return x(d[0]); }) - .y(function(d) { return time(d[3]); }); - - var g = main.append("svg:g"); - - g.append("path") - .attr("class", "line diff" ) - .attr("d", dline(data) ); - - g.append("path") - .attr("class", "line avShort" ) - .attr("d", tsline(data) ); - - g.append("path") - .attr("class", "line" ) - .attr("d", tlline(data) ); - }, - - drawScatter: function(data) { - var maxy = d3.max(data, function(d) { return d[1]; }) - var maxx = d3.max(data, function(d) { return d[0]; }) - - var margin = {top: 20, right: 15, bottom: 60, left: 60} - , width = 500 - margin.left - margin.right - , height = 300 - margin.top - margin.bottom; - - var x = d3.scale.linear() - .domain([0, 1200]) - .range([ 0, width]); - - var y = d3.scale.linear() - .domain([0, maxy]) - .range([ height, 0 ]); - - $("#timeBetweenBlocks").css('display', 'block') - $(".scatter").html("") - - var chart = d3.select('.scatter') - .append('svg:svg') - .attr('width', width + margin.right + margin.left) - .attr('height', height + margin.top + margin.bottom) - .attr('class', 'chart') - - var main = chart.append('g') - .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')') - .attr('width', width) - .attr('height', height) - .attr('class', 'main') - - // draw the x axis - var xAxis = d3.svg.axis() - .scale(x) - .orient('bottom'); - - main.append('g') - .attr('transform', 'translate(0,' + height + ')') - .attr('class', 'main axis date') - .call(xAxis) - .append("text") - .attr("dx", ".71em") - .attr("x", 420) - .attr("y", -6) - .style("text-anchor", "end") - .text("Time between blocks (in seconds)"); - - // draw the y axis - var yAxis = d3.svg.axis() - .scale(y) - .orient('left'); - - main.append('g') - .attr('transform', 'translate(0,0)') - .attr('class', 'main axis date') - .call(yAxis) - .append("text") - .attr("transform", "rotate(-90)") - .attr("y", 6) - .attr("dy", ".71em") - .style("text-anchor", "end") - .text("Blocks"); - - var g = main.append("svg:g"); - - g.selectAll("scatter-dots") - .data(data) - .enter().append("svg:circle") - .attr("cx", function (d,i) { return x(d[0]); } ) - .attr("cy", function (d) { return y(d[1]); } ) - .attr("r", 2); - }, - - init: function() { - // init the network layout/svg - $(this.divname).css('width', this.width); - $(this.divname).css('height', this.height); - - this.force = d3.layout.force() - .size([this.width,this.height]) - .nodes([]) // no nodes - .friction(0.5) - .linkDistance(function(link) { - return link.linkDistance; - }) - .charge(this.charge) - .gravity(this.gravity); - - this.svg = d3.select(this.divname).append("svg") - .attr("width", this.width) - .attr("height", this.height); - - this.svg.append("rect") - .attr("width", this.width) - .attr("height", this.height); - - this.nodes = this.force.nodes(); - this.links = this.force.links(); - this.slink = this.svg.selectAll(".link"); - this.snode = this.svg.selectAll(".node"); - - this.force = this.force.on("tick", this.tick()); - - this.updated = true; - this.rehash(0); - }, - - log: function(msg) { - this.lastmsgs.push(msg); - if (this.lastmsgs.length > 35) - this.lastmsgs.shift(); - - var newlogs = ""; - - this.lastmsgs.forEach(function(e) { - newlogs += e + "<br />"; - }) - - $("#logs").html(newlogs); - }, - - setColor: function(p, color) { - this.colormap_u = true; - this.colormap[p] = color; - }, - - setLinkActivity: function(p, now) { - this.link_colormap[p] = now; - this.link_colormap_last = 0; - }, - - getRandomLink: function() { - var result; - var count=1; - for (var prop in this.edges) { - if (Math.random() < 1/++count) - result = prop; - } - if (!result) - return -1; - var e = result.split("-"); - return [parseInt(e[0]), parseInt(e[1])]; - }, - - getRandomNode: function() { - return this.inodes[Math.floor(Math.random()*this.inodes.length)]; - }, - - getKeyForID: function(id) { - return this.inodes.indexOf(id); - }, - - incCharge: function(amt) { - this.force.charge(this.force.charge() - amt); - this.updated = true; - ///////////this.rehash(); - }, - - addNode: function() { - // add a node, return the index - this.nodes.push({id:"n"+this.nindex}); - this.inodes.push(this.nindex); - this.updated = true; - /////////////this.rehash(); - - this.nindex++; - return this.nindex-1; - }, - - connect: function(a, b, latency) { - if (this.edges.hasOwnProperty(a + '-' + b) || this.edges.hasOwnProperty(b + '-' + a)) - return false; // we're already connected - - if (a==b) - return false; // can't connect to ourself silly! - - this.edges[a + '-' + b] = {source:this.nodes[this.getKeyForID(a)],target:this.nodes[this.getKeyForID(b)],linkDistance:latency}; - this.links.push(this.edges[a + '-' + b]); - - this.updated = true; - //////this.rehash(); - }, - - disconnect: function(a, b) { - if (!this.edges.hasOwnProperty(a + '-' + b) && !this.edges.hasOwnProperty(b + '-' + a)) - return false; // we're already disconnected - - var i = this.links.indexOf(this.edges[a + '-' + b]); - if (i<0) - i = this.links.indexOf(this.edges[b + '-' + a]); - - delete this.edges[a + '-' + b]; - delete this.edges[b + '-' + a]; - - this.links.splice(i, 1); // remove the link - - this.updated = true; - //////this.rehash(); - }, - - removeNode: function(index) { - // remove a node at index - var i = this.getKeyForID(index); - if (i < 0) - return false; // this one has already been removed - - this.nodes.splice(i, 1); - this.inodes.splice(i, 1); - this.updated = true; - ///////////////////this.rehash(); - }, - - tick: function() { - var svg = this.svg; - return function() { - svg.selectAll(".link").attr("x1", function(d) { return d.source.x; }) - .attr("y1", function(d) { return d.source.y; }) - .attr("x2", function(d) { return d.target.x; }) - .attr("y2", function(d) { return d.target.y; }) - .attr("id", function(d) {return "l-" + d.source.id + "-" + d.target.id;}); - - svg.selectAll(".node").attr("cx", function(d) { return d.x; }) - .attr("cy", function(d) { return d.y; }); - } - }, - - rehash: function(now) { - /***** COLORMAP *****/ - if (this.colormap_u) { - for (var p in this.colormap) { - $(".n" + p).css('fill', this.colormap[p]); - } - this.colormap_u = false; - } - - if (this.link_colormap_last < (now-100)) { - this.link_colormap_last = now; - for (var p in this.link_colormap) { - if (this.link_colormap[p] + 100 > now) { - $("#l-" + p).css('stroke', "black") - } else { - $("#l-" + p).css('stroke', "#999") - delete this.link_colormap[p]; - } - } - } - - if (!this.updated) - return; - - this.slink = this.slink.data(this.force.links(), function(d) { return d.source.id + "-" + d.target.id; }); - this.slink.enter().insert("line", ".node") - .attr("class", "link"); - this.slink.exit().remove(); - - this.snode = this.snode.data(this.force.nodes(), function(d) {return d.id;}); - this.snode.enter().append("circle").attr("class", function (d) {return "node " + d.id;}) - .attr("r", 3) - // .call(this.force.drag); - this.snode.exit().remove(); - - this.force.start(); - - this.updated = false; - } - }; - - $("#network").html(""); - var VISUALIZER = new Visualizer(".network"); - VISUALIZER.init(); - </script> - <script type="text/javascript" src="sim.js"></script> - <script type="text/javascript"> - var amt = 10; - var play = false; - - $("#play").click(function() { - play = true; - }) - - $("#pause").click(function() { - play = false; - }) - - $("#slower").click(function() { - amt = amt / 2; - $("#relspeed").html("(" + (amt / 10).toFixed(2) + "x)"); - }) - - $("#faster").click(function() { - amt = amt * 2; - $("#relspeed").html("(" + (amt / 10).toFixed(2) + "x)"); - }) - - $("#defaultspeed").click(function() { - amt = 10; - $("#relspeed").html("(" + (amt / 10).toFixed(2) + "x)"); - }) - - $("#disable").click(function() { - VISUALIZER.rehash = function() {return;} - VISUALIZER.force.stop(); - $("#disable").prop("disabled", true); - }) - $("#disable").prop("disabled", false); - - setInterval(function() { - if (play && DELAY_RUN.net) { - DELAY_RUN.net._run(amt); - VISUALIZER.rehash(DELAY_RUN.net.now); - $("#elapsed").html((DELAY_RUN.net.now / 1000).toFixed(2) + " seconds") - } - }, 10); - </script> - </body> -</html> +<!-- simbit visualizer frontend for in-browser simulation --> +<!doctype html> +<html> + <head> + <title>simbit</title> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> + <style> + body { + font: 10px sans-serif; + } + + .network { + border: 1px solid #DEDEDE; + display:block; + margin:auto; + } + + svg { + margin: auto; + display:block; + } + + rect { + fill: none; + pointer-events: all; + } + + .node { + fill: #000; + } + + .link { + stroke: #999; + } + + input[type="number"] { + width: 50px; + } + + path { + stroke: steelblue; + stroke-width: 2; + fill: none; + } + + path.diff { + stroke: red; + } + + path.avShort { + stroke: green; + } + + .axis path, + .axis line { + fill: none; + stroke: grey; + stroke-width: 1; + shape-rendering: crispEdges; + } + </style> + + <script type="text/javascript" src="d3.v3.min.js"></script> + <script type="text/javascript" src="jquery.min.js"></script> + <script type="text/javascript" src="require.js"></script> + <script src="graph.js"></script> + <script src="goog/base.js"></script> + <script> + goog.require("goog.structs.PriorityQueue") + </script> + </head> + <body> + <table> + <tr> + <td id="buttonparent"> + <div style="float:left"> + <input id="disable" type="button" value="Disable Visualizer"> + </div> + </td> + <td id="optionparent"> + <div id="graphwindows" style="display:none"> + Short window: + <input id="shortwindow" type="number" min=1 value=10> + Long window: + <input id="longwindow" type="number" min=10 step=10 value=100> + </div> + </td> + </tr> + <tr> + <td id="networkparent"> + <div class="network" style="width: 700px; height: 500px"></div> + </td> + <td id="graphparent" style="display:none"> + <div class="graph" style="width: 700px; height: 500px"></div> + </td> + <td id="logs" valign="top"> + + </td> + </tr> + <tr> + <td valign="top"> + <div style="float:right"> + Elapsed: <span id="elapsed">0 seconds</span> + <input id="play" type="button" value="Play"> + <input id="pause" type="button" value="Pause"> + </div> + + <div style="float:left"> + Speed: <input id="slower" type="button" value="⇠"><input id="defaultspeed" type="button" value="Default"><input id="faster" type="button" value="⇢"> + <span id="relspeed">(1.00x)</span> + </div> + </td> + <td id="timeBetweenBlocks" style="display:none" valign="top"> + <center> + <h1>time between each block</h1> + </center> + <div class='scatter'> + <!-- /the chart goes here --> + </div> + </td> + </tr> + </table> + + + <script type="text/javascript"> + // make it so that net.run() doesn't actually run + var DELAY_RUN = {net:false}; + + function Visualizer(div) { + this.nindex = 0; + this.svg = null; + this.divname = div; + this.force = null; + this.nodes = null; + this.links = null; + this.slink = null; + this.snode = null; + this.edges = {}; + this.inodes = []; + this.lastmsgs = []; + this.updated = false; + this.colormap = {}; + this.colormap_u = false; + this.link_colormap = {}; + this.link_colormap_last = 0; + } + + Visualizer.prototype = { + width: 700, + height: 500, + linkDistance: 30, + charge: -150, + gravity: .5, + + drawGraph: function(data) { + var maxx = d3.max(data, function(d) { return d[0]; }) + var maxdiff = d3.max(data, function(d) { return d[1]; }) + var mintime = d3.min(data, function(d) { return d[2]; }) + var maxtime = d3.max(data, function(d) { return d[2]; }) + + var margin = {top: 20, right: 60, bottom: 60, left: 60}, + width = 700 - margin.left - margin.right, + height = 500 - margin.top - margin.bottom; + + var x = d3.scale.linear() + .domain([0, maxx]) + .range([ 0, width]); + + var diff = d3.scale.linear() + .domain([0, maxdiff]) + .range([ height, 0 ]); + + var time = d3.scale.linear() + .domain([mintime, maxtime]) + .range([ height, 0 ]); + + $("#graphparent").css('display', 'block') + $("#graphwindows").css('display', 'inline') + $(".graph").html("") + + var chart = d3.select('.graph') + .append('svg:svg') + .attr('width', width + margin.right + margin.left) + .attr('height', height + margin.top + margin.bottom) + .attr('class', 'chart') + + var main = chart.append('g') + .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')') + .attr('width', width) + .attr('height', height) + .attr('class', 'main') + + // draw the x axis + var xAxis = d3.svg.axis() + .scale(x) + .orient('bottom'); + + main.append('g') + .attr('transform', 'translate(0,' + height + ')') + .attr('class', 'main axis date') + .call(xAxis) + .append("text") + .attr("dx", ".71em") + .attr("x", 420) + .attr("y", -6) + .style("text-anchor", "end") + .text("Block"); + + // draw the d axis + var diffAxis = d3.svg.axis() + .scale(diff) + .orient('left'); + + main.append('g') + .attr('transform', 'translate(0,0)') + .attr('class', 'main axis date') + .call(diffAxis) + .append("text") + .attr("transform", "rotate(-90)") + .attr("y", 6) + .attr("dy", ".71em") + .style("text-anchor", "end") + .text("Difficulty"); + + // draw the t axis + var timeAxis = d3.svg.axis() + .scale(time) + .orient('right'); + + main.append('g') + .attr('transform', 'translate(' + width + ',0)') + .attr('class', 'main axis date') + .call(timeAxis) + .append("text") + .attr("transform", "rotate(-90)") + .attr("y", -10) + .attr("dy", ".71em") + .style("text-anchor", "end") + .text("Block time"); + + var dline = d3.svg.line() + .x(function(d) { return x(d[0]); }) + .y(function(d) { return diff(d[1]); }); + + var tsline = d3.svg.line() + .x(function(d) { return x(d[0]); }) + .y(function(d) { return time(d[2]); }); + + var tlline = d3.svg.line() + .x(function(d) { return x(d[0]); }) + .y(function(d) { return time(d[3]); }); + + var g = main.append("svg:g"); + + g.append("path") + .attr("class", "line diff" ) + .attr("d", dline(data) ); + + g.append("path") + .attr("class", "line avShort" ) + .attr("d", tsline(data) ); + + g.append("path") + .attr("class", "line" ) + .attr("d", tlline(data) ); + }, + + drawScatter: function(data) { + var maxy = d3.max(data, function(d) { return d[1]; }) + var maxx = d3.max(data, function(d) { return d[0]; }) + + var margin = {top: 20, right: 15, bottom: 60, left: 60} + , width = 500 - margin.left - margin.right + , height = 300 - margin.top - margin.bottom; + + var x = d3.scale.linear() + .domain([0, 1200]) + .range([ 0, width]); + + var y = d3.scale.linear() + .domain([0, maxy]) + .range([ height, 0 ]); + + $("#timeBetweenBlocks").css('display', 'block') + $(".scatter").html("") + + var chart = d3.select('.scatter') + .append('svg:svg') + .attr('width', width + margin.right + margin.left) + .attr('height', height + margin.top + margin.bottom) + .attr('class', 'chart') + + var main = chart.append('g') + .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')') + .attr('width', width) + .attr('height', height) + .attr('class', 'main') + + // draw the x axis + var xAxis = d3.svg.axis() + .scale(x) + .orient('bottom'); + + main.append('g') + .attr('transform', 'translate(0,' + height + ')') + .attr('class', 'main axis date') + .call(xAxis) + .append("text") + .attr("dx", ".71em") + .attr("x", 420) + .attr("y", -6) + .style("text-anchor", "end") + .text("Time between blocks (in seconds)"); + + // draw the y axis + var yAxis = d3.svg.axis() + .scale(y) + .orient('left'); + + main.append('g') + .attr('transform', 'translate(0,0)') + .attr('class', 'main axis date') + .call(yAxis) + .append("text") + .attr("transform", "rotate(-90)") + .attr("y", 6) + .attr("dy", ".71em") + .style("text-anchor", "end") + .text("Blocks"); + + var g = main.append("svg:g"); + + g.selectAll("scatter-dots") + .data(data) + .enter().append("svg:circle") + .attr("cx", function (d,i) { return x(d[0]); } ) + .attr("cy", function (d) { return y(d[1]); } ) + .attr("r", 2); + }, + + init: function() { + // init the network layout/svg + $(this.divname).css('width', this.width); + $(this.divname).css('height', this.height); + + this.force = d3.layout.force() + .size([this.width,this.height]) + .nodes([]) // no nodes + .friction(0.5) + .linkDistance(function(link) { + return link.linkDistance; + }) + .charge(this.charge) + .gravity(this.gravity); + + this.svg = d3.select(this.divname).append("svg") + .attr("width", this.width) + .attr("height", this.height); + + this.svg.append("rect") + .attr("width", this.width) + .attr("height", this.height); + + this.nodes = this.force.nodes(); + this.links = this.force.links(); + this.slink = this.svg.selectAll(".link"); + this.snode = this.svg.selectAll(".node"); + + this.force = this.force.on("tick", this.tick()); + + this.updated = true; + this.rehash(0); + }, + + log: function(msg) { + this.lastmsgs.push(msg); + if (this.lastmsgs.length > 35) + this.lastmsgs.shift(); + + var newlogs = ""; + + this.lastmsgs.forEach(function(e) { + newlogs += e + "<br />"; + }) + + $("#logs").html(newlogs); + }, + + setColor: function(p, color) { + this.colormap_u = true; + this.colormap[p] = color; + }, + + setLinkActivity: function(p, now) { + this.link_colormap[p] = now; + this.link_colormap_last = 0; + }, + + getRandomLink: function() { + var result; + var count=1; + for (var prop in this.edges) { + if (Math.random() < 1/++count) + result = prop; + } + if (!result) + return -1; + var e = result.split("-"); + return [parseInt(e[0]), parseInt(e[1])]; + }, + + getRandomNode: function() { + return this.inodes[Math.floor(Math.random()*this.inodes.length)]; + }, + + getKeyForID: function(id) { + return this.inodes.indexOf(id); + }, + + incCharge: function(amt) { + this.force.charge(this.force.charge() - amt); + this.updated = true; + ///////////this.rehash(); + }, + + addNode: function() { + // add a node, return the index + this.nodes.push({id:"n"+this.nindex}); + this.inodes.push(this.nindex); + this.updated = true; + /////////////this.rehash(); + + this.nindex++; + return this.nindex-1; + }, + + connect: function(a, b, latency) { + if (this.edges.hasOwnProperty(a + '-' + b) || this.edges.hasOwnProperty(b + '-' + a)) + return false; // we're already connected + + if (a==b) + return false; // can't connect to ourself silly! + + this.edges[a + '-' + b] = {source:this.nodes[this.getKeyForID(a)],target:this.nodes[this.getKeyForID(b)],linkDistance:latency}; + this.links.push(this.edges[a + '-' + b]); + + this.updated = true; + //////this.rehash(); + }, + + disconnect: function(a, b) { + if (!this.edges.hasOwnProperty(a + '-' + b) && !this.edges.hasOwnProperty(b + '-' + a)) + return false; // we're already disconnected + + var i = this.links.indexOf(this.edges[a + '-' + b]); + if (i<0) + i = this.links.indexOf(this.edges[b + '-' + a]); + + delete this.edges[a + '-' + b]; + delete this.edges[b + '-' + a]; + + this.links.splice(i, 1); // remove the link + + this.updated = true; + //////this.rehash(); + }, + + removeNode: function(index) { + // remove a node at index + var i = this.getKeyForID(index); + if (i < 0) + return false; // this one has already been removed + + this.nodes.splice(i, 1); + this.inodes.splice(i, 1); + this.updated = true; + ///////////////////this.rehash(); + }, + + tick: function() { + var svg = this.svg; + return function() { + svg.selectAll(".link").attr("x1", function(d) { return d.source.x; }) + .attr("y1", function(d) { return d.source.y; }) + .attr("x2", function(d) { return d.target.x; }) + .attr("y2", function(d) { return d.target.y; }) + .attr("id", function(d) {return "l-" + d.source.id + "-" + d.target.id;}); + + svg.selectAll(".node").attr("cx", function(d) { return d.x; }) + .attr("cy", function(d) { return d.y; }); + } + }, + + rehash: function(now) { + /***** COLORMAP *****/ + if (this.colormap_u) { + for (var p in this.colormap) { + $(".n" + p).css('fill', this.colormap[p]); + } + this.colormap_u = false; + } + + if (this.link_colormap_last < (now-100)) { + this.link_colormap_last = now; + for (var p in this.link_colormap) { + if (this.link_colormap[p] + 100 > now) { + $("#l-" + p).css('stroke', "black") + } else { + $("#l-" + p).css('stroke', "#999") + delete this.link_colormap[p]; + } + } + } + + if (!this.updated) + return; + + this.slink = this.slink.data(this.force.links(), function(d) { return d.source.id + "-" + d.target.id; }); + this.slink.enter().insert("line", ".node") + .attr("class", "link"); + this.slink.exit().remove(); + + this.snode = this.snode.data(this.force.nodes(), function(d) {return d.id;}); + this.snode.enter().append("circle").attr("class", function (d) {return "node " + d.id;}) + .attr("r", 3) + // .call(this.force.drag); + this.snode.exit().remove(); + + this.force.start(); + + this.updated = false; + } + }; + + $("#network").html(""); + var VISUALIZER = new Visualizer(".network"); + VISUALIZER.init(); + </script> + <script type="text/javascript" src="sim.js"></script> + <script type="text/javascript"> + var amt = 10; + var play = false; + + $("#play").click(function() { + play = true; + }) + + $("#pause").click(function() { + play = false; + }) + + $("#slower").click(function() { + amt = amt / 2; + $("#relspeed").html("(" + (amt / 10).toFixed(2) + "x)"); + }) + + $("#faster").click(function() { + amt = amt * 2; + $("#relspeed").html("(" + (amt / 10).toFixed(2) + "x)"); + }) + + $("#defaultspeed").click(function() { + amt = 10; + $("#relspeed").html("(" + (amt / 10).toFixed(2) + "x)"); + }) + + $("#disable").click(function() { + VISUALIZER.rehash = function() {return;} + VISUALIZER.force.stop(); + $("#disable").prop("disabled", true); + }) + $("#disable").prop("disabled", false); + + setInterval(function() { + if (play && DELAY_RUN.net) { + DELAY_RUN.net._run(amt); + VISUALIZER.rehash(DELAY_RUN.net.now); + $("#elapsed").html((DELAY_RUN.net.now / 1000).toFixed(2) + " seconds") + } + }, 10); + </script> + </body> +</html> diff --git a/sim/jquery.min.js b/sim/TinyNets/public_html/jquery.min.js old mode 100755 new mode 100644 similarity index 100% rename from sim/jquery.min.js rename to sim/TinyNets/public_html/jquery.min.js diff --git a/sim/manager.js b/sim/TinyNets/public_html/manager.js similarity index 98% rename from sim/manager.js rename to sim/TinyNets/public_html/manager.js index e13093e7089aea01fed69b23ec9eb91c31d1da0e..f8ac0a0147df851eb2efddb8a088a6acc40e99d3 100644 --- a/sim/manager.js +++ b/sim/TinyNets/public_html/manager.js @@ -201,14 +201,14 @@ function Manager(self) { data: packet.data }; if (this.hasSeen(thisFlood)) { // If I have seen it before, don't forward - self.log(`not forwarding ${packet.data} from port ${packet.port}`); +// self.log(`not forwarding ${packet.data} from port ${packet.port}`); return; } this.seenFloods.push(thisFlood); // Remember the packet if (packet.dest === self.id) { // If I am destination const nextPort = this.getMinCostPort(packet.src); // Pick the port to send ACK based off minimizing cost self.log(`got flood ${packet.data} from port ${packet.port}. ACKing ${packet.src} along port ${nextPort}`); - this.sendPacket(ACK, packet.src, 0, self.id, nextPort); + this.sendPacket(ACK, packet.src, undefined, self.id, undefined, undefined, nextPort); } else { packet.hopcount++; // Increment hopcount diff --git a/sim/network.js b/sim/TinyNets/public_html/network.js old mode 100755 new mode 100644 similarity index 95% rename from sim/network.js rename to sim/TinyNets/public_html/network.js index 0a4bc19382ff50042c29b463d18a0b9eec834c67..a8728a0c3642c4400d1cf5c9b34d46022824c01e --- a/sim/network.js +++ b/sim/TinyNets/public_html/network.js @@ -1,534 +1,534 @@ -if (typeof goog == "undefined") { - require('./goog/bootstrap/nodejs') - goog.require("goog.structs.PriorityQueue") -} - -var BitArray = require("./bit-array"); - -var topologySeed = Math.floor(Math.random() * 1000000000); - -function latency(a, b) { - var min = 10 + Math.abs(((a*topologySeed)^(b*topologySeed)) % 300); - var avgVariance = 15; - - return Math.floor((Math.log(1-Math.random())/-1) * (avgVariance)) + min -} - -/* - Events - - This object is used to coordinate events that occur in the simulation. It is a proxy - for a priority queue. -*/ -function Events() { - this.heapBuckets = { - "default":new goog.structs.PriorityQueue(), - "probs":new goog.structs.PriorityQueue() - }; -} - -Events.prototype = { - add: function(time, event, bucket) { - if (typeof bucket == "undefined") - bucket = "default" - - this.heapBuckets[bucket].insert(time, event); - }, - - next: function(maxtime) { - var best = Number.POSITIVE_INFINITY; - var best_bucket = false; - - for (var b in this.heapBuckets) { - var time = this.heapBuckets[b].peekKey(); - - if (typeof time == "undefined") - continue; // bucket is empty - - if (time < best) { - best = time; - best_bucket = b; - } - } - - if (!best_bucket) - return false; - - if (best > maxtime) - return false; - - return {time:best, event:this.heapBuckets[best_bucket].dequeue()}; - } -} - -/* - Interface: - run(network) - runs an event against the Network - delay - msec delay before the event should occur once it is committed to the network - - NodeEvent: runs a function against a node's state - NodeMessageEvent: triggers a handler against a node's state, follows middleware paths - NodeTickEvent: a repetitive function ran against a node's state. - - if the function returns false, we do not run the tick again - - the return of this function can override the delay if it is a number - NodeProbabilisticTickEvent: a pool of events that can occur at any time, like mining -*/ - -function NodeEvent(delay, nid, f, ctx) { - this.delay = delay; - - this.run = function(network) { - if (typeof ctx == "undefined") - ctx = network.nodes[nid] - - f.call(ctx); - } -} - -function NodeMessageEvent(from, nid, name, obj) { - this.delay = latency(from, nid); - - this.run = function(network) { - network.setLinkActivity(from, nid) - - network.nodes[nid].handle(from, name, obj) - } -} - -function NodeTickEvent(delay, f, ctx) { - this.delay = delay; - - this.run = function(network) { - var newDelay; - if ((newDelay = f.call(ctx)) !== false) { - if (typeof newDelay == "number") - this.delay = newDelay; - - network.exec(this) - } - } -} - -/**** -@probability: used to describe probability of event firing every msec -@event: function called -@ctx: function context - -NodeProbabilisticTickEvent.ignore is used to disable an event if it's -never going to occur again, thus avoiding a seek and destroy on the -binary heap. -****/ -function NodeProbabilisticTickEvent(probability, event, nid, ctx) { - // The event will occur in this.delay msec - this.delay = Math.floor(Math.log(1.0-Math.random())/-probability); - this.ignore = false; - - this.run = function(network) { - if (this.ignore) - return false; - - if (typeof ctx == "undefined") - ctx = network.nodes[nid] - - // fire event - event.call(ctx) - } -} - -/* - NodeState - - Has a bunch of helper functions for the node. -*/ - -function NodeState(node, network, id) { - this.id = id; - this.network = network; - this.handlers = []; - - node.setup(this); -} - -NodeState.prototype = { - prob: function(label, p, f, ctx) { - this.network.pregister(label, p, this.id, f, ctx) - }, - - deprob: function(label) { - this.network.depregister(label, this.id) - }, - - setColor: function(color) { - this.network.setColor(this.id, color); - }, - - connect: function(remoteid) { - this.network.connect(this.id, remoteid); - }, - - disconnect: function(remoteid) { - this.network.disconnect(this.id, remoteid); - }, - - log: function(msg) { - var str = "[" + this.now() + "]: " + this.id + ": " + msg; - - this.network.log(str) - }, - - now: function() { - return this.network.now; - }, - - tick: function(delay, f, ctx) { - if (typeof ctx == "undefined") - ctx = this; - - this.network.exec(new NodeTickEvent(delay, f, ctx)) - }, - - send: function(nid, name, obj) { - this.network.exec(new NodeMessageEvent(this.id, nid, name, obj)) - }, - - handle: function(from, name, obj) { - if (typeof this.handlers[name] != "undefined") { - return this.handlers[name](from, obj) - } - }, - - on: function(name, f, ctx) { - if (typeof ctx == "undefined") - ctx = this; - - if (typeof this.handlers[name] != "undefined") { - var oldHandler = this.handlers[name]; - this.handlers[name] = function(from, obj) {if (f.call(ctx, from, obj) !== false) oldHandler.call(ctx, from, obj);} - } else { - this.handlers[name] = function(from, obj) {return f.call(ctx, from, obj);}; - } - }, - - delay: function(delay, f, ctx) { - this.network.exec(new NodeEvent(delay, this.id, f, ctx)) - } -} - -function Client() { - this._use = []; - this._init = false; -} - -Client.prototype = { - setup: function(node) { - // run middleware - for (var i=0;i<this._use.length;i++) { - new this._use[i](node); - } - - // run init functions - if (this._init) - this._init.call(node); - }, - - use: function(f) { - this._use.push(f); - }, - - init: function(callback) { - if (!this._init) - this._init = callback; - else { - var oldInit = this._init; - this._init = function() {oldInit.call(this); callback.call(this)}; - } - }, -} - -////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -function Consensus() { - this.store = {}; // key value store for objects themselves - this.n = 0; -} - -function LocalizedState(consensus) { - this.consensus = consensus; - this.id = consensus.n++; -} - -Consensus.prototype = { - add: function(key, obj) { - if (!(key in this.store)) - this.store[key] = {obj:obj, states:[]}; - }, - obtain: function() { - return new LocalizedState(this); - }, - rand: function() { - /*return String.fromCharCode( - Math.floor(Math.random() * 256), - Math.floor(Math.random() * 256), - Math.floor(Math.random() * 256), - Math.floor(Math.random() * 256), - Math.floor(Math.random() * 256), - Math.floor(Math.random() * 256), - Math.floor(Math.random() * 256), - Math.floor(Math.random() * 256), - Math.floor(Math.random() * 256), - Math.floor(Math.random() * 256), - Math.floor(Math.random() * 256), - Math.floor(Math.random() * 256), - Math.floor(Math.random() * 256), - Math.floor(Math.random() * 256), - Math.floor(Math.random() * 256) - )*/ - return 'xxxxxxxxxxxxxx'.replace(/[xy]/g, function(c) { - var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8); - return v.toString(16); - }); - } -}; - -function ConsensusState() { - this.status = "none"; - - this.equals = function(v) { if ((this.status == "none") && (v.status == "none")) { return true; } return false;} -} - -LocalizedState.prototype = { - // sets k's state to v - set: function(k, v) { - if (!(k in this.consensus.store)) { - this.consensus.add(k, {}) - } - - var states = this.consensus.store[k].states; - var del = false; - states.forEach(function(s) { - if (s.members.get(this.id)) - del = s; - }, this) - - if (del !== false) { - del.members.set(this.id, false); - if (del.members.count() == 0) { - states.splice(states.indexOf(del), 1); - } - } - - var proc = false; - - states.forEach(function(s) { - if (s.state.equals(v)) { - proc = s; - } - }, this) - - if (proc !== false) - proc.members.set(this.id, true); - else { - var n = {state:v, members: new BitArray(1024)}; - n.state.__proto__ = this.consensus.store[k].obj; - n.members.set(this.id, true); - states.push(n) - } - }, - get: function(k) { - if (!(k in this.consensus.store)) { - this.consensus.add(k, {}) - } - - var states = this.consensus.store[k].states; - var get = false; - states.forEach(function(s) { - if (s.members.get(this.id)) { - get = s; - } - }, this) - - if (get !== false) - return get.state; - else { - var gen = new ConsensusState(); - this.set(k, gen); - return gen; - } - }, - find: function(v) { - // TODO: improve efficiency of this by indexing states -> objects - - var results = []; - - for (k in this.consensus.store) { - var state = this.get(k); - - if (state.equals(v)) { - results.push(state.__proto__); - } - } - - return results; - }, - create: function(obj) { - return obj.init(this.consensus); - }, - rand: function() { - return this.consensus.rand(); - } -}; - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -function Network() { - this.events = new Events(); // normal events - this.pevents = {}; // probablistic event buckets - if (typeof VISUALIZER != "undefined") { - this.visualizer = VISUALIZER; - } else { - this.visualizer = false; - } - this.now = 0; - this.maxrun = 0; - - this.nodes = []; - this.nindex = 0; - - this._shared = {}; -} - -Network.prototype = { - Client: Client, - // random data - rand: function(name) { - return Consensus.prototype.rand(); - }, - // grab a shared cache object - shared: function(name) { - if (typeof this._shared[name] == "undefined") { - this._shared[name] = new Consensus(); - } - - return this._shared[name].obtain(); - }, - - log: function(str) { - if (this.visualizer) - this.visualizer.log(str) - else - console.log(str) - }, - - // registers probablistic event - pregister: function(label, p, nid, cb, ctx) { - if (typeof this.pevents[nid + "-" + label] == "undefined") { - this.pevents[nid + "-" + label] = new NodeProbabilisticTickEvent(p, cb, nid, ctx) - this.exec(this.pevents[nid + "-" + label], "probs") - } - }, - - // deregisters a probablistic event - depregister: function(label, nid) { - if (typeof this.pevents[nid + "-" + label] != "undefined") { - this.pevents[nid + "-" + label].ignore = true; - delete this.pevents[nid + "-" + label]; - } - }, - - // sets the color of the node in the visualizer - setColor: function(id, color) { - if (typeof this.nodes[id] != "undefined") - if (this.visualizer) { - this.visualizer.setColor(this.nodes[id]._vid, color); - } - }, - - // could be used to show that network activity occurred between two nodes - setLinkActivity: function(from, to) { - if (typeof this.nodes[to] != "undefined") - if (typeof this.nodes[from] != "undefined") - if (this.visualizer) { - this.visualizer.setLinkActivity("n" + this.nodes[from]._vid + "-n" + this.nodes[to]._vid, this.now); - this.visualizer.setLinkActivity("n" + this.nodes[to]._vid + "-n" + this.nodes[from]._vid, this.now); - } - }, - - // places an event in the queue - exec: function(e, bucket) { - this.events.add(e.delay+this.now, e, bucket) - }, - - // connects two nodes in the visualizer - connect: function (a, b) { - if (this.visualizer) { - this.visualizer.connect(this.nodes[a]._vid, this.nodes[b]._vid, latency(this.nodes[a].id, this.nodes[b].id)); - } - }, - - // disconnects two nodes in the visualizer - disconnect: function (a, b) { - if (this.visualizer) { - this.visualizer.disconnect(this.nodes[a]._vid, this.nodes[b]._vid); - } - }, - - // adds amt nodes using the node constructor parameter - add: function(amt, node) { - for (;amt>0;amt--) { - var state = new NodeState(node, this, this.nindex); - if (this.visualizer) - state._vid = this.visualizer.addNode(); - - this.nodes[this.nindex] = state; - this.nindex++; - } - }, - - // run buffer time (msec) worth of tasks - run: function(msec, next) { - this.maxrun = this.now + msec; - - if (typeof(DELAY_RUN) != "undefined") { - // this is an async call - DELAY_RUN.net = this; - DELAY_RUN.cb = next; - } else { - this._run(msec) - if (next) - next.call(this); - } - }, - - _run: function(msec) { - if (this.now >= this.maxrun) { - if (DELAY_RUN) { - if (DELAY_RUN.cb) { - var cb = DELAY_RUN.cb; - DELAY_RUN.cb = false; - cb.call(this); - } - } - return; - } - - var max = Math.min(this.now + msec, this.maxrun); - - // actually run msec worth of shit - while (e = this.events.next(max)) { - this.now = e.time; - e.event.run(this) - } - - this.now = max; - }, - - check: function(msec, f) { - this.exec(new NodeTickEvent(msec, f, this)) - }, - - stop: function() { - this.maxrun = this.now; - } -} - +if (typeof goog == "undefined") { + require('./goog/bootstrap/nodejs') + goog.require("goog.structs.PriorityQueue") +} + +var BitArray = require("./bit-array"); + +var topologySeed = Math.floor(Math.random() * 1000000000); + +function latency(a, b) { + var min = 10 + Math.abs(((a*topologySeed)^(b*topologySeed)) % 300); + var avgVariance = 15; + + return Math.floor((Math.log(1-Math.random())/-1) * (avgVariance)) + min +} + +/* + Events + + This object is used to coordinate events that occur in the simulation. It is a proxy + for a priority queue. +*/ +function Events() { + this.heapBuckets = { + "default":new goog.structs.PriorityQueue(), + "probs":new goog.structs.PriorityQueue() + }; +} + +Events.prototype = { + add: function(time, event, bucket) { + if (typeof bucket == "undefined") + bucket = "default" + + this.heapBuckets[bucket].insert(time, event); + }, + + next: function(maxtime) { + var best = Number.POSITIVE_INFINITY; + var best_bucket = false; + + for (var b in this.heapBuckets) { + var time = this.heapBuckets[b].peekKey(); + + if (typeof time == "undefined") + continue; // bucket is empty + + if (time < best) { + best = time; + best_bucket = b; + } + } + + if (!best_bucket) + return false; + + if (best > maxtime) + return false; + + return {time:best, event:this.heapBuckets[best_bucket].dequeue()}; + } +} + +/* + Interface: + run(network) - runs an event against the Network + delay - msec delay before the event should occur once it is committed to the network + + NodeEvent: runs a function against a node's state + NodeMessageEvent: triggers a handler against a node's state, follows middleware paths + NodeTickEvent: a repetitive function ran against a node's state. + - if the function returns false, we do not run the tick again + - the return of this function can override the delay if it is a number + NodeProbabilisticTickEvent: a pool of events that can occur at any time, like mining +*/ + +function NodeEvent(delay, nid, f, ctx) { + this.delay = delay; + + this.run = function(network) { + if (typeof ctx == "undefined") + ctx = network.nodes[nid] + + f.call(ctx); + } +} + +function NodeMessageEvent(from, nid, name, obj) { + this.delay = latency(from, nid); + + this.run = function(network) { + network.setLinkActivity(from, nid) + + network.nodes[nid].handle(from, name, obj) + } +} + +function NodeTickEvent(delay, f, ctx) { + this.delay = delay; + + this.run = function(network) { + var newDelay; + if ((newDelay = f.call(ctx)) !== false) { + if (typeof newDelay == "number") + this.delay = newDelay; + + network.exec(this) + } + } +} + +/**** +@probability: used to describe probability of event firing every msec +@event: function called +@ctx: function context + +NodeProbabilisticTickEvent.ignore is used to disable an event if it's +never going to occur again, thus avoiding a seek and destroy on the +binary heap. +****/ +function NodeProbabilisticTickEvent(probability, event, nid, ctx) { + // The event will occur in this.delay msec + this.delay = Math.floor(Math.log(1.0-Math.random())/-probability); + this.ignore = false; + + this.run = function(network) { + if (this.ignore) + return false; + + if (typeof ctx == "undefined") + ctx = network.nodes[nid] + + // fire event + event.call(ctx) + } +} + +/* + NodeState + + Has a bunch of helper functions for the node. +*/ + +function NodeState(node, network, id) { + this.id = id; + this.network = network; + this.handlers = []; + + node.setup(this); +} + +NodeState.prototype = { + prob: function(label, p, f, ctx) { + this.network.pregister(label, p, this.id, f, ctx) + }, + + deprob: function(label) { + this.network.depregister(label, this.id) + }, + + setColor: function(color) { + this.network.setColor(this.id, color); + }, + + connect: function(remoteid) { + this.network.connect(this.id, remoteid); + }, + + disconnect: function(remoteid) { + this.network.disconnect(this.id, remoteid); + }, + + log: function(msg) { + var str = "[" + this.now() + "]: " + this.id + ": " + msg; + + this.network.log(str) + }, + + now: function() { + return this.network.now; + }, + + tick: function(delay, f, ctx) { + if (typeof ctx == "undefined") + ctx = this; + + this.network.exec(new NodeTickEvent(delay, f, ctx)) + }, + + send: function(nid, name, obj) { + this.network.exec(new NodeMessageEvent(this.id, nid, name, obj)) + }, + + handle: function(from, name, obj) { + if (typeof this.handlers[name] != "undefined") { + return this.handlers[name](from, obj) + } + }, + + on: function(name, f, ctx) { + if (typeof ctx == "undefined") + ctx = this; + + if (typeof this.handlers[name] != "undefined") { + var oldHandler = this.handlers[name]; + this.handlers[name] = function(from, obj) {if (f.call(ctx, from, obj) !== false) oldHandler.call(ctx, from, obj);} + } else { + this.handlers[name] = function(from, obj) {return f.call(ctx, from, obj);}; + } + }, + + delay: function(delay, f, ctx) { + this.network.exec(new NodeEvent(delay, this.id, f, ctx)) + } +} + +function Client() { + this._use = []; + this._init = false; +} + +Client.prototype = { + setup: function(node) { + // run middleware + for (var i=0;i<this._use.length;i++) { + new this._use[i](node); + } + + // run init functions + if (this._init) + this._init.call(node); + }, + + use: function(f) { + this._use.push(f); + }, + + init: function(callback) { + if (!this._init) + this._init = callback; + else { + var oldInit = this._init; + this._init = function() {oldInit.call(this); callback.call(this)}; + } + }, +} + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +function Consensus() { + this.store = {}; // key value store for objects themselves + this.n = 0; +} + +function LocalizedState(consensus) { + this.consensus = consensus; + this.id = consensus.n++; +} + +Consensus.prototype = { + add: function(key, obj) { + if (!(key in this.store)) + this.store[key] = {obj:obj, states:[]}; + }, + obtain: function() { + return new LocalizedState(this); + }, + rand: function() { + /*return String.fromCharCode( + Math.floor(Math.random() * 256), + Math.floor(Math.random() * 256), + Math.floor(Math.random() * 256), + Math.floor(Math.random() * 256), + Math.floor(Math.random() * 256), + Math.floor(Math.random() * 256), + Math.floor(Math.random() * 256), + Math.floor(Math.random() * 256), + Math.floor(Math.random() * 256), + Math.floor(Math.random() * 256), + Math.floor(Math.random() * 256), + Math.floor(Math.random() * 256), + Math.floor(Math.random() * 256), + Math.floor(Math.random() * 256), + Math.floor(Math.random() * 256) + )*/ + return 'xxxxxxxxxxxxxx'.replace(/[xy]/g, function(c) { + var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8); + return v.toString(16); + }); + } +}; + +function ConsensusState() { + this.status = "none"; + + this.equals = function(v) { if ((this.status == "none") && (v.status == "none")) { return true; } return false;} +} + +LocalizedState.prototype = { + // sets k's state to v + set: function(k, v) { + if (!(k in this.consensus.store)) { + this.consensus.add(k, {}) + } + + var states = this.consensus.store[k].states; + var del = false; + states.forEach(function(s) { + if (s.members.get(this.id)) + del = s; + }, this) + + if (del !== false) { + del.members.set(this.id, false); + if (del.members.count() == 0) { + states.splice(states.indexOf(del), 1); + } + } + + var proc = false; + + states.forEach(function(s) { + if (s.state.equals(v)) { + proc = s; + } + }, this) + + if (proc !== false) + proc.members.set(this.id, true); + else { + var n = {state:v, members: new BitArray(1024)}; + n.state.__proto__ = this.consensus.store[k].obj; + n.members.set(this.id, true); + states.push(n) + } + }, + get: function(k) { + if (!(k in this.consensus.store)) { + this.consensus.add(k, {}) + } + + var states = this.consensus.store[k].states; + var get = false; + states.forEach(function(s) { + if (s.members.get(this.id)) { + get = s; + } + }, this) + + if (get !== false) + return get.state; + else { + var gen = new ConsensusState(); + this.set(k, gen); + return gen; + } + }, + find: function(v) { + // TODO: improve efficiency of this by indexing states -> objects + + var results = []; + + for (k in this.consensus.store) { + var state = this.get(k); + + if (state.equals(v)) { + results.push(state.__proto__); + } + } + + return results; + }, + create: function(obj) { + return obj.init(this.consensus); + }, + rand: function() { + return this.consensus.rand(); + } +}; + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +function Network() { + this.events = new Events(); // normal events + this.pevents = {}; // probablistic event buckets + if (typeof VISUALIZER != "undefined") { + this.visualizer = VISUALIZER; + } else { + this.visualizer = false; + } + this.now = 0; + this.maxrun = 0; + + this.nodes = []; + this.nindex = 0; + + this._shared = {}; +} + +Network.prototype = { + Client: Client, + // random data + rand: function(name) { + return Consensus.prototype.rand(); + }, + // grab a shared cache object + shared: function(name) { + if (typeof this._shared[name] == "undefined") { + this._shared[name] = new Consensus(); + } + + return this._shared[name].obtain(); + }, + + log: function(str) { + if (this.visualizer) + this.visualizer.log(str) + else + console.log(str) + }, + + // registers probablistic event + pregister: function(label, p, nid, cb, ctx) { + if (typeof this.pevents[nid + "-" + label] == "undefined") { + this.pevents[nid + "-" + label] = new NodeProbabilisticTickEvent(p, cb, nid, ctx) + this.exec(this.pevents[nid + "-" + label], "probs") + } + }, + + // deregisters a probablistic event + depregister: function(label, nid) { + if (typeof this.pevents[nid + "-" + label] != "undefined") { + this.pevents[nid + "-" + label].ignore = true; + delete this.pevents[nid + "-" + label]; + } + }, + + // sets the color of the node in the visualizer + setColor: function(id, color) { + if (typeof this.nodes[id] != "undefined") + if (this.visualizer) { + this.visualizer.setColor(this.nodes[id]._vid, color); + } + }, + + // could be used to show that network activity occurred between two nodes + setLinkActivity: function(from, to) { + if (typeof this.nodes[to] != "undefined") + if (typeof this.nodes[from] != "undefined") + if (this.visualizer) { + this.visualizer.setLinkActivity("n" + this.nodes[from]._vid + "-n" + this.nodes[to]._vid, this.now); + this.visualizer.setLinkActivity("n" + this.nodes[to]._vid + "-n" + this.nodes[from]._vid, this.now); + } + }, + + // places an event in the queue + exec: function(e, bucket) { + this.events.add(e.delay+this.now, e, bucket) + }, + + // connects two nodes in the visualizer + connect: function (a, b) { + if (this.visualizer) { + this.visualizer.connect(this.nodes[a]._vid, this.nodes[b]._vid, latency(this.nodes[a].id, this.nodes[b].id)); + } + }, + + // disconnects two nodes in the visualizer + disconnect: function (a, b) { + if (this.visualizer) { + this.visualizer.disconnect(this.nodes[a]._vid, this.nodes[b]._vid); + } + }, + + // adds amt nodes using the node constructor parameter + add: function(amt, node) { + for (;amt>0;amt--) { + var state = new NodeState(node, this, this.nindex); + if (this.visualizer) + state._vid = this.visualizer.addNode(); + + this.nodes[this.nindex] = state; + this.nindex++; + } + }, + + // run buffer time (msec) worth of tasks + run: function(msec, next) { + this.maxrun = this.now + msec; + + if (typeof(DELAY_RUN) != "undefined") { + // this is an async call + DELAY_RUN.net = this; + DELAY_RUN.cb = next; + } else { + this._run(msec) + if (next) + next.call(this); + } + }, + + _run: function(msec) { + if (this.now >= this.maxrun) { + if (DELAY_RUN) { + if (DELAY_RUN.cb) { + var cb = DELAY_RUN.cb; + DELAY_RUN.cb = false; + cb.call(this); + } + } + return; + } + + var max = Math.min(this.now + msec, this.maxrun); + + // actually run msec worth of shit + while (e = this.events.next(max)) { + this.now = e.time; + e.event.run(this) + } + + this.now = max; + }, + + check: function(msec, f) { + this.exec(new NodeTickEvent(msec, f, this)) + }, + + stop: function() { + this.maxrun = this.now; + } +} + module.exports = new Network(); \ No newline at end of file diff --git a/sim/peermgr.js b/sim/TinyNets/public_html/peermgr.js old mode 100755 new mode 100644 similarity index 96% rename from sim/peermgr.js rename to sim/TinyNets/public_html/peermgr.js index 76219223b2ef4e82bd9adde93932fdd3c089a9a7..6902aec24c833e888a4183417d106fcf9d795e5b --- a/sim/peermgr.js +++ b/sim/TinyNets/public_html/peermgr.js @@ -1,313 +1,313 @@ -/* -The PeerMgr is the inter-client behavior middleware for establishing connections -with other peers, thus forming a network. - -(This peer manager is not intended to accurately mimic bitcoin yet.) - -Current behavior: every 1 second it checks to see if it has 'maxpeers'. If not, it attempts -either to connect to an archived addr, or get addresses from other nodes it has contacted. -Initially, the only archived addr is the bootstrap node, 0. Once the node has received -maxpeers, it stops ticking. -*/ - -function PeerState(id, lastmessage) { - this.id = id; - this.lastmessage = lastmessage; - this.active = false; - this.locked = true; - this.msgqueue = []; -} - -function PeerMgr(self) { - // attach to nodestate 'peers' property - // self.peers = this; - self.peermgr = this; - - this.freeze = false; - this.ticking = false; - this.peers = {}; // current established or attempted connections - this.numpeers = 0; // the number of peers we have - this.maxpeers = 8; // the max number of peers we can have - if (self.id != 0) - this.nodearchive = [new PeerState(0, self.now())]; // a node archived, initialized with a bootstrap node - else - this.nodearchive = [] - - var peerTick = function() { - if (!this.freeze && (this.numpeers < this.maxpeers)) { - // we need more peers - - // let's try connecting to a peer in our nodearchive, if there are any - if (this.nodearchive.length) { - var p = this.nodearchive.shift(); - this.nodearchive.push(p); - - // try connecting to this node, fails if we're already trying/connected - this.connect(p); - } - - // if we have (active) connections, ask them for new peers - if (this.numpeers) { - var randomPeer = this.peers[Object.keys(this.peers)[Math.floor(Math.random() * Object.keys(this.peers).length)]] - - if (randomPeer.active) { - this.getpeers(randomPeer.id) - } - } - - if (self.now() > 1000 * 1000) // after 1000 seconds, let's tick every 5 seconds instead - return 5000; - } else { - self.peermgr.ticking = false; - return false; // no more ticking necessary - } - } - - var startTicking = function() { - if (!self.peermgr.ticking) { - self.peermgr.ticking = true; - - self.tick(1000, peerTick, self.peermgr); - } - } - - this.numActive = function() { - var n = 0; - for (var p in this.peers) { - if (this.peers[p].active) { - n++; - } - } - return n; - } - - this.send = function(to, name, msg) { - if (this.peers[to].active) { - if (this.peers[to].locked) { - this.peers[to].msgqueue.push({name:name,obj:msg}) - } else { - self.send(this.peers[to].id, "__peermsg", {name:name,obj:msg}) - } - } - } - - this.each = function(cb) { - for (var p in this.peers) { - if (this.peers[p].active) - cb.call(self, p) - } - } - - // sends a message to all active peers - this.broadcast = function(name, msg) { - for (var p in this.peers) { - this.send(p, name, msg) - } - } - - // request peers from a remote node - this.getpeers = function(p) { - self.send(p, 'getpeers', {}); - } - - // send a portion of our archived node list - this.sendpeers = function(p) { - var someNodes = this.nodearchive.slice(0, 15) - if (someNodes.length == 0) { - var randomPeer = this.peers[Object.keys(this.peers)[Math.floor(Math.random() * Object.keys(this.peers).length)]] - if (randomPeer.active) - someNodes = [randomPeer] - } - - self.send(p, 'peerlist', someNodes); - } - - // connect to a remote node (if we haven't already tried) - this.connect = function(p) { - if (self.id == p.id) - return; // can't connect to ourselves! - - if (typeof this.peers[p.id] == "undefined") { - this.peers[p.id] = p; - this.numpeers += 1; - self.send(p.id, 'connect', {}) - } - } - - // disconnect from a remote node - this.disconnect = function(p) { - if (typeof this.peers[p] != "undefined") { - var peer = this.peers[p]; - delete this.peers[p]; - - //if (peer.active) { - self.send(p, 'disconnect', {}) - self.disconnect(p); - startTicking(); - //} - - this.nodearchive.push(peer); - this.numpeers -= 1; - self.handle(p, "peermgr:disconnect", p) - } - } - - // accept a remote node's connection - this.accept = function(p) { - p.locked = true; // wait for an ack - self.send(p.id, 'accept', {}) - } - - // reject a remote node's connection - this.reject = function(p) { - var someNodes = this.nodearchive.slice(0, 15) - if (someNodes.length == 0) { - var randomPeer = this.peers[Object.keys(this.peers)[Math.floor(Math.random() * Object.keys(this.peers).length)]] - if (randomPeer.active) - someNodes = [this.randomPeer] - } - - self.send(p.id, 'reject', someNodes) - } - - this.onAck = function(from, o) { - if (typeof this.peers[from] != "undefined") { - this.peers[from].locked = false; - this.peers[from].lastmessage = self.now() - - if (this.peers[from].msgqueue.length > 0) { - var domsg = this.peers[from].msgqueue.shift(); - this.peers[from].locked = true; - self.send(this.peers[from].id, "__peermsg", {name:domsg.name,obj:domsg.obj}) - } - } - } - - // processes a received message from another peer - this.onReceive = function(from, o) { - if (typeof this.peers[from] != "undefined" && this.peers[from].active) { - self.send(from, 'ack', {}) - self.handle(from, o.name, o.obj) - this.peers[from].lastmessage = self.now(); - } - } - - // receive a peerlist message - this.onPeerlist = function(from, obj) { - // are we connected to this peer? - if (this.peers[from] != "undefined") { - // add these peers to our nodearchive - // if we don't have them already - - for (var i=0;i<obj.length;i++) { - var candidate = obj[i]; - - // remove redundant existing nodearchive objects - for (var k=0;k<this.nodearchive.length;k++) { - if (this.nodearchive[k].id == candidate.id) { - this.nodearchive.splice(k, 1); - } - } - - this.nodearchive.push(new PeerState(candidate.id, self.now())) - } - } - } - - // receive a getpeers message - this.onGetpeers = function(from, obj) { - // are we connected to this peer? - if (this.peers[from] != "undefined") { - this.sendpeers(from) - } - } - - // receive a reject message - this.onReject = function(from, obj) { - if (typeof this.peers[from] != "undefined") { - this.onPeerlist(from, obj) // process rejection peerlist - this.disconnect(from) // cya - } - } - - // receive an accept message - this.onAccept = function(from, obj) { - if (typeof this.peers[from] != "undefined") { - // remove them from our nodearchive - for (var i=0;i<this.nodearchive.length;i++) { - if (this.nodearchive[i].id == from) { - this.nodearchive.splice(i, 1) - } - } - - // set connection active - this.peers[from].lastmessage = self.now(); - this.peers[from].active = true; - - self.send(from, 'ack', {}) - - // notify Network of connection - self.connect(from); - - self.handle(from, "peermgr:connect", from) - } - } - - // receive a disconnect message - this.onDisconnect = function(from, obj) { - this.disconnect(from) - } - - this.onConnect = function(from, obj) { - // are you already trying to connect? - if (typeof this.peers[from] != "undefined") - return; // do nothing - - if (this.numpeers < this.maxpeers) { - // sure, we'll accept you. we need a peer. - - // are we already connected to you? - this.peers[from] = new PeerState(from, self.now()) - this.numpeers += 1; - this.onAccept(from, obj); - this.accept(this.peers[from]) - - return; - } - - // otherwise, reject this node. - - // remove node from nodearchive in any redundant locations - for (var i=0;i<this.nodearchive.length;i++) { - if (this.nodearchive[i].id == from) { - this.nodearchive.splice(i, 1) - } - } - - var rejectPeer = new PeerState(from, self.now()); - - // add back to nodearchive in the front - this.nodearchive.push(rejectPeer) - - // send node a rejection message - this.reject(rejectPeer); - } - - // - // attach to the node - // - - // tick that runs every 1 second - //startTicking(); - - self.on("connect", this.onConnect, this); - self.on("accept", this.onAccept, this); - self.on("reject", this.onReject, this); - self.on("disconnect", this.onDisconnect, this); - self.on("peerlist", this.onPeerlist, this); - self.on("getpeers", this.onGetpeers, this); - self.on("ack", this.onAck, this); - self.on("__peermsg", this.onReceive, this); -} - +/* +The PeerMgr is the inter-client behavior middleware for establishing connections +with other peers, thus forming a network. + +(This peer manager is not intended to accurately mimic bitcoin yet.) + +Current behavior: every 1 second it checks to see if it has 'maxpeers'. If not, it attempts +either to connect to an archived addr, or get addresses from other nodes it has contacted. +Initially, the only archived addr is the bootstrap node, 0. Once the node has received +maxpeers, it stops ticking. +*/ + +function PeerState(id, lastmessage) { + this.id = id; + this.lastmessage = lastmessage; + this.active = false; + this.locked = true; + this.msgqueue = []; +} + +function PeerMgr(self) { + // attach to nodestate 'peers' property + // self.peers = this; + self.peermgr = this; + + this.freeze = false; + this.ticking = false; + this.peers = {}; // current established or attempted connections + this.numpeers = 0; // the number of peers we have + this.maxpeers = 8; // the max number of peers we can have + if (self.id != 0) + this.nodearchive = [new PeerState(0, self.now())]; // a node archived, initialized with a bootstrap node + else + this.nodearchive = [] + + var peerTick = function() { + if (!this.freeze && (this.numpeers < this.maxpeers)) { + // we need more peers + + // let's try connecting to a peer in our nodearchive, if there are any + if (this.nodearchive.length) { + var p = this.nodearchive.shift(); + this.nodearchive.push(p); + + // try connecting to this node, fails if we're already trying/connected + this.connect(p); + } + + // if we have (active) connections, ask them for new peers + if (this.numpeers) { + var randomPeer = this.peers[Object.keys(this.peers)[Math.floor(Math.random() * Object.keys(this.peers).length)]] + + if (randomPeer.active) { + this.getpeers(randomPeer.id) + } + } + + if (self.now() > 1000 * 1000) // after 1000 seconds, let's tick every 5 seconds instead + return 5000; + } else { + self.peermgr.ticking = false; + return false; // no more ticking necessary + } + } + + var startTicking = function() { + if (!self.peermgr.ticking) { + self.peermgr.ticking = true; + + self.tick(1000, peerTick, self.peermgr); + } + } + + this.numActive = function() { + var n = 0; + for (var p in this.peers) { + if (this.peers[p].active) { + n++; + } + } + return n; + } + + this.send = function(to, name, msg) { + if (this.peers[to].active) { + if (this.peers[to].locked) { + this.peers[to].msgqueue.push({name:name,obj:msg}) + } else { + self.send(this.peers[to].id, "__peermsg", {name:name,obj:msg}) + } + } + } + + this.each = function(cb) { + for (var p in this.peers) { + if (this.peers[p].active) + cb.call(self, p) + } + } + + // sends a message to all active peers + this.broadcast = function(name, msg) { + for (var p in this.peers) { + this.send(p, name, msg) + } + } + + // request peers from a remote node + this.getpeers = function(p) { + self.send(p, 'getpeers', {}); + } + + // send a portion of our archived node list + this.sendpeers = function(p) { + var someNodes = this.nodearchive.slice(0, 15) + if (someNodes.length == 0) { + var randomPeer = this.peers[Object.keys(this.peers)[Math.floor(Math.random() * Object.keys(this.peers).length)]] + if (randomPeer.active) + someNodes = [randomPeer] + } + + self.send(p, 'peerlist', someNodes); + } + + // connect to a remote node (if we haven't already tried) + this.connect = function(p) { + if (self.id == p.id) + return; // can't connect to ourselves! + + if (typeof this.peers[p.id] == "undefined") { + this.peers[p.id] = p; + this.numpeers += 1; + self.send(p.id, 'connect', {}) + } + } + + // disconnect from a remote node + this.disconnect = function(p) { + if (typeof this.peers[p] != "undefined") { + var peer = this.peers[p]; + delete this.peers[p]; + + //if (peer.active) { + self.send(p, 'disconnect', {}) + self.disconnect(p); + startTicking(); + //} + + this.nodearchive.push(peer); + this.numpeers -= 1; + self.handle(p, "peermgr:disconnect", p) + } + } + + // accept a remote node's connection + this.accept = function(p) { + p.locked = true; // wait for an ack + self.send(p.id, 'accept', {}) + } + + // reject a remote node's connection + this.reject = function(p) { + var someNodes = this.nodearchive.slice(0, 15) + if (someNodes.length == 0) { + var randomPeer = this.peers[Object.keys(this.peers)[Math.floor(Math.random() * Object.keys(this.peers).length)]] + if (randomPeer.active) + someNodes = [this.randomPeer] + } + + self.send(p.id, 'reject', someNodes) + } + + this.onAck = function(from, o) { + if (typeof this.peers[from] != "undefined") { + this.peers[from].locked = false; + this.peers[from].lastmessage = self.now() + + if (this.peers[from].msgqueue.length > 0) { + var domsg = this.peers[from].msgqueue.shift(); + this.peers[from].locked = true; + self.send(this.peers[from].id, "__peermsg", {name:domsg.name,obj:domsg.obj}) + } + } + } + + // processes a received message from another peer + this.onReceive = function(from, o) { + if (typeof this.peers[from] != "undefined" && this.peers[from].active) { + self.send(from, 'ack', {}) + self.handle(from, o.name, o.obj) + this.peers[from].lastmessage = self.now(); + } + } + + // receive a peerlist message + this.onPeerlist = function(from, obj) { + // are we connected to this peer? + if (this.peers[from] != "undefined") { + // add these peers to our nodearchive + // if we don't have them already + + for (var i=0;i<obj.length;i++) { + var candidate = obj[i]; + + // remove redundant existing nodearchive objects + for (var k=0;k<this.nodearchive.length;k++) { + if (this.nodearchive[k].id == candidate.id) { + this.nodearchive.splice(k, 1); + } + } + + this.nodearchive.push(new PeerState(candidate.id, self.now())) + } + } + } + + // receive a getpeers message + this.onGetpeers = function(from, obj) { + // are we connected to this peer? + if (this.peers[from] != "undefined") { + this.sendpeers(from) + } + } + + // receive a reject message + this.onReject = function(from, obj) { + if (typeof this.peers[from] != "undefined") { + this.onPeerlist(from, obj) // process rejection peerlist + this.disconnect(from) // cya + } + } + + // receive an accept message + this.onAccept = function(from, obj) { + if (typeof this.peers[from] != "undefined") { + // remove them from our nodearchive + for (var i=0;i<this.nodearchive.length;i++) { + if (this.nodearchive[i].id == from) { + this.nodearchive.splice(i, 1) + } + } + + // set connection active + this.peers[from].lastmessage = self.now(); + this.peers[from].active = true; + + self.send(from, 'ack', {}) + + // notify Network of connection + self.connect(from); + + self.handle(from, "peermgr:connect", from) + } + } + + // receive a disconnect message + this.onDisconnect = function(from, obj) { + this.disconnect(from) + } + + this.onConnect = function(from, obj) { + // are you already trying to connect? + if (typeof this.peers[from] != "undefined") + return; // do nothing + + if (this.numpeers < this.maxpeers) { + // sure, we'll accept you. we need a peer. + + // are we already connected to you? + this.peers[from] = new PeerState(from, self.now()) + this.numpeers += 1; + this.onAccept(from, obj); + this.accept(this.peers[from]) + + return; + } + + // otherwise, reject this node. + + // remove node from nodearchive in any redundant locations + for (var i=0;i<this.nodearchive.length;i++) { + if (this.nodearchive[i].id == from) { + this.nodearchive.splice(i, 1) + } + } + + var rejectPeer = new PeerState(from, self.now()); + + // add back to nodearchive in the front + this.nodearchive.push(rejectPeer) + + // send node a rejection message + this.reject(rejectPeer); + } + + // + // attach to the node + // + + // tick that runs every 1 second + //startTicking(); + + self.on("connect", this.onConnect, this); + self.on("accept", this.onAccept, this); + self.on("reject", this.onReject, this); + self.on("disconnect", this.onDisconnect, this); + self.on("peerlist", this.onPeerlist, this); + self.on("getpeers", this.onGetpeers, this); + self.on("ack", this.onAck, this); + self.on("__peermsg", this.onReceive, this); +} + module.exports = PeerMgr; \ No newline at end of file diff --git a/sim/require.js b/sim/TinyNets/public_html/require.js old mode 100755 new mode 100644 similarity index 98% rename from sim/require.js rename to sim/TinyNets/public_html/require.js index 9d070199ffd97e77cfe0b5d552f7a8fff48fb615..ed4eb1a468b68556b65f5e5f0a7b328bf99971d8 --- a/sim/require.js +++ b/sim/TinyNets/public_html/require.js @@ -1,35 +1,35 @@ -// found on stackoverflow somewhere -// run chrome with --allow-file-access-from-files so this works in file:// -function require(url){ - if (url.toLowerCase().substr(-3)!=='.js') url+='.js'; // to allow loading without js suffix; - if (!require.cache) require.cache=[]; //init cache - var exports=require.cache[url]; //get from cache - if (!exports) { //not cached - try { - exports={}; - var X=new XMLHttpRequest(); - X.open("GET", url, 0); // sync - X.send(); - if (X.status && X.status !== 200) throw new Error(X.statusText); - var source = X.responseText; - // fix (if saved form for Chrome Dev Tools) - if (source.substr(0,10)==="(function("){ - var moduleStart = source.indexOf('{'); - var moduleEnd = source.lastIndexOf('})'); - var CDTcomment = source.indexOf('//@ '); - if (CDTcomment>-1 && CDTcomment<moduleStart+6) moduleStart = source.indexOf('\n',CDTcomment); - source = source.slice(moduleStart+1,moduleEnd-1); - } - // fix, add comment to show source on Chrome Dev Tools - source="//@ sourceURL="+window.location.origin+url+"\n" + source; - //------ - var module = { id: url, uri: url, exports:exports }; //according to node.js modules - var anonFn = new Function("require", "exports", "module", source); //create a Fn with module code, and 3 params: require, exports & module - anonFn(require, exports, module); // call the Fn, Execute the module - require.cache[url] = exports = module.exports; //cache obj exported by module - } catch (err) { - throw new Error("Error loading module "+url+": "+err); - } - } - return exports; //require returns object exported by module +// found on stackoverflow somewhere +// run chrome with --allow-file-access-from-files so this works in file:// +function require(url){ + if (url.toLowerCase().substr(-3)!=='.js') url+='.js'; // to allow loading without js suffix; + if (!require.cache) require.cache=[]; //init cache + var exports=require.cache[url]; //get from cache + if (!exports) { //not cached + try { + exports={}; + var X=new XMLHttpRequest(); + X.open("GET", url, 0); // sync + X.send(); + if (X.status && X.status !== 200) throw new Error(X.statusText); + var source = X.responseText; + // fix (if saved form for Chrome Dev Tools) + if (source.substr(0,10)==="(function("){ + var moduleStart = source.indexOf('{'); + var moduleEnd = source.lastIndexOf('})'); + var CDTcomment = source.indexOf('//@ '); + if (CDTcomment>-1 && CDTcomment<moduleStart+6) moduleStart = source.indexOf('\n',CDTcomment); + source = source.slice(moduleStart+1,moduleEnd-1); + } + // fix, add comment to show source on Chrome Dev Tools + source="//@ sourceURL="+window.location.origin+url+"\n" + source; + //------ + var module = { id: url, uri: url, exports:exports }; //according to node.js modules + var anonFn = new Function("require", "exports", "module", source); //create a Fn with module code, and 3 params: require, exports & module + anonFn(require, exports, module); // call the Fn, Execute the module + require.cache[url] = exports = module.exports; //cache obj exported by module + } catch (err) { + throw new Error("Error loading module "+url+": "+err); + } + } + return exports; //require returns object exported by module } \ No newline at end of file diff --git a/sim/sim-example-alert.js b/sim/TinyNets/public_html/sim-example-alert.js old mode 100755 new mode 100644 similarity index 95% rename from sim/sim-example-alert.js rename to sim/TinyNets/public_html/sim-example-alert.js index 110f385d45d5c826ec45b66b427dbd61217f23f0..e1f4ffb5eb4a42a5eef0e3f79f1574e52cc65c9a --- a/sim/sim-example-alert.js +++ b/sim/TinyNets/public_html/sim-example-alert.js @@ -1,55 +1,55 @@ -/* - Example: Alert Message - - An alert message needs to be broadcasted to all nodes as fast as possible! - - Let's calculate how long it takes the message to be received by all nodes. -*/ - - -var net = require("./network"), - peermgr = require("./peermgr"), - client = new net.Node() - -client.use(peermgr) - -client.init(function() { - this.alertflag = false; - - if (this.id == 0) { - this.delay(20000, function() { - this.log("dispatching alert") - // give the network about 20 seconds so everybody is connected - - this.alertflag = true; - this.setColor("red") - - this.peermgr.broadcast("alert") - }) - } - - this.on("alert", function() { - if (this.alertflag) - return; - - this.alertflag = true; - this.setColor("red") - this.peermgr.broadcast("alert") - }) -}) - -net.add(100, client) -net.check(20, function() { - var aware = 0; - net.nodes.forEach(function(n) { - if (n.alertflag) { - aware++; - } - }) - - if (aware == net.nodes.length) { - net.visualizer.log(net.now + ": ALL NODES HAVE RECEIVED ALERT MESSAGE"); - net.stop(); - } -}) +/* + Example: Alert Message + + An alert message needs to be broadcasted to all nodes as fast as possible! + + Let's calculate how long it takes the message to be received by all nodes. +*/ + + +var net = require("./network"), + peermgr = require("./peermgr"), + client = new net.Node() + +client.use(peermgr) + +client.init(function() { + this.alertflag = false; + + if (this.id == 0) { + this.delay(20000, function() { + this.log("dispatching alert") + // give the network about 20 seconds so everybody is connected + + this.alertflag = true; + this.setColor("red") + + this.peermgr.broadcast("alert") + }) + } + + this.on("alert", function() { + if (this.alertflag) + return; + + this.alertflag = true; + this.setColor("red") + this.peermgr.broadcast("alert") + }) +}) + +net.add(100, client) +net.check(20, function() { + var aware = 0; + net.nodes.forEach(function(n) { + if (n.alertflag) { + aware++; + } + }) + + if (aware == net.nodes.length) { + net.visualizer.log(net.now + ": ALL NODES HAVE RECEIVED ALERT MESSAGE"); + net.stop(); + } +}) net.run(Infinity) \ No newline at end of file diff --git a/sim/sim-selfish.js b/sim/TinyNets/public_html/sim-selfish.js similarity index 100% rename from sim/sim-selfish.js rename to sim/TinyNets/public_html/sim-selfish.js diff --git a/sim/sim.js b/sim/TinyNets/public_html/sim.js old mode 100755 new mode 100644 similarity index 50% rename from sim/sim.js rename to sim/TinyNets/public_html/sim.js index 88dd984ce052b2a097c5960422bd21b3f603535b..5ca8b09e834b8a683afa3876c685315adc287431 --- a/sim/sim.js +++ b/sim/TinyNets/public_html/sim.js @@ -1,147 +1,241 @@ -var net = require("./network"), - manager = require('./manager'); - -const startupDelay = 100; -const connectDelay = 100; -const bufferCheckDelay = 1000; -const heartbeatPeriod = 400; - -// INITIALIZE NETWORK TOPOLOGY HERE -var initTopology = [ - [1,2], // 0 - [0,3], // 1 - [0,4,5], // 2 - [1,4], // 3 - [3,2], // 4 - [2] // 5 -]; - -// Don't touch this code - -var clients = []; -for (let i = 0; i < initTopology.length; i++) { - c = new net.Client(); - c.use(manager); - clients.push(c); -} - -for (let i = 0; i < initTopology.length; i++) { - clients[i].init(function() { - this.delay(startupDelay, function() { - this.manager.setup(initTopology[i].length); - }); - this.delay(500, function() { - this.manager.printPorts(); - }); - this.tick(bufferCheckDelay, function() { - this.manager.checkBuffer(); - }); - this.tick(heartbeatPeriod, function() { - this.manager.heartbeat(); - }); - this.tick(2*heartbeatPeriod, function() { - this.manager.takePulse(); - }); - }); - for (let j = 0; j < initTopology[i].length; j++) { - if (initTopology[i][j] == -1) { - continue; - } - - clients[i].init(function() { - this.delay(startupDelay+connectDelay, function() { - this.manager.connect(j, initTopology[i][j]); - }); - }); - } -} - -//----------------------------------------------------------------------------// -// PUT CUSTOM CODE HERE: -sendPacket(0,4,1,"Hello Four!",1000); - -// To test link failure, uncomment one. To test node failure, uncomment both. -//disconnect(0,1,2,0,6000); -//disconnect(2,1,4,1,6000); - -// To test how network reacts to heavy traffic at a desired node, uncomment. -//sendPacket(5,2,1,"Distraction 0!",6000); -//sendPacket(5,2,1,"Distraction 1!",6000); -//sendPacket(5,2,1,"Distraction 2!",6000); -//sendPacket(5,2,1,"Distraction 3!",6000); -//sendPacket(5,2,1,"Distraction 4!",6000); -//sendPacket(5,2,1,"Distraction 5!",6000); -//sendPacket(5,2,1,"Distraction 6!",6000); -//sendPacket(5,2,1,"Distraction 7!",6000); -//sendPacket(5,2,1,"Distraction 8!",6000); - -sendPacket(4,0,1,"I love you, One!",8000); - - -//Don't add stuff below this: -//----------------------------------------------------------------------------// - -for (let i = 0; i < initTopology.length; i++) { - net.add(1, clients[i]); -} -net.run(100 * 1000); // runs for 100 seconds - - - -function send(from, port, message, delay, periodic=false) { - if (periodic) { - clients[from].init(function() { - this.tick(delay, function() { - this.manager.send(port, message); - }); - }); - } else { - clients[from].init(function() { - this.delay(delay, function() { - this.manager.send(port, message); - }); - }); - } -} - -function sendPacket(from, dest, size, data, delay, periodic=false) { - if (periodic) { - clients[from].init(function() { - this.tick(delay, function() { - this.manager.sendPacket(252, dest, -1, undefined, size, data, -1); - }); - }); - } else { - clients[from].init(function() { - this.delay(delay, function() { - this.manager.sendPacket(252, dest, -1, undefined, size, data, -1); - }); - }); - } -} - -function connect(a, aPort, b, bPort, delay) { - clients[a].init(function() { - this.delay(delay, function() { - this.manager.connect(aPort, b); - }); - }); - clients[b].init(function() { - this.delay(delay, function() { - this.manager.connect(bPort, a); - }); - }); -} - -function disconnect(a, aPort, b, bPort, delay) { - clients[a].init(function() { - this.delay(delay, function() { - this.manager.disconnect(aPort); - }); - }); - clients[b].init(function() { - this.delay(delay, function() { - this.manager.disconnect(bPort); - }); - }); +var net = require("./network"), + manager = require('./manager'); + +const startupDelay = 100; +const connectDelay = 100; +const bufferCheckDelay = 1000; +const heartbeatPeriod = 400; + +// INITIALIZE NETWORK TOPOLOGY HERE +var initTopology = [ + [1,2,3,5,6,9], // 0 + [0,3,5,6,9,10], // 1 + [0,3,6,8,9,10,11], // 2 + [0,1,2,4,10], // 3 + [3,5,12], // 4 + [0,1,4,6,8,9,12,13,14], // 5 + [0,1,2,5,11], // 6 + [9,10,13,14], // 7 + [2,5,13,17], // 8 + [0,1,2,5,7,12,15,16], // 9 + [1,2,3,7,12,15,17,19], // 10 + [2,6,14,19], // 11 + [4,5,9,10,15,20,21], // 12 + [5,7,8,15,16,18,19], // 13 + [5,7,11,19,20,22,23], // 14 + [9,10,12,13,19,22], // 15 + [9,13,17,18,21,24,25], // 16 + [8,10,16,18,19,20,21,22,25], // 17 + [13,16,17,20,21,24,25], // 18 + [10,11,13,14,15,17,20,21,22,24,27,28], // 19 + [12,14,17,18,19,21,24,27,28,29], // 20 + [12,16,17,18,19,20,24,25,29,30], // 21 + [14,15,17,19,24,25], // 22 + [14,29,31], // 23 + [16,18,19,20,21,22,26,28,29,30,32], // 24 + [16,17,18,21,22,26,27,28], // 25 + [24,25,27,30,35], // 26 + [19,20,25,26,29,30,31,32,35,36], // 27 + [19,20,24,25,29,30,37], // 28 + [20,21,23,24,27,28,30,32,33,37], // 29 + [21,24,26,27,28,29,36,37], // 30 + [23,27,33,34,36,38], // 31 + [24,27,29,36,38], // 32 + [29,31,34,36,37,38,40,42], // 33 + [31,33,38,40,42], // 34 + [26,27,39,40,41,43,44], // 35 + [27,30,31,32,33,38,39,41,44], // 36 + [28,29,30,33,39,40,41], // 37 + [31,32,33,34,36,40,42,47], // 38 + [35,36,37,41,42,43,45,46,48], // 39 + [33,34,35,37,38,42,43], // 40 + [35,36,37,39,42,45,46], // 41 + [33,34,38,39,40,41,43,50,51], // 42 + [35,39,40,42,46,48,49,52], // 43 + [35,36,49,50,51], // 44 + [39,41,46,49,53,54], // 45 + [39,41,43,45,47,48,49,50,52,53,54], // 46 + [38,46,51,54,55,56], // 47 + [39,43,46,49,50,53,54,55,57], // 48 + [43,44,45,46,48,51,52,55,57], // 49 + [42,44,46,48,55], // 50 + [42,44,47,49,58,60], // 51 + [43,46,49,53,57,59], // 52 + [45,46,48,52,54,55,60,61], // 53 + [45,46,47,48,53,58,59,60,62,63], // 54 + [47,48,49,50,53,56,57,59,63], // 55 + [47,55,59,60,62], // 56 + [48,49,52,55,66], // 57 + [51,54,59,62], // 58 + [52,54,55,56,58,63,64], // 59 + [51,53,54,56,61,63,64,65,67,69], // 60 + [53,60,66,67,68], // 61 + [54,56,58,63,64,68,71], // 62 + [54,55,59,60,62,65,67,68,69,70,71,72], // 63 + [59,60,62,66,68,69], // 64 + [60,63,68,69,71], // 65 + [57,61,64,73,74,75], // 66 + [60,61,63,68,73,75,76], // 67 + [61,62,63,64,65,67,69,70,71,72,74], // 68 + [60,63,64,65,68,75], // 69 + [63,68,71,76,77], // 70 + [62,63,65,68,70,75,76,77], // 71 + [63,68,73,75,76,77,78,79,80,81], // 72 + [66,67,72,76], // 73 + [66,68,77,78], // 74 + [66,67,69,71,72,80,81,83,84], // 75 + [67,70,71,72,73,77,79,83], // 76 + [70,71,72,74,76,78,81,83,84,85,86], // 77 + [72,74,77,81,82,83,85,86], // 78 + [72,76,80,81,84,86], // 79 + [72,75,79,82,83,86,87], // 80 + [72,75,77,78,79,82,83,84,85,86,87,88], // 81 + [78,80,81,84,88,89,91], // 82 + [75,76,77,78,80,81,85,88,89], // 83 + [75,77,79,81,82,88,90,91], // 84 + [77,78,81,83,88,90,91,93], // 85 + [77,78,79,80,81,92,95], // 86 + [80,81,92,93,94], // 87 + [81,82,83,84,85,89,90,97], // 88 + [82,83,88,93,94,95,96], // 89 + [84,85,88,91,92,95,97,99], // 90 + [82,84,85,90,95,96,98,99], // 91 + [86,87,90,93,94,95,96,98], // 92 + [85,87,89,92,96,97], // 93 + [87,89,92,95,96,97,98], // 94 + [86,89,90,91,92,94,99], // 95 + [89,91,92,93,94,98,99], // 96 + [88,90,93,94,99], // 97 + [91,92,94,96,99], // 98 + [90,91,95,96,97,98] // 99 +]; + +// Don't touch this code + +var clients = []; +for (let i = 0; i < initTopology.length; i++) { + c = new net.Client(); + c.use(manager); + clients.push(c); +} + +for (let i = 0; i < initTopology.length; i++) { + clients[i].init(function() { + this.delay(startupDelay, function() { + this.manager.setup(initTopology[i].length); + }); + this.delay(500, function() { + this.manager.printPorts(); + }); + this.tick(bufferCheckDelay, function() { + this.manager.checkBuffer(); + }); + this.tick(heartbeatPeriod, function() { + this.manager.heartbeat(); + }); + this.tick(2*heartbeatPeriod, function() { + this.manager.takePulse(); + }); + }); + for (let j = 0; j < initTopology[i].length; j++) { + if (initTopology[i][j] == -1) { + continue; + } + + clients[i].init(function() { + this.delay(startupDelay+connectDelay, function() { + this.manager.connect(j, initTopology[i][j]); + }); + }); + } +} + +//----------------------------------------------------------------------------// +// PUT CUSTOM CODE HERE: +sendPacket(0,99,1,"Hello!",1000); + +// To test link failure, uncomment one. To test node failure, uncomment both. +//disconnect(0,1,2,0,6000); +//disconnect(2,1,4,1,6000); + +// To test how network reacts to heavy traffic at a desired node, uncomment. +//sendPacket(5,2,1,"Distraction 0!",6000); +//sendPacket(5,2,1,"Distraction 1!",6000); +//sendPacket(5,2,1,"Distraction 2!",6000); +//sendPacket(5,2,1,"Distraction 3!",6000); +//sendPacket(5,2,1,"Distraction 4!",6000); +//sendPacket(5,2,1,"Distraction 5!",6000); +//sendPacket(5,2,1,"Distraction 6!",6000); +//sendPacket(5,2,1,"Distraction 7!",6000); +//sendPacket(5,2,1,"Distraction 8!",6000); + +//sendPacket(4,0,1,"I love you, One!",8000); + + +//Don't add stuff below this: +//----------------------------------------------------------------------------// + +for (let i = 0; i < initTopology.length; i++) { + net.add(1, clients[i]); +} +net.run(100 * 1000); // runs for 100 seconds + + + +function send(from, port, message, delay, periodic=false) { + if (periodic) { + clients[from].init(function() { + this.tick(delay, function() { + this.manager.send(port, message); + }); + }); + } else { + clients[from].init(function() { + this.delay(delay, function() { + this.manager.send(port, message); + }); + }); + } +} + +function sendPacket(from, dest, size, data, delay, periodic=false) { + if (periodic) { + clients[from].init(function() { + this.tick(delay, function() { + this.manager.sendPacket(252, dest, -1, undefined, size, data, -1); + }); + }); + } else { + clients[from].init(function() { + this.delay(delay, function() { + this.manager.sendPacket(252, dest, -1, undefined, size, data, -1); + }); + }); + } +} + +function connect(a, aPort, b, bPort, delay) { + clients[a].init(function() { + this.delay(delay, function() { + this.manager.connect(aPort, b); + }); + }); + clients[b].init(function() { + this.delay(delay, function() { + this.manager.connect(bPort, a); + }); + }); +} + +function disconnect(a, aPort, b, bPort, delay) { + clients[a].init(function() { + this.delay(delay, function() { + this.manager.disconnect(aPort); + }); + }); + clients[b].init(function() { + this.delay(delay, function() { + this.manager.disconnect(bPort); + }); + }); } \ No newline at end of file diff --git a/sim/random_networks.m b/sim/random_networks.m new file mode 100644 index 0000000000000000000000000000000000000000..a71e1501b133100b062102f710f45493ee24004f --- /dev/null +++ b/sim/random_networks.m @@ -0,0 +1,68 @@ +function random_networks(n,max_out_degree,max_length) +%% Code modified from: +% Copyright (c) 2010, huxp ?? +% All rights reserved. +% +% Redistribution and use in source and binary forms, with or without +% modification, are permitted provided that the following conditions are +% met: +% +% * Redistributions of source code must retain the above copyright +% notice, this list of conditions and the following disclaimer. +% * Redistributions in binary form must reproduce the above copyright +% notice, this list of conditions and the following disclaimer in +% the documentation and/or other materials provided with the distribution +% +% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +% ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +% SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +% CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +% POSSIBILITY OF SUCH DAMAGE. + +rng(0) +edge_list = zeros(0,2); +r=0; +out_arc=zeros(max_out_degree,2); +for i=1:n-1 + end_node=i+unidrnd(max_length,1,max(unidrnd(max_out_degree),1)); + end_node=end_node(end_node<=n); + if isempty(end_node) + end_node=n; + end + end_node = unique(end_node, 'first'); + l_end_node=length(end_node); + out_arc(1:l_end_node,1)=i; + out_arc(1:l_end_node,2)=end_node; + edge_list(r+1:r+l_end_node,:) = out_arc(1:l_end_node,:); + r=r+l_end_node; +end + +%% Original code +topology = cell(max(max(edge_list)),1); +for i = 1:length(edge_list) + topology{edge_list(i,1)} = [topology{edge_list(i,1)},edge_list(i,2)]; + topology{edge_list(i,2)} = [topology{edge_list(i,2)},edge_list(i,1)]; +end + +fh = fopen('js_code.txt','w'); +fprintf(fh,'var initTopology = [\n'); +for i = 1:numel(topology) + fprintf(fh,'\t[%d',topology{i}(1)-1); + for j = 2:numel(topology{i}) + fprintf(fh,',%d',topology{i}(j)-1); + end + fprintf(fh,']'); + if i ~= length(topology) + fprintf(fh,','); + end + fprintf(fh,'\t\t// %d\n',i-1); +end +fprintf(fh,'];'); +fclose(fh); +end \ No newline at end of file