From 5ece45091beb47aed5651231a4bd12e11fec7cd5 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 18 Dec 2015 22:37:02 -0800 Subject: [PATCH] style consistency. --- README.md | 4 - lib/bcoin/block.js | 98 +++++++----- lib/bcoin/bloom.js | 53 +++--- lib/bcoin/chain.js | 139 ++++++++++------ lib/bcoin/hd.js | 110 +++++++------ lib/bcoin/peer.js | 55 +++++-- lib/bcoin/pool.js | 199 +++++++++++++++-------- lib/bcoin/protocol/constants.js | 7 +- lib/bcoin/protocol/framer.js | 76 +++++---- lib/bcoin/protocol/network.js | 5 +- lib/bcoin/protocol/parser.js | 224 ++++++++++++++++---------- lib/bcoin/script.js | 276 ++++++++++++++++++-------------- lib/bcoin/tx-pool.js | 57 ++++--- lib/bcoin/tx.js | 193 ++++++++++++---------- lib/bcoin/utils.js | 172 +++++++++++++------- lib/bcoin/wallet.js | 65 +++++--- 16 files changed, 1072 insertions(+), 661 deletions(-) diff --git a/README.md b/README.md index 4a7e46bf..6537a5c9 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,6 @@ synchronized balance and send and receive payments without keeping track of a BCoin is implemented in *pure* javascript, and is browserify-able (this means compiling a binding to an ECDSA library is not even required for node.js). -**NOTE**: BCoin is also in the process of supporting the original (pre-bip37) -satoshi protocol, which will also optionally give the user the ability download -the entire blockchain. - ## Prerequisites ``` diff --git a/lib/bcoin/block.js b/lib/bcoin/block.js index 8fd6999f..deaeaeb5 100644 --- a/lib/bcoin/block.js +++ b/lib/bcoin/block.js @@ -3,6 +3,8 @@ var utils = bcoin.utils; var constants = bcoin.protocol.constants; function Block(data, subtype) { + var self = this; + if (!(this instanceof Block)) return new Block(data, subtype); @@ -28,7 +30,6 @@ function Block(data, subtype) { this.invalid = false; if (this.subtype === 'block') { - var self = this; this.txs = data.txs || []; this.txs = this.txs.map(function(tx) { tx._network = self._network; @@ -49,7 +50,6 @@ function Block(data, subtype) { // Verify partial merkle tree and fill `ts` array this._verifyMerkle(); } -module.exports = Block; Block.prototype.hash = function hash(enc) { // Hash it @@ -91,33 +91,30 @@ Block.prototype.hasTX = function hasTX(hash) { Block.prototype._verifyMerkle = function verifyMerkle() { var height = 0; - - if (this.subtype === 'block') - return; - - // Count leafs - for (var i = this.totalTX; i > 0; i >>= 1) - height++; - if (this.totalTX > (1 << (height - 1))) - height++; - var tx = []; var i = 0; var j = 0; var hashes = this.hashes; var flags = this.flags; + var i, root; - var root = visit(1); - if (!root || root !== this.merkleRoot) { - this.invalid = true; + if (this.subtype === 'block') return; - } - this.tx = tx; + + // Count leaves + for (i = this.totalTX; i > 0; i >>= 1) + height++; + + if (this.totalTX > (1 << (height - 1))) + height++; + function visit(depth) { + var flag, left, right; + if (i === flags.length * 8 || j === hashes.length) return null; - var flag = (flags[i >> 3] >>> (i & 7)) & 1; + flag = (flags[i >> 3] >>> (i & 7)) & 1; i++; if (flag === 0 || depth === height) { @@ -127,34 +124,49 @@ Block.prototype._verifyMerkle = function verifyMerkle() { } // Go deeper - var left = visit(depth + 1); + left = visit(depth + 1); if (!left) return null; - var right = visit(depth + 1); + + right = visit(depth + 1); if (right === left) return null; + if (!right) right = left; + return utils.toHex(utils.dsha256(left + right, 'hex')); } + + root = visit(1); + + if (!root || root !== this.merkleRoot) { + this.invalid = true; + return; + } + + this.tx = tx; }; Block.prototype.getMerkleRoot = function getMerkleRoot() { var merkleTree = []; + var i, j, size, i2, hash; - for (var i = 0; i < this.txs.length; i++) { + for (i = 0; i < this.txs.length; i++) { merkleTree.push(this.txs[i].hash('hex')); } - var j = 0; - for (var size = this.txs.length; size > 1; size = ((size + 1) / 2) | 0) { - for (var i = 0; i < size; i += 2) { - var i2 = Math.min(i + 1, size - 1); + j = 0; + size = this.txs.length; + + for (; size > 1; size = ((size + 1) / 2) | 0) { + for (i = 0; i < size; i += 2) { + i2 = Math.min(i + 1, size - 1); if (i2 === i + 1 && i2 + 1 === size && merkleTree[j + i] === merkleTree[j + i2]) { return utils.toHex(constants.zeroHash); } - var hash = utils.dsha256(merkleTree[j + i] + merkleTree[j + i2], 'hex'); + hash = utils.dsha256(merkleTree[j + i] + merkleTree[j + i2], 'hex'); merkleTree.push(utils.toHex(hash)); } j += size; @@ -169,6 +181,8 @@ Block.prototype.getMerkleRoot = function getMerkleRoot() { // This mimics the behavior of CheckBlockHeader() // and CheckBlock() in bitcoin/src/main.cpp. Block.prototype._checkBlock = function checkBlock() { + var i, unique, hash, merkleRoot; + // Check proof of work matches claimed amount if (!utils.testTarget(this.bits, this.hash())) return false; @@ -184,29 +198,29 @@ Block.prototype._checkBlock = function checkBlock() { } // First TX must be a coinbase - if (!this.txs.length || - this.txs[0].inputs.length !== 1 || - +this.txs[0].inputs[0].out.hash !== 0) + if (!this.txs.length + || this.txs[0].inputs.length !== 1 + || +this.txs[0].inputs[0].out.hash !== 0) return false; // The rest of the txs must not be coinbases - for (var i = 1; i < this.txs.length; i++) { - if (this.txs[i].inputs.length === 1 && - +this.txs[i].inputs[0].out.hash === 0) + for (i = 1; i < this.txs.length; i++) { + if (this.txs[i].inputs.length === 1 + && +this.txs[i].inputs[0].out.hash === 0) return false; } // Check for duplicate tx ids - var unique = {}; - for (var i = 0; i < this.txs.length; i++) { - var hash = this.txs[i].hash('hex'); + unique = {}; + for (i = 0; i < this.txs.length; i++) { + hash = this.txs[i].hash('hex'); if (unique[hash]) return false; unique[hash] = true; } // Build MerkleTree - var merkleRoot = this.getMerkleRoot(); + merkleRoot = this.getMerkleRoot(); // Check merkle root if (merkleRoot !== this.merkleRoot) @@ -228,22 +242,26 @@ Block.prototype.toJSON = function toJSON() { }; Block.fromJSON = function fromJSON(json) { + var raw, parser, data, block; + utils.assert.equal(json.v, 1); utils.assert.equal(json.type, 'block'); - var raw = utils.toArray(json.block, 'hex'); + raw = utils.toArray(json.block, 'hex'); - var parser = new bcoin.protocol.parser(); + parser = new bcoin.protocol.parser(); - var data = json.subtype === 'merkleblock' ? + data = json.subtype === 'merkleblock' ? parser.parseMerkleBlock(raw) : parser.parseBlock(raw); data._network = json._network; - var block = new Block(data, json.subtype); + block = new Block(data, json.subtype); block._hash = json.hash; return block; }; + +module.exports = Block; diff --git a/lib/bcoin/bloom.js b/lib/bcoin/bloom.js index 9011b553..c786d24e 100644 --- a/lib/bcoin/bloom.js +++ b/lib/bcoin/bloom.js @@ -12,7 +12,6 @@ function Bloom(size, n, tweak) { this.reset(); } -module.exports = Bloom; Bloom.prototype.hash = function hash(val, n) { return Bloom.hash(val, sum32(mul32(n, 0xfba4c795), this.tweak)) % this.size; @@ -24,24 +23,28 @@ Bloom.prototype.reset = function reset() { }; Bloom.prototype.add = function add(val, enc) { + var i, bit, pos, shift; + val = utils.toArray(val, enc); - for (var i = 0; i < this.n; i++) { - var bit = this.hash(val, i); - var pos = 1 << (bit & 0x1f); - var shift = bit >> 5; + for (i = 0; i < this.n; i++) { + bit = this.hash(val, i); + pos = 1 << (bit & 0x1f); + shift = bit >> 5; this.filter[shift] |= pos; } }; Bloom.prototype.test = function test(val, enc) { + var i, bit, pos, shift; + val = utils.toArray(val, enc); - for (var i = 0; i < this.n; i++) { - var bit = this.hash(val, i); - var pos = 1 << (bit & 0x1f); - var shift = bit >> 5; + for (i = 0; i < this.n; i++) { + bit = this.hash(val, i); + pos = 1 << (bit & 0x1f); + shift = bit >> 5; if ((this.filter[shift] & pos) === 0) return false; @@ -53,8 +56,10 @@ Bloom.prototype.test = function test(val, enc) { Bloom.prototype.toArray = function toArray() { var bytes = Math.ceil(this.size / 8); var res = new Array(this.filter.length * 4); - for (var i = 0; i < this.filter.length; i++) { - var w = this.filter[i]; + var i, w; + + for (i = 0; i < this.filter.length; i++) { + w = this.filter[i]; res[i * 4] = w & 0xff; res[i * 4 + 1] = (w >> 8) & 0xff; res[i * 4 + 2] = (w >> 16) & 0xff; @@ -69,22 +74,26 @@ function mul32(a, b) { var blo = b & 0xffff; var ahi = a >>> 16; var bhi = b >>> 16; + var r; var lo = alo * blo; var hi = (ahi * blo + bhi * alo) & 0xffff; hi += lo >>> 16; lo &= 0xffff; - var r = (hi << 16) | lo; + r = (hi << 16) | lo; if (r < 0) r += 0x100000000; + return r; } function sum32(a, b) { var r = (a + b) & 0xffffffff; + if (r < 0) r += 0x100000000; + return r; } @@ -103,11 +112,14 @@ function hash(data, seed) { var n = 0xe6546b64; var hash = seed; - for (var i = 0; i + 4 <= data.length; i += 4) { - var w = data[i] | - (data[i + 1] << 8) | - (data[i + 2] << 16) | - (data[i + 3] << 24); + + var i, w, r, j; + + for (i = 0; i + 4 <= data.length; i += 4) { + w = data[i] + | (data[i + 1] << 8) + | (data[i + 2] << 16) + | (data[i + 3] << 24); w = mul32(w, c1); w = rotl32(w, r1); @@ -120,8 +132,8 @@ function hash(data, seed) { } if (i !== data.length) { - var r = 0; - for (var j = data.length - 1; j >= i; j--) + r = 0; + for (j = data.length - 1; j >= i; j--) r = (r << 8) | data[j]; r = mul32(r, c1); @@ -145,4 +157,7 @@ function hash(data, seed) { return hash; } + Bloom.hash = hash; + +module.exports = Bloom; diff --git a/lib/bcoin/chain.js b/lib/bcoin/chain.js index 4db58cec..b813904e 100644 --- a/lib/bcoin/chain.js +++ b/lib/bcoin/chain.js @@ -8,6 +8,8 @@ var utils = bcoin.utils; var assert = utils.assert; function Chain(options) { + var preload = network.preload; + if (!(this instanceof Chain)) return new Chain(options); @@ -38,8 +40,6 @@ function Chain(options) { }; this.request = new utils.RequestCache(); - var preload = network.preload; - // Start from the genesis block // if we're a full node. if (this.options.fullNode) { @@ -60,8 +60,8 @@ function Chain(options) { this.loading = false; this._init(); } + inherits(Chain, EventEmitter); -module.exports = Chain; function compareTs(a, b) { return a -b; @@ -69,6 +69,7 @@ function compareTs(a, b) { Chain.prototype._init = function _init() { var self = this; + var s; if (!this.storage) return; @@ -79,7 +80,7 @@ Chain.prototype._init = function _init() { this.loading = true; - var s = this.storage.createReadStream({ + s = this.storage.createReadStream({ start: this.prefix, end: this.prefix + 'z' }); @@ -103,18 +104,19 @@ Chain.prototype._init = function _init() { Chain.prototype._getRange = function _getRange(hash, ts, futureOnly) { var pos = utils.binaryInsert(this.index.ts, ts, compareTs, true); var start = Math.min(Math.max(0, pos), this.index.ts.length - 1); + var curr, wnd, end; while (start > 0 && this.index.ts[start] > ts) start--; - var curr = this.index.ts[start]; - var wnd = 2 * 3600; + curr = this.index.ts[start]; + wnd = 2 * 3600; if (!futureOnly) while (start > 0 && this.index.ts[start] + wnd > curr) start--; - var end = Math.min(Math.max(0, pos), this.index.ts.length - 1); + end = Math.min(Math.max(0, pos), this.index.ts.length - 1); while (end < this.index.ts.length - 1 && this.index.ts[end] - wnd < ts) end++; @@ -127,13 +129,15 @@ Chain.prototype._probeIndex = function _probeIndex(hash, ts) { var start = 0; var end = this.index.ts.length; + var range, i; + if (ts) { - var range = this._getRange(hash, ts); + range = this._getRange(hash, ts); start = range.start; end = range.end; } - for (var i = start; i <= end; i++) + for (i = start; i <= end; i++) if (this.index.hashes[i] === hash) return { i: i, height: this.index.heights[i], ts: this.index.ts[i] }; @@ -141,19 +145,22 @@ Chain.prototype._probeIndex = function _probeIndex(hash, ts) { }; Chain.prototype._addIndex = function _addIndex(hash, ts, height) { + var self = this; + if (this._probeIndex(hash, ts)) return new Error('Already added.'); var pos = utils.binaryInsert(this.index.ts, ts, compareTs, true); + var checkpoint, obj; // Avoid duplicates - if (this.index.hashes[pos] === hash || - this.index.hashes[pos - 1] === hash || - this.index.hashes[pos + 1] === hash) { + if (this.index.hashes[pos] === hash + || this.index.hashes[pos - 1] === hash + || this.index.hashes[pos + 1] === hash) { return new Error('Duplicate height.'); } - var checkpoint = network.checkpoints[height]; + checkpoint = network.checkpoints[height]; if (checkpoint) { this.emit('checkpoint', height, hash, checkpoint); if (hash !== checkpoint) { @@ -171,8 +178,8 @@ Chain.prototype._addIndex = function _addIndex(hash, ts, height) { if (!this.storage) return; - var self = this; - var obj = { ts: ts, height: height }; + obj = { ts: ts, height: height }; + this.storage.put(this.prefix + hash, obj, function(err) { if (err) self.emit('error', err); @@ -195,14 +202,14 @@ Chain.prototype.resetHeight = function resetHeight(height) { if (index < 0) throw new Error('Cannot reset to height of ' + height); - this.block.list = []; + this.block.list.length = 0; this.block.bloom.reset(); this.orphan.map = {}; this.orphan.bmap = {}; this.orphan.count = 0; - this.index.ts = this.index.ts.slice(0, index + 1); - this.index.hashes = this.index.hashes.slice(0, index + 1); - this.index.heights = this.index.heights.slice(0, index + 1); + this.index.ts.length = index + 1; + this.index.hashes.length = index + 1; + this.index.heights.length = index + 1; this.index.bloom.reset(); this.index.hashes.forEach(function(hash) { self.index.bloom.add(hash, 'hex'); @@ -211,13 +218,15 @@ Chain.prototype.resetHeight = function resetHeight(height) { }; Chain.prototype._killFork = function _killFork(probe) { + var self = this; var delta = 2 * 3600; var upper = probe.ts + delta; var lower = probe.ts - delta; + var index, i, len, hash; // Search duplicate heights down - var index = -1; - for (var i = probe.i - 1; i > 0 && this.index.ts[i] > lower; i--) { + index = -1; + for (i = probe.i - 1; i > 0 && this.index.ts[i] > lower; i--) { if (probe.height === this.index.heights[i]) { index = i; break; @@ -226,8 +235,8 @@ Chain.prototype._killFork = function _killFork(probe) { // And up if (index === -1) { - var len = this.index.ts.length; - for (var i = probe.i + 1; i < len && this.index.ts[i] < upper; i++) { + len = this.index.ts.length; + for (i = probe.i + 1; i < len && this.index.ts[i] < upper; i++) { if (probe.height === this.index.heights[i]) { index = i; break; @@ -238,7 +247,7 @@ Chain.prototype._killFork = function _killFork(probe) { if (index === -1) return false; - var hash = this.index.hashes[index]; + hash = this.index.hashes[index]; this.index.hashes.splice(index, 1); this.index.ts.splice(index, 1); this.index.heights.splice(index, 1); @@ -247,7 +256,6 @@ Chain.prototype._killFork = function _killFork(probe) { if (!this.storage) return true; - var self = this; this.storage.del(this.prefix + hash, function(err) { if (err) self.emit('error', err); @@ -267,6 +275,8 @@ Chain.prototype.add = function add(block) { var res = false; var err = null; var initial = block; + var hash, prev, prevProbe, range, hashes; + do { // No need to revalidate orphans if (!res && !block.verify()) { @@ -274,8 +284,8 @@ Chain.prototype.add = function add(block) { break; } - var hash = block.hash('hex'); - var prev = block.prevBlock; + hash = block.hash('hex'); + prev = block.prevBlock; // If the block is already known to be an orphan if (this.orphan.map[prev]) { @@ -283,7 +293,7 @@ Chain.prototype.add = function add(block) { break; } - var prevProbe = this._probeIndex(prev, block.ts); + prevProbe = this._probeIndex(prev, block.ts); // Remove forked nodes from storage, if shorter chain is detected if (this._killFork(prevProbe)) { @@ -297,8 +307,9 @@ Chain.prototype.add = function add(block) { this.orphan.map[prev] = block; this.orphan.bmap[hash] = block; - var range = this._getRange(hash, block.ts, true); - var hashes = this.index.hashes.slice(range.start, range.end + 1); + range = this._getRange(hash, block.ts, true); + hashes = this.index.hashes.slice(range.start, range.end + 1); + this.emit('missing', prev, hashes, block); break; } @@ -334,6 +345,8 @@ Chain.prototype.add = function add(block) { }; Chain.prototype._compress = function compress() { + var i; + // Keep at least 1000 blocks and at most 2000 by default if (this.block.list.length < this.cacheLimit) return; @@ -342,7 +355,7 @@ Chain.prototype._compress = function compress() { this.block.list = this.block.list.slice(-(this.cacheLimit / 2 | 0)); this.block.bloom.reset(); - for (var i = 0; i < this.block.list.length; i++) + for (i = 0; i < this.block.list.length; i++) this._bloomBlock(this.block.list[i]); }; @@ -351,21 +364,25 @@ Chain.prototype._bloomBlock = function _bloomBlock(block) { }; Chain.prototype.has = function has(hash, noProbe, cb) { + var i; + if (typeof noProbe === 'function') { cb = noProbe; noProbe = false; } + if (this.loading) { this.once('load', function() { this.has(hash, noProbe, cb); }); return; } + cb = utils.asyncify(cb); if (this.block.bloom.test(hash, 'hex')) { if (this.strict) { - for (var i = 0; i < this.block.list.length; i++) + for (i = 0; i < this.block.list.length; i++) if (this.block.list[i].hash('hex') === hash) return cb(true); } else { @@ -406,6 +423,8 @@ Chain.prototype.hasOrphan = function hasOrphan(hash) { }; Chain.prototype.get = function get(hash, force, cb) { + var i, block; + if (typeof force === 'function') { cb = force; force = false; @@ -413,11 +432,11 @@ Chain.prototype.get = function get(hash, force, cb) { // Cached block found if (!force && this.block.bloom.test(hash, 'hex')) { - for (var i = 0; i < this.block.list.length; i++) { + for (i = 0; i < this.block.list.length; i++) { if (this.block.list[i].hash('hex') === hash) { // NOTE: we return right after the statement - so `block` should be // valid at the time of nextTick call - var block = this.block.list[i]; + block = this.block.list[i]; bcoin.utils.nextTick(function() { cb(block); }); @@ -437,6 +456,7 @@ Chain.prototype.isFull = function isFull() { return false; var delta = (+new Date() / 1000) - this.index.ts[this.index.ts.length - 1]; + return delta < 40 * 60; }; @@ -447,34 +467,42 @@ Chain.prototype.fillPercent = function fillPercent() { }; Chain.prototype.hashesInRange = function hashesInRange(start, end, cb) { + var ts, hashes, heights, zip, i, count; + if (this.loading) { this.once('load', function() { this.hashesInRange(start, end, cb); }); return; } + cb = utils.asyncify(cb); - var ts = this.index.ts; + ts = this.index.ts; start = utils.binaryInsert(ts, start, compareTs, true); if (start > 0 && ts[start - 1] >= start) start--; + end = utils.binaryInsert(ts, end, compareTs, true); // Zip hashes and heights together and sort them by height - var hashes = this.index.hashes.slice(start, end); - var heights = this.index.heights.slice(start, end); - var zip = []; - for (var i = 0; i < hashes.length; i++) + hashes = this.index.hashes.slice(start, end); + heights = this.index.heights.slice(start, end); + zip = []; + + for (i = 0; i < hashes.length; i++) zip.push({ hash: hashes[i], height: heights[i] }); + zip = zip.sort(function(a, b) { return a.height - b.height; }); - var hashes = zip.map(function(a) { + + hashes = zip.map(function(a) { return a.hash; }); - var count = zip[zip.length - 1].height - zip[0].height + 1; + count = zip[zip.length - 1].height - zip[0].height + 1; + return cb(hashes, count); }; @@ -538,14 +566,13 @@ Chain.prototype.locatorHashes = function(start) { Chain.prototype.getOrphanRoot = function getOrphanRoot(hash) { var self = this; + var root = hash; if (Array.isArray(hash)) hash = utils.toHex(hash); else if (hash.hash) hash = hash.hash('hex'); - var root = hash; - while (this.orphan.bmap[hash]) { root = hash; hash = this.orphan.bmap[hash].prevBlock; @@ -580,11 +607,17 @@ Chain.prototype.toJSON = function toJSON() { var lastTs = 0; var lastHeight = -1000; - for (var i = 0; i < this.index.ts.length - keep; i++) { - var ts = this.index.ts[i]; - var delta = ts < 1356984000 ? delta1 : - ts < 1388520000 ? delta2 : delta3; - var hdelta = this.index.heights[i] - lastHeight; + var i, ts, delta, hdelta; + + for (i = 0; i < this.index.ts.length - keep; i++) { + ts = this.index.ts[i]; + + delta = ts < 1356984000 + ? delta1 + : ts < 1388520000 ? delta2 : delta3; + + hdelta = this.index.heights[i] - lastHeight; + if (ts - lastTs < delta && hdelta < 250) continue; @@ -606,13 +639,18 @@ Chain.prototype.toJSON = function toJSON() { }; Chain.prototype.fromJSON = function fromJSON(json) { + var i; + assert.equal(json.v, 1); assert.equal(json.type, 'chain'); + if (json.network) assert.equal(json.network, network.type); + this.index.hashes = json.hashes.slice(); this.index.ts = json.ts.slice(); this.index.heights = json.heights.slice(); + if (this.index.bloom) this.index.bloom.reset(); else @@ -621,7 +659,8 @@ Chain.prototype.fromJSON = function fromJSON(json) { if (this.index.hashes.length === 0) this.add(new bcoin.block(network.genesis, 'block')); - for (var i = 0; i < this.index.hashes.length; i++) { + for (i = 0; i < this.index.hashes.length; i++) this.index.bloom.add(this.index.hashes[i], 'hex'); - } }; + +module.exports = Chain; diff --git a/lib/bcoin/hd.js b/lib/bcoin/hd.js index 70f51c50..bd2e33b4 100644 --- a/lib/bcoin/hd.js +++ b/lib/bcoin/hd.js @@ -96,13 +96,14 @@ HDSeed._entropy = function(size) { HDSeed._mnemonic = function(entropy) { var bin = ''; - for (var i = 0; i < entropy.length; i++) { - bin = bin + ('00000000' + entropy[i].toString(2)).slice(-8); - } - var mnemonic = []; + var i, wi; + + for (i = 0; i < entropy.length; i++) + bin = bin + ('00000000' + entropy[i].toString(2)).slice(-8); + for (i = 0; i < bin.length / 11; i++) { - var wi = parseInt(bin.slice(i * 11, (i + 1) * 11), 2); + wi = parseInt(bin.slice(i * 11, (i + 1) * 11), 2); mnemonic.push(english[wi]); } @@ -231,6 +232,7 @@ HDPriv.prototype._unbuild = function(xkey) { var raw = utils.fromBase58(xkey); var data = {}; var off = 0; + var hash; data.version = utils.readU32BE(raw, off); off += 4; @@ -248,7 +250,7 @@ HDPriv.prototype._unbuild = function(xkey) { data.checksum = utils.readU32BE(raw, off); off += 4; - var hash = utils.dsha256(raw.slice(0, -4)); + hash = utils.dsha256(raw.slice(0, -4)); if (data.checksum !== utils.readU32BE(hash, 0)) throw new Error('checksum mismatch'); @@ -258,6 +260,7 @@ HDPriv.prototype._unbuild = function(xkey) { HDPriv.prototype._build = function(data) { var sequence = []; var off = 0; + var checksum, xprivkey, pair, privateKey, publicKey, size, fingerPrint; utils.writeU32BE(sequence, data.version, off); off += 4; @@ -273,18 +276,18 @@ HDPriv.prototype._build = function(data) { off += 1; utils.copy(data.privateKey, sequence, off, true); off += data.privateKey.length; - var checksum = utils.dsha256(sequence).slice(0, 4); + checksum = utils.dsha256(sequence).slice(0, 4); utils.copy(checksum, sequence, off, true); off += 4; - var xprivkey = utils.toBase58(sequence); + xprivkey = utils.toBase58(sequence); - var pair = bcoin.ecdsa.keyPair({ priv: data.privateKey }); - var privateKey = pair.getPrivate().toArray(); - var publicKey = pair.getPublic(true, 'array'); + pair = bcoin.ecdsa.keyPair({ priv: data.privateKey }); + privateKey = pair.getPrivate().toArray(); + publicKey = pair.getPublic(true, 'array'); - var size = PARENT_FINGER_PRINT_SIZE; - var fingerPrint = utils.ripesha(publicKey).slice(0, size); + size = PARENT_FINGER_PRINT_SIZE; + fingerPrint = utils.ripesha(publicKey).slice(0, size); this.version = data.version; this.depth = data.depth; @@ -307,22 +310,24 @@ HDPriv.prototype.derive = function(index, hard) { if (typeof index === 'string') return this.deriveString(index); + var index_ = []; + var data, hash, leftPart, chainCode, privateKey; + hard = index >= HARDENED ? true : hard; if (index < HARDENED && hard === true) index += HARDENED; - var index_ = []; utils.writeU32BE(index_, index, 0); - var data = hard + data = hard ? [0].concat(this.privateKey).concat(index_) : data = [].concat(this.publicKey).concat(index_); - var hash = sha512hmac(data, this.chainCode); - var leftPart = new bn(hash.slice(0, 32)); - var chainCode = hash.slice(32, 64); + hash = sha512hmac(data, this.chainCode); + leftPart = new bn(hash.slice(0, 32)); + chainCode = hash.slice(32, 64); - var privateKey = leftPart.add(new bn(this.privateKey)).mod(ec.curve.n).toArray(); + privateKey = leftPart.add(new bn(this.privateKey)).mod(ec.curve.n).toArray(); return new HDPriv({ master: this.master, @@ -340,6 +345,7 @@ HDPriv._getIndexes = function(path) { var steps = path.split('/'); var root = steps.shift(); var indexes = []; + var i, step, hard, index; if (~PATH_ROOTS.indexOf(path)) return indexes; @@ -347,9 +353,9 @@ HDPriv._getIndexes = function(path) { if (!~PATH_ROOTS.indexOf(root)) return null; - for (var i = 0; i < steps.length; i++) { - var step = steps[i]; - var hard = step[step.length - 1] === '\''; + for (i = 0; i < steps.length; i++) { + step = steps[i]; + hard = step[step.length - 1] === '\''; if (hard) step = step.slice(0, -1); @@ -357,7 +363,7 @@ HDPriv._getIndexes = function(path) { if (!step || step[0] === '-') return null; - var index = +step; + index = +step; if (hard) index += HARDENED; @@ -399,18 +405,17 @@ HDPriv.prototype.deriveString = function(path) { */ function HDPub(options) { + var data; + if (!(this instanceof HDPub)) return new HDPub(options); - var data; - if (typeof options === 'string' && options.indexOf('xpub') === 0) options = { xkey: options }; - if (options.xkey) - data = this._unbuild(options.xkey); - else - data = options; + data = options.xkey + ? this._unbuild(options.xkey) + : options; data = this._normalize(data, network.prefixes.xpubkey); @@ -425,6 +430,7 @@ HDPub.prototype._unbuild = function(xkey) { var raw = utils.fromBase58(xkey); var data = {}; var off = 0; + var hash; data.version = utils.readU32BE(raw, off); off += 4; @@ -441,7 +447,7 @@ HDPub.prototype._unbuild = function(xkey) { data.checksum = utils.readU32BE(raw, off); off += 4; - var hash = utils.dsha256(raw.slice(0, -4)); + hash = utils.dsha256(raw.slice(0, -4)); if (data.checksum !== utils.readU32BE(hash, 0)) throw new Error('checksum mismatch'); @@ -451,6 +457,7 @@ HDPub.prototype._unbuild = function(xkey) { HDPub.prototype._build = function(data) { var sequence = []; var off = 0; + var checksum, xpubkey, publicKey, size, fingerPrint; utils.writeU32BE(sequence, data.version, off); off += 4; @@ -464,7 +471,7 @@ HDPub.prototype._build = function(data) { off += data.chainCode.length; utils.copy(data.publicKey, sequence, off, true); off += data.publicKey.length; - var checksum = utils.dsha256(sequence).slice(0, 4); + checksum = utils.dsha256(sequence).slice(0, 4); utils.copy(checksum, sequence, off, true); off += 4; @@ -473,11 +480,11 @@ HDPub.prototype._build = function(data) { else if (utils.toHex(checksum) !== utils.toHex(data.checksum)) throw new Error('checksum mismatch'); - var xpubkey = utils.toBase58(sequence); + xpubkey = utils.toBase58(sequence); - var publicKey = data.publicKey; - var size = PARENT_FINGER_PRINT_SIZE; - var fingerPrint = utils.ripesha(publicKey).slice(0, size); + publicKey = data.publicKey; + size = PARENT_FINGER_PRINT_SIZE; + fingerPrint = utils.ripesha(publicKey).slice(0, size); this.version = data.version; this.depth = data.depth; @@ -495,6 +502,9 @@ HDPub.prototype._build = function(data) { }; HDPub.prototype.derive = function(index, hard) { + var index_ = []; + var data, hash, leftPart, chainCode, pair, pubkeyPoint, publicKey; + if (typeof index === 'string') return this.deriveString(index); @@ -504,17 +514,16 @@ HDPub.prototype.derive = function(index, hard) { if (index < 0) throw new Error('invalid path'); - var index_ = []; utils.writeU32BE(index_, index, 0); - var data = [].concat(this.publicKey).concat(index_); - var hash = sha512hmac(data, this.chainCode); - var leftPart = new bn(hash.slice(0, 32)); - var chainCode = hash.slice(32, 64); + data = [].concat(this.publicKey).concat(index_); + hash = sha512hmac(data, this.chainCode); + leftPart = new bn(hash.slice(0, 32)); + chainCode = hash.slice(32, 64); - var pair = bcoin.ecdsa.keyPair({ pub: this.publicKey }); - var pubkeyPoint = ec.curve.g.mul(leftPart).add(pair.pub); - var publicKey = bcoin.ecdsa.keyFromPublic(pubkeyPoint).getPublic(true, 'array'); + pair = bcoin.ecdsa.keyPair({ pub: this.publicKey }); + pubkeyPoint = ec.curve.g.mul(leftPart).add(pair.pub); + publicKey = bcoin.ecdsa.keyFromPublic(pubkeyPoint).getPublic(true, 'array'); return new HDPub({ // version: network.prefixes.xpubkey, @@ -610,8 +619,8 @@ function sha512hmac(data, salt) { function randomBytes(size) { if (isBrowser) { var a = Uint8Array(size); - (window.crypto || window.msCrypto).getRandomValues(a); var buf = new Array(size); + (window.crypto || window.msCrypto).getRandomValues(a); utils.copy(a, buf, 0); return buf; } @@ -629,6 +638,7 @@ function pbkdf2(key, salt, iterations, dkLen) { 'use strict'; var hLen = 64; + if (dkLen > (Math.pow(2, 32) - 1) * hLen) throw Error('Requested key length too long'); @@ -652,9 +662,11 @@ function pbkdf2(key, salt, iterations, dkLen) { var l = Math.ceil(dkLen / hLen); var r = dkLen - (l - 1) * hLen; + var i, j, k, destPos, len; + utils.copy(salt.slice(0, salt.length), block1, 0); - for (var i = 1; i <= l; i++) { + for (i = 1; i <= l; i++) { block1[salt.length + 0] = (i >> 24 & 0xff); block1[salt.length + 1] = (i >> 16 & 0xff); block1[salt.length + 2] = (i >> 8 & 0xff); @@ -664,15 +676,15 @@ function pbkdf2(key, salt, iterations, dkLen) { utils.copy(U.slice(0, hLen), T, 0); - for (var j = 1; j < iterations; j++) { + for (j = 1; j < iterations; j++) { U = sha512hmac(U, key); - for (var k = 0; k < hLen; k++) + for (k = 0; k < hLen; k++) T[k] ^= U[k]; } - var destPos = (i - 1) * hLen; - var len = (i === l ? r : hLen); + destPos = (i - 1) * hLen; + len = (i === l ? r : hLen); utils.copy(T.slice(0, len), DK, 0); } diff --git a/lib/bcoin/peer.js b/lib/bcoin/peer.js index ed65bff1..0046b41c 100644 --- a/lib/bcoin/peer.js +++ b/lib/bcoin/peer.js @@ -13,6 +13,8 @@ try { } function Peer(pool, createSocket, options) { + var self = this; + if (!(this instanceof Peer)) return new Peer(pool, createSocket, options); @@ -31,7 +33,6 @@ function Peer(pool, createSocket, options) { this.ts = this.options.ts || 0; if (this.options.backoff) { - var self = this; setTimeout(function() { self.socket = createSocket(self); self.emit('socket'); @@ -65,26 +66,32 @@ function Peer(pool, createSocket, options) { else this.once('socket', this._init); } + inherits(Peer, EventEmitter); -module.exports = Peer; Peer.prototype._init = function init() { var self = this; + this.socket.once('connect', function() { self.ts = Date.now() / 1000 | 0; }); + this.socket.once('error', function(err) { self._error(err); }); + this.socket.once('close', function() { self._error('socket hangup'); }); + this.socket.on('data', function(chunk) { self.parser.feed(chunk); }); + this.parser.on('packet', function(packet) { self._onPacket(packet); }); + this.parser.on('error', function(err) { self._error(err); }); @@ -134,13 +141,16 @@ Peer.prototype.startSync = function startSync() { }; Peer.prototype.broadcast = function broadcast(items) { + var self = this; + var result; + if (this.destroyed) return; + if (!Array.isArray(items)) items = [ items ]; - var self = this; - var result = items.map(function(item) { + result = items.map(function(item) { var key = item.hash('hex'); var old = this._broadcast.map[key]; if (old) { @@ -195,8 +205,11 @@ Peer.prototype.updateWatch = function updateWatch() { }; Peer.prototype.destroy = function destroy() { + var i; + if (this.destroyed) return; + if (!this.socket) return this.once('socket', this.destroy); @@ -214,7 +227,7 @@ Peer.prototype.destroy = function destroy() { clearInterval(this._ping.timer); this._ping.timer = null; - for (var i = 0; i < this._request.queue.length; i++) + for (i = 0; i < this._request.queue.length; i++) clearTimeout(this._request.queue[i].timer); }; @@ -223,11 +236,13 @@ Peer.prototype.destroy = function destroy() { Peer.prototype._write = function write(chunk) { if (this.destroyed) return; + if (!this.socket) { return this.once('socket', function() { this._write(chunk); }); } + if (NodeBuffer) this.socket.write(new NodeBuffer(chunk)); else @@ -237,15 +252,17 @@ Peer.prototype._write = function write(chunk) { Peer.prototype._error = function error(err) { if (this.destroyed) return; + this.destroy(); this.emit('error', typeof err === 'string' ? new Error(err) : err); }; Peer.prototype._req = function _req(cmd, cb) { + var self = this; + if (this.destroyed) return cb(new Error('Destroyed, sorry')); - var self = this; var entry = { cmd: cmd, cb: cb, @@ -258,19 +275,24 @@ Peer.prototype._req = function _req(cmd, cb) { }, timer: null }; + entry.timer = setTimeout(entry.ontimeout, this._request.timeout); + this._request.queue.push(entry); return entry; }; Peer.prototype._res = function _res(cmd, payload) { - for (var i = 0; i < this._request.queue.length; i++) { - var entry = this._request.queue[i]; + var i, entry, res; + + for (i = 0; i < this._request.queue.length; i++) { + entry = this._request.queue[i]; + if (!entry || entry.cmd && entry.cmd !== cmd) return false; - var res = entry.cb(null, payload, cmd); + res = entry.cb(null, payload, cmd); if (res === this._request.cont) { assert(!entry.cmd); @@ -388,6 +410,7 @@ Peer.prototype._handleGetAddr = function handleGetAddr() { this.pool.peers.block, this.pool.peers.load ).filter(Boolean); + var addrs; // NOTE: For IPv6 BTC uses: // '0000:0000:0000:0000:0000:xxxx:xxxx:ffff' @@ -417,7 +440,7 @@ Peer.prototype._handleGetAddr = function handleGetAddr() { }; }); - var addrs = peers.map(function(peer) { + addrs = peers.map(function(peer) { if (peer.ver === 6) { while (peer.ipv6.split(':').length < 8) peer.ipv6 = '0000:' + peer.ipv6; @@ -450,16 +473,18 @@ Peer.prototype._handleInv = function handleInv(items) { return item.hash; }); + var req, i, block, hash; + if (blocks.length === 1) this.bestBlock = utils.toHex(blocks[0]); this.emit('blocks', blocks); if (this.pool.options.fullNode) { - var req = []; - for (var i = 0; i < blocks.length; i++) { - var block = blocks[i]; - var hash = utils.toHex(block); + req = []; + for (i = 0; i < blocks.length; i++) { + block = blocks[i]; + hash = utils.toHex(block); if (this.chain.hasOrphan(hash)) { this.loadBlocks(this.chain.locatorHashes(), this.chain.getOrphanRoot(hash)); continue; @@ -507,3 +532,5 @@ Peer.prototype.loadHeaders = function loadHeaders(hashes, stop) { Peer.prototype.loadBlocks = function loadBlocks(hashes, stop) { this._write(this.framer.getBlocks(hashes, stop)); }; + +module.exports = Peer; diff --git a/lib/bcoin/pool.js b/lib/bcoin/pool.js index 8c5fd119..f538a7f6 100644 --- a/lib/bcoin/pool.js +++ b/lib/bcoin/pool.js @@ -29,10 +29,12 @@ function Pool(options) { this.size = options.size || 32; this.parallel = options.parallel || 2000; this.redundancy = options.redundancy || 2; + this.backoff = { delta: options.backoffDelta || 500, max: options.backoffMax || 5000 }; + this.load = { timeout: options.loadTimeout || 3000, interval: options.loadInterval || 5000, @@ -44,8 +46,10 @@ function Pool(options) { hwm: options.hwm || this.parallel * 8, hiReached: false }; + this.maxRetries = options.maxRetries || 42; this.requestTimeout = options.requestTimeout || 10000; + this.chain = new bcoin.chain({ storage: this.storage, // Since regular blocks contain transactions and full merkle @@ -54,10 +58,14 @@ function Pool(options) { fullNode: this.options.fullNode, startHeight: this.options.startHeight }); + this.watchMap = {}; - this.bloom = new bcoin.bloom(8 * 1024, - 10, - (Math.random() * 0xffffffff) | 0); + + this.bloom = new bcoin.bloom( + 8 * 1024, + 10, + (Math.random() * 0xffffffff) | 0 + ); this.bestHeight = 0; this.bestBlock = null; @@ -72,14 +80,17 @@ function Pool(options) { // Peers that are loading block ids load: null }; + this.block = { lastHash: null }; + this.request = { map: {}, active: 0, queue: [] }; + this.validate = { // 5 days scan delta for obtaining TXs delta: 5 * 24 * 3600, @@ -117,15 +128,18 @@ function Pool(options) { }); } } + inherits(Pool, EventEmitter); -module.exports = Pool; Pool.prototype._init = function _init() { var self = this; + var i; this._addLoader(); - for (var i = 0; i < this.size; i++) + + for (i = 0; i < this.size; i++) this._addPeer(0); + this._load(); this.chain.on('missing', function(hash, preload, parent) { @@ -141,19 +155,22 @@ Pool.prototype._init = function _init() { }; Pool.prototype._addLoader = function _addLoader() { + var self = this; + var peer, interval, timer; + if (this.destroyed) return; + if (this.peers.load !== null) return; - var peer = new bcoin.peer(this, this.createSocket, { + peer = new bcoin.peer(this, this.createSocket, { backoff: 750 * Math.random(), startHeight: this.options.startHeight, relay: this.options.relay }); this.peers.load = peer; - var self = this; peer.on('error', function(err) { self.emit('error', err, peer); }); @@ -168,7 +185,7 @@ Pool.prototype._addLoader = function _addLoader() { self._addLoader(); } - var interval = setInterval(function() { + interval = setInterval(function() { self._load(); }, this.load.interval); @@ -178,7 +195,8 @@ Pool.prototype._addLoader = function _addLoader() { clearTimeout(timer); }); - if (this.options.fullNode) return; + if (this.options.fullNode) + return; function destroy() { // Chain is full and up-to-date @@ -191,7 +209,8 @@ Pool.prototype._addLoader = function _addLoader() { peer.destroy(); } - var timer = setTimeout(destroy, this.load.timeout); + + timer = setTimeout(destroy, this.load.timeout); // Split blocks and request them using multiple peers peer.on('blocks', function(hashes) { @@ -236,7 +255,11 @@ Pool.prototype.isFull = function isFull() { }; Pool.prototype._loadRange = function _loadRange(hashes, force) { - if (this.options.fullNode) return; + var now = +new Date(); + var last; + + if (this.options.fullNode) + return; if (!hashes) return; @@ -245,29 +268,33 @@ Pool.prototype._loadRange = function _loadRange(hashes, force) { return; // Limit number of requests - var now = +new Date(); if (!force && now - this.load.lastRange < this.load.rangeWindow) return; + this.load.lastRange = now; if (!this.peers.load) this._addLoader(); - var last = hashes[hashes.length - 1]; + last = hashes[hashes.length - 1]; + hashes.slice(0, -1).forEach(function(hash) { this.peers.load.loadBlocks([ hash ], last); }, this); }; Pool.prototype._load = function _load() { - if (this.options.fullNode) return; + var self = this; + + if (this.options.fullNode) + return; if (this.request.queue.length >= this.load.hwm) { this.load.hiReached = true; return false; } + this.load.hiReached = false; - var self = this; // Load more blocks, starting from last hash if (this.block.lastHash) @@ -286,12 +313,16 @@ Pool.prototype._load = function _load() { }; Pool.prototype._addPeer = function _addPeer(backoff) { + var self = this; + var peer; + if (this.destroyed) return; + if (this.peers.block.length + this.peers.pending.length >= this.size) return; - var peer = new bcoin.peer(this, this.createSocket, { + peer = new bcoin.peer(this, this.createSocket, { backoff: backoff, startHeight: this.options.startHeight, relay: this.options.relay @@ -302,7 +333,6 @@ Pool.prototype._addPeer = function _addPeer(backoff) { peer._retry = 0; // Create new peer on failure - var self = this; peer.on('error', function(err) { self.emit('error', err, peer); }); @@ -315,10 +345,12 @@ Pool.prototype._addPeer = function _addPeer(backoff) { }); peer.once('ack', function() { + var i; + if (self.destroyed) return; - var i = self.peers.pending.indexOf(peer); + i = self.peers.pending.indexOf(peer); if (i !== -1) { self.peers.pending.splice(i, 1); self.peers.block.push(peer); @@ -349,6 +381,9 @@ Pool.prototype._addPeer = function _addPeer(backoff) { }); } else { peer.on('block', function(block) { + var hashes = self.chain.index.hashes; + var hash, len, orphan, err; + if (self.syncPeer !== peer) return; @@ -356,11 +391,11 @@ Pool.prototype._addPeer = function _addPeer(backoff) { self._response(block); - var hash = block.hash('hex'); - var len = self.chain.index.hashes.length; - var orphan = self.chain.hasOrphan(block); + hash = block.hash('hex'); + len = hashes.length; + orphan = self.chain.hasOrphan(block); - var err = self.chain.add(block); + err = self.chain.add(block); if (err) self.emit('chain-error', err, peer); @@ -373,12 +408,10 @@ Pool.prototype._addPeer = function _addPeer(backoff) { return; } - if (self.chain.index.hashes.length === len) + if (hashes.length === len) return; - var top = self.chain.index.hashes[self.chain.index.hashes.length - 1]; - - self.needSync = top !== self.bestBlock; + self.needSync = hashes[hashes.length - 1] !== self.bestBlock; self.emit('chain-progress', self.chain.fillPercent(), peer); self.emit('block', block, peer); @@ -386,9 +419,9 @@ Pool.prototype._addPeer = function _addPeer(backoff) { } this.chain.on('fork', function(height, hash, checkpoint) { - if (!self.syncPeer) - return; var peer = self.syncPeer; + if (!peer) + return; delete self.syncPeer; peer.destroy(); self.startSync(); @@ -450,6 +483,7 @@ Pool.prototype.bestPeer = function bestPeer() { this.peers.block.forEach(function(peer) { if (!peer.version || !peer.socket) return; + if (!best || peer.version.height > best.version.height) best = peer; }); @@ -495,13 +529,15 @@ Pool.prototype._removePeer = function _removePeer(peer) { }; Pool.prototype.watch = function watch(id) { + var hid, i; + if (id instanceof bcoin.wallet) { this.watchWallet(id); return; } if (id) { - var hid = utils.toHex(id); + hid = utils.toHex(id); if (this.watchMap[hid]) this.watchMap[hid]++; else @@ -515,11 +551,14 @@ Pool.prototype.watch = function watch(id) { if (this.peers.load) this.peers.load.updateWatch(); - for (var i = 0; i < this.peers.block.length; i++) + + for (i = 0; i < this.peers.block.length; i++) this.peers.block[i].updateWatch(); }; Pool.prototype.unwatch = function unwatch(id) { + var i; + if (!this.bloom.test(id, 'hex')) return; @@ -538,17 +577,21 @@ Pool.prototype.unwatch = function unwatch(id) { // Resend it to peers if (this.peers.load) this.peers.load.updateWatch(); - for (var i = 0; i < this.peers.block.length; i++) + for (i = 0; i < this.peers.block.length; i++) this.peers.block[i].updateWatch(); }; Pool.prototype.addWallet = function addWallet(w, defaultTs) { + var self = this; + var e; + if (this.wallets.indexOf(w) !== -1) return false; + this.watchWallet(w); - var self = this; - var e = new EventEmitter(); + e = new EventEmitter(); + if (w.loaded) search(w.lastTs); else @@ -606,16 +649,19 @@ Pool.prototype.unwatchWallet = function unwatchWallet(w) { }; Pool.prototype.search = function search(id, range, e) { + var self = this; + e = e || new EventEmitter(); // Optional id argument - if (id !== null && - typeof id === 'object' && - !Array.isArray(id) || - typeof id === 'number') { + if (id !== null + && typeof id === 'object' + && !Array.isArray(id) + || typeof id === 'number') { range = id; id = null; } + if (typeof id === 'string') id = utils.toArray(id, 'hex'); @@ -633,7 +679,6 @@ Pool.prototype.search = function search(id, range, e) { if (!range.start) range.start = +new Date() / 1000 - 432000; - var self = this; this.chain.hashesInRange(range.start, range.end, function(hashes, count) { var waiting = count; @@ -691,6 +736,8 @@ Pool.prototype.search = function search(id, range, e) { }; Pool.prototype._request = function _request(type, hash, options, cb) { + var self = this; + // Optional `force` if (typeof options === 'function') { cb = options; @@ -703,8 +750,6 @@ Pool.prototype._request = function _request(type, hash, options, cb) { if (this.request.map[hash]) return this.request.map[hash].addCallback(cb); - var self = this; - // Block should be not in chain, or be requested if (!options.force && type === 'block') return this.chain.has(hash, true, next); @@ -732,8 +777,11 @@ Pool.prototype._response = function _response(entity) { }; Pool.prototype._scheduleRequests = function _scheduleRequests() { + var self = this; + if (this.destroyed) return; + if (this.request.active > this.parallel / 2) return; @@ -749,7 +797,6 @@ Pool.prototype._scheduleRequests = function _scheduleRequests() { if (this.load.timer !== null) return; - var self = this; this.load.timer = setTimeout(function() { self.load.timer = null; self._doRequests(); @@ -757,6 +804,9 @@ Pool.prototype._scheduleRequests = function _scheduleRequests() { }; Pool.prototype._doRequests = function _doRequests() { + var queue, above, items, below; + var red, count, split, i, off, req, j; + if (this.request.active >= this.parallel) return; @@ -764,11 +814,11 @@ Pool.prototype._doRequests = function _doRequests() { if (this.peers.block.length === 0) return; - var queue = this.request.queue; - var above = queue.length >= this.load.lwm; - var items = queue.slice(0, this.parallel - this.request.active); + queue = this.request.queue; + above = queue.length >= this.load.lwm; + items = queue.slice(0, this.parallel - this.request.active); this.request.queue = queue.slice(items.length); - var below = this.request.queue.length < this.load.lwm; + below = this.request.queue.length < this.load.lwm; // Watermark boundary crossed, load more blocks if (above && below && this.load.hiReached) @@ -779,19 +829,20 @@ Pool.prototype._doRequests = function _doRequests() { } // Split list between peers - var red = this.redundancy; - var count = this.peers.block.length; - var split = Math.ceil(items.length * red / count); - for (var i = 0, off = 0; i < count; i += red, off += split) { - var req = items.slice(off, off + split).map(mapReq, this); - - for (var j = 0; j < red && i + j < count; j++) { + red = this.redundancy; + count = this.peers.block.length; + split = Math.ceil(items.length * red / count); + for (i = 0, off = 0; i < count; i += red, off += split) { + req = items.slice(off, off + split).map(mapReq, this); + for (j = 0; j < red && i + j < count; j++) this.peers.block[i + j].getData(req); - } } }; Pool.prototype.getTX = function getTX(hash, range, cb) { + var self = this; + var cbs, tx, finished, req, delta; + hash = utils.toHex(hash); if (typeof range === 'function') { @@ -802,19 +853,20 @@ Pool.prototype.getTX = function getTX(hash, range, cb) { // Do not perform duplicate searches if (this.validate.map[hash]) return this.validate.map[hash].push(cb); - var cbs = [ cb ]; + + cbs = [ cb ]; this.validate.map[hash] = cbs; // Add request without queueing it to get notification at the time of load - var tx = null; - var finished = false; - var req = this._request('tx', hash, { noQueue: true }, function(t) { + tx = null; + finished = false; + req = this._request('tx', hash, { noQueue: true }, function(t) { finished = true; tx = t; }); // Do incremental search until the TX is found - var delta = this.validate.delta; + delta = this.validate.delta; // Start from the existing range if given if (range) @@ -822,9 +874,6 @@ Pool.prototype.getTX = function getTX(hash, range, cb) { else range = { start: (+new Date() / 1000) - delta, end: 0 }; - var self = this; - doSearch(); - function doSearch() { var e = self.search(hash, range); e.on('end', function(empty) { @@ -849,12 +898,14 @@ Pool.prototype.getTX = function getTX(hash, range, cb) { doSearch(); }); } + + doSearch(); }; Pool.prototype.sendTX = function sendTX(tx) { + var self = this; var e = new EventEmitter(); - var self = this; var entry = { tx: tx, e: e, @@ -864,6 +915,7 @@ Pool.prototype.sendTX = function sendTX(tx) { self.tx.list.splice(i, 1); }, this.tx.timeout) }; + this.tx.list.push(entry); this.peers.block.forEach(function(peer) { @@ -880,6 +932,7 @@ Pool.prototype.sendTX = function sendTX(tx) { Pool.prototype.destroy = function destroy() { if (this.destroyed) return; + this.destroyed = true; if (this.peers.load) @@ -888,18 +941,23 @@ Pool.prototype.destroy = function destroy() { this.request.queue.slice().forEach(function(item) { item.finish(null); }); + this.tx.list.forEach(function(tx) { clearTimeout(tx.timer); tx.timer = null; }); + this.peers.pending.slice().forEach(function(peer) { peer.destroy(); }); + this.peers.block.slice().forEach(function(peer) { peer.destroy(); }); + if (this.load.timer) clearTimeout(this.load.timer); + this.load.timer = null; }; @@ -920,6 +978,8 @@ Pool.prototype.fromJSON = function fromJSON(json) { }; function LoadRequest(pool, type, hash, cb) { + var self = this; + this.pool = pool; this.type = type; this.hash = hash; @@ -930,7 +990,6 @@ function LoadRequest(pool, type, hash, cb) { this.active = false; this.noQueue = false; - var self = this; this.onclose = function onclose() { if (self.pool.destroyed) self.clear(); @@ -941,8 +1000,9 @@ function LoadRequest(pool, type, hash, cb) { LoadRequest.prototype.start = function start(peer) { var self = this; - assert(!this.active); + var reqType; + assert(!this.active); this.active = true; this.pool.request.active++; @@ -956,7 +1016,6 @@ LoadRequest.prototype.start = function start(peer) { this.peer = peer; this.peer.once('close', this.onclose); - var reqType; if (this.type === 'block') reqType = 'filtered'; else if (this.type === 'tx') @@ -991,9 +1050,11 @@ LoadRequest.prototype.clear = function clear() { }; LoadRequest.prototype.retry = function retry() { + var peer = this.peer; + // Put block into the queue, ensure that the queue is always sorted by ts utils.binaryInsert(this.pool.request.queue, this, LoadRequest.compare); - var peer = this.peer; + this.clear(); // Kill peer, if it misbehaves @@ -1005,12 +1066,14 @@ LoadRequest.prototype.retry = function retry() { }; LoadRequest.prototype.finish = function finish(entity) { + var index; + if (this.active) { this.clear(); } else { // It could be that request was never sent to the node, remove it from // queue and forget about it - var index = this.pool.request.queue.indexOf(this); + index = this.pool.request.queue.indexOf(this); assert(index !== -1 || this.noQueue); if (!this.noQueue) this.pool.request.queue.splice(index, 1); @@ -1031,3 +1094,5 @@ LoadRequest.prototype.addCallback = function addCallback(cb) { if (cb) this.cbs.push(cb); }; + +module.exports = Pool; diff --git a/lib/bcoin/protocol/constants.js b/lib/bcoin/protocol/constants.js index d2531113..a2e80272 100644 --- a/lib/bcoin/protocol/constants.js +++ b/lib/bcoin/protocol/constants.js @@ -1,6 +1,8 @@ var bcoin = require('../../bcoin'); var utils = bcoin.utils; +var i; + exports.minVersion = 70001; exports.version = 70002; @@ -119,10 +121,11 @@ exports.opcodes = { }; exports.opcodes['-1'] = 0x50 + -1; -for (var i = 1; i <= 16; i++) + +for (i = 1; i <= 16; i++) exports.opcodes[i] = 0x50 + i; -for (var i = 0; i <= 7; i++) +for (i = 0; i <= 7; i++) exports.opcodes['nop' + (i + 3)] = 0xb2 + i; exports.opcodesByVal = new Array(256); diff --git a/lib/bcoin/protocol/framer.js b/lib/bcoin/protocol/framer.js index 12c4d0b7..7a55d6b1 100644 --- a/lib/bcoin/protocol/framer.js +++ b/lib/bcoin/protocol/framer.js @@ -19,20 +19,20 @@ function Framer(options) { this.agent = utils.toArray(options.agent || '/bcoin:' + version + '/'); this.agent = this.agent.slice(0, 0xfc); } -module.exports = Framer; Framer.prototype.header = function header(cmd, payload) { + var h = new Array(24); + var len, i; + assert(cmd.length < 12); assert(payload.length <= 0xffffffff); - var h = new Array(24); - // Magic value writeU32(h, network.magic, 0); // Command - var len = writeAscii(h, cmd, 4); - for (var i = 4 + len; i < 4 + 12; i++) + len = writeAscii(h, cmd, 4); + for (i = 4 + len; i < 4 + 12; i++) h[i] = 0; // Payload length @@ -64,6 +64,7 @@ Framer.prototype._addr = function addr(buf, off) { Framer.prototype.version = function version(packet) { var p = new Array(86 + this.agent.length); var off = 0; + var ts, i; if (!packet) packet = {}; @@ -76,7 +77,7 @@ Framer.prototype.version = function version(packet) { off += writeU32(p, 0, off); // Timestamp - var ts = ((+new Date()) / 1000) | 0; + ts = ((+new Date()) / 1000) | 0; off += writeU32(p, ts, off); off += writeU32(p, 0, off); @@ -94,7 +95,7 @@ Framer.prototype.version = function version(packet) { p[off++] = 0; } else { off += varint(p, this.agent.length, off); - for (var i = 0; i < this.agent.length; i++) + for (i = 0; i < this.agent.length; i++) p[off++] = this.agent[i]; } @@ -139,14 +140,16 @@ function varint(arr, value, off) { Framer.prototype._inv = function _inv(command, items) { var res = []; var off = varint(res, items.length, 0); + var i, hash; + assert(items.length <= 50000); - for (var i = 0; i < items.length; i++) { + for (i = 0; i < items.length; i++) { // Type off += writeU32(res, constants.inv[items[i].type], off); // Hash - var hash = items[i].hash; + hash = items[i].hash; if (typeof hash === 'string') hash = utils.toArray(hash, 'hex'); assert.equal(hash.length, 32); @@ -181,10 +184,10 @@ Framer.prototype.pong = function pong(nonce) { Framer.prototype.filterLoad = function filterLoad(bloom, update) { var filter = bloom.toArray(); var before = []; - varint(before, filter.length, 0); - var after = new Array(9); + varint(before, filter.length, 0); + // Number of hash functions writeU32(after, bloom.n, 0); @@ -194,8 +197,7 @@ Framer.prototype.filterLoad = function filterLoad(bloom, update) { // nFlags after[8] = constants.filterFlags[update]; - var r = this.packet('filterload', before.concat(filter, after)); - return r; + return this.packet('filterload', before.concat(filter, after)); }; Framer.prototype.filterClear = function filterClear() { @@ -212,28 +214,36 @@ Framer.prototype.getBlocks = function getBlocks(hashes, stop) { Framer.prototype._getBlocks = function _getBlocks(cmd, hashes, stop) { var p = []; + var off, i, hash, len; + writeU32(p, constants.version, 0); - var off = 4 + varint(p, hashes.length, 4); + off = 4 + varint(p, hashes.length, 4); p.length = off + 32 * (hashes.length + 1); - for (var i = 0; i < hashes.length; i++) { - var hash = hashes[i]; + for (i = 0; i < hashes.length; i++) { + hash = hashes[i]; + if (typeof hash === 'string') hash = utils.toArray(hash, 'hex'); - var len = utils.copy(hash, p, off); + + len = utils.copy(hash, p, off); + for (; len < 32; len++) p[off + len] = 0; + off += len; } if (stop) { stop = utils.toArray(stop, 'hex'); - var len = utils.copy(stop, p, off); + len = utils.copy(stop, p, off); } else { - var len = 0; + len = 0; } + for (; len < 32; len++) p[off + len] = 0; + assert.equal(off + len, p.length); return this.packet(cmd, p); @@ -241,16 +251,18 @@ Framer.prototype._getBlocks = function _getBlocks(cmd, hashes, stop) { Framer.tx = function tx(tx) { var p = []; - var off = writeU32(p, tx.version, 0); + var off, i, input, s, output, value, j; + + off = writeU32(p, tx.version, 0); off += varint(p, tx.inputs.length, off); - for (var i = 0; i < tx.inputs.length; i++) { - var input = tx.inputs[i]; + for (i = 0; i < tx.inputs.length; i++) { + input = tx.inputs[i]; off += utils.copy(utils.toArray(input.out.hash, 'hex'), p, off, true); off += writeU32(p, input.out.index, off); - var s = bcoin.script.encode(input.script); + s = bcoin.script.encode(input.script); off += varint(p, s.length, off); off += utils.copy(s, p, off, true); @@ -258,17 +270,19 @@ Framer.tx = function tx(tx) { } off += varint(p, tx.outputs.length, off); - for (var i = 0; i < tx.outputs.length; i++) { - var output = tx.outputs[i]; + for (i = 0; i < tx.outputs.length; i++) { + output = tx.outputs[i]; // Put LE value - var value = output.value.toArray().slice().reverse(); + value = output.value.toArray().slice().reverse(); assert(value.length <= 8); + off += utils.copy(value, p, off, true); - for (var j = value.length; j < 8; j++, off++) + + for (j = value.length; j < 8; j++, off++) p[off] = 0; - var s = bcoin.script.encode(output.script); + s = bcoin.script.encode(output.script); off += varint(p, s.length, off); off += utils.copy(s, p, off, true); } @@ -356,15 +370,15 @@ Framer.prototype.merkleBlock = function merkleBlock(block) { Framer.prototype.addr = function addr(peers) { var p = []; - var i = 0; var off = 0; var peer; var start = (Date.now() / 1000 | 0) - process.uptime(); + var i; // count off += varint(p, peers.length, off); - for (; i < peers.length; i++) { + for (i = 0; i < peers.length; i++) { peer = peers[i]; // timestamp @@ -387,3 +401,5 @@ Framer.prototype.addr = function addr(peers) { return this.packet('addr', p); }; + +module.exports = Framer; diff --git a/lib/bcoin/protocol/network.js b/lib/bcoin/protocol/network.js index 07028c68..2e238f54 100644 --- a/lib/bcoin/protocol/network.js +++ b/lib/bcoin/protocol/network.js @@ -6,6 +6,7 @@ var utils = bcoin.utils; */ var network = exports; +var main, testnet; network.set = function(type) { var net = network[type]; @@ -16,7 +17,7 @@ network.set = function(type) { * Main */ -var main = network.main = {}; +main = network.main = {}; main.prefixes = { pubkey: 0, @@ -103,7 +104,7 @@ main.preload = require('./preload'); * https://en.bitcoin.it/wiki/Testnet */ -var testnet = network.testnet = {}; +testnet = network.testnet = {}; testnet.type = 'testnet'; diff --git a/lib/bcoin/protocol/parser.js b/lib/bcoin/protocol/parser.js index ebc58731..5bed0ee3 100644 --- a/lib/bcoin/protocol/parser.js +++ b/lib/bcoin/protocol/parser.js @@ -22,20 +22,28 @@ function Parser() { this.waiting = 24; this.packet = null; } + inherits(Parser, EventEmitter); -module.exports = Parser; Parser.prototype._error = function _error(str) { this.emit('error', new Error(str)); }; Parser.prototype.feed = function feed(data) { + var chunk, i, off, len; + this.pendingTotal += data.length; this.pending.push(data); + while (this.pendingTotal >= this.waiting) { // Concat chunks - var chunk = new Array(this.waiting); - for (var i = 0, off = 0, len = 0; off < chunk.length; i++) { + chunk = new Array(this.waiting); + + i = 0; + off = 0; + len = 0; + + for (; off < chunk.length; i++) { len = utils.copy(this.pending[0], chunk, off); if (len === this.pending[0].length) this.pending.shift(); @@ -45,6 +53,7 @@ Parser.prototype.feed = function feed(data) { this.pending[0] = this.pending[0].slice(len); off += len; } + assert.equal(off, chunk.length); // Slice buffers @@ -56,32 +65,39 @@ Parser.prototype.feed = function feed(data) { Parser.prototype.parse = function parse(chunk) { if (this.packet === null) { this.packet = this.parseHeader(chunk) || {}; - } else { - this.packet.payload = chunk; - if (readU32(utils.checksum(this.packet.payload)) !== this.packet.checksum) - return this._error('Invalid checksum'); - this.packet.payload = this.parsePayload(this.packet.cmd, - this.packet.payload); - if (this.packet.payload) - this.emit('packet', this.packet); - - this.waiting = 24; - this.packet = null; + return; } + + this.packet.payload = chunk; + + if (readU32(utils.checksum(this.packet.payload)) !== this.packet.checksum) + return this._error('Invalid checksum'); + + this.packet.payload = this.parsePayload(this.packet.cmd, + this.packet.payload); + if (this.packet.payload) + this.emit('packet', this.packet); + + this.waiting = 24; + this.packet = null; }; Parser.prototype.parseHeader = function parseHeader(h) { - var magic = readU32(h, 0); + var i, magic, cmd; + + magic = readU32(h, 0); + if (magic !== network.magic) { return this._error('Invalid magic value: ' + magic.toString(16)); } // Count length of the cmd - for (var i = 0; h[i + 4] !== 0 && i < 12; i++); + for (i = 0; h[i + 4] !== 0 && i < 12; i++); + if (i === 12) return this._error('Not NULL-terminated cmd'); - var cmd = utils.stringify(h.slice(4, 4 + i)); + cmd = utils.stringify(h.slice(4, 4 + i)); this.waiting = readU32(h, 16); return { @@ -113,30 +129,32 @@ Parser.prototype.parsePayload = function parsePayload(cmd, p) { }; Parser.prototype.parseVersion = function parseVersion(p) { + var v, services, ts, nonce, result, off, agent, height, relay; + if (p.length < 85) return this._error('version packet is too small'); - var v = readU32(p, 0); - var services = readU64(p, 4); + v = readU32(p, 0); + services = readU64(p, 4); // Timestamp - var ts = readU64(p, 12); + ts = readU64(p, 12); // Nonce, very dramatic - var nonce = { lo: readU32(p, 72), hi: readU32(p, 76) }; + nonce = { lo: readU32(p, 72), hi: readU32(p, 76) }; // User agent length - var result = readIntv(p, 80); - var off = result.off; - var agent = p.slice(off, off + result.r); + result = readIntv(p, 80); + off = result.off; + agent = p.slice(off, off + result.r); off += result.r; // Start height - var height = readU32(p, off); + height = readU32(p, off); off += 4; // Relay - var relay = p.length > off ? p[off] === 1 : true; + relay = p.length > off ? p[off] === 1 : true; return { v: v, @@ -150,10 +168,11 @@ Parser.prototype.parseVersion = function parseVersion(p) { }; function readIntv(p, off) { + var r, bytes; + if (!off) off = 0; - var r, bytes; if (p[off] < 0xfd) { r = p[off]; bytes = 1; @@ -172,45 +191,54 @@ function readIntv(p, off) { } Parser.prototype.parseInvList = function parseInvList(p) { - var count = readIntv(p, 0); + var items = []; + var off = 0; + var i, count; + + count = readIntv(p, 0); p = p.slice(count.off); count = count.r; + if (p.length < count * 36) return this._error('Invalid getdata size'); - var items = []; - for (var i = 0, off = 0; i < count; i++, off += 36) { + for (i = 0; i < count; i++, off += 36) { items.push({ type: constants.invByVal[readU32(p, off)], hash: p.slice(off + 4, off + 36) }); } + return items; }; Parser.prototype.parseMerkleBlock = function parseMerkleBlock(p) { + var i, hashCount, off, hashes, flagCount, flags; + if (p.length < 86) return this._error('Invalid merkleblock size'); - var hashCount = readIntv(p, 84); - var off = hashCount.off; + hashCount = readIntv(p, 84); + off = hashCount.off; hashCount = hashCount.r; + if (off + 32 * hashCount + 1 > p.length) return this._error('Invalid hash count'); - var hashes = new Array(hashCount); - for (var i = 0; i < hashCount; i++) + hashes = new Array(hashCount); + + for (i = 0; i < hashCount; i++) hashes[i] = p.slice(off + i * 32, off + (i + 1) * 32); off = off + 32 * hashCount; - var flagCount = readIntv(p, off); + flagCount = readIntv(p, off); off = flagCount.off; flagCount = flagCount.r; if (off + flagCount > p.length) return this._error('Invalid flag count'); - var flags = p.slice(off, off + flagCount); + flags = p.slice(off, off + flagCount); return { version: readU32(p, 0), @@ -228,19 +256,20 @@ Parser.prototype.parseMerkleBlock = function parseMerkleBlock(p) { }; Parser.prototype.parseHeaders = function parseHeaders(p) { + var headers = []; + var i, result, off, count, header, start, r; + if (p.length < 81) return this._error('Invalid headers size'); - var result = readIntv(p, 0); - var off = result.off; - var count = result.r; - - var headers = []; + result = readIntv(p, 0); + off = result.off; + count = result.r; if (p.length >= off + 81) { - for (var i = 0; i < count && off + 81 < p.length; i++) { - var header = {}; - var start = off; + for (i = 0; i < count && off + 81 < p.length; i++) { + header = {}; + start = off; header.version = readU32(p, off); off += 4; header.prevBlock = p.slice(off, off + 32); @@ -253,7 +282,7 @@ Parser.prototype.parseHeaders = function parseHeaders(p) { off += 4; header.nonce = readU32(p, off); off += 4; - var r = readIntv(p, off); + r = readIntv(p, off); header.totalTX = r.r; off = r.off; header._raw = p.slice(start, start + 80); @@ -265,17 +294,19 @@ Parser.prototype.parseHeaders = function parseHeaders(p) { }; Parser.prototype.parseBlock = function parseBlock(p) { + var txs = []; + var i, result, off, totalTX, tx; + if (p.length < 81) return this._error('Invalid block size'); - var result = readIntv(p, 80); - var off = result.off; - var totalTX = result.r; - var txs = []; + result = readIntv(p, 80); + off = result.off; + totalTX = result.r; if (p.length >= off + 10) { - for (var i = 0; i < totalTX; i++) { - var tx = this.parseTX(p.slice(off)); + for (i = 0; i < totalTX; i++) { + tx = this.parseTX(p.slice(off)); off += tx._off; txs.push(tx); } @@ -296,12 +327,15 @@ Parser.prototype.parseBlock = function parseBlock(p) { }; Parser.prototype.parseTXIn = function parseTXIn(p) { + var scriptLen, off; + if (p.length < 41) return this._error('Invalid tx_in size'); - var scriptLen = readIntv(p, 36); - var off = scriptLen.off; + scriptLen = readIntv(p, 36); + off = scriptLen.off; scriptLen = scriptLen.r; + if (off + scriptLen + 4 > p.length) return this._error('Invalid tx_in script length'); @@ -317,12 +351,15 @@ Parser.prototype.parseTXIn = function parseTXIn(p) { }; Parser.prototype.parseTXOut = function parseTXOut(p) { + var scriptLen, off; + if (p.length < 9) return this._error('Invalid tx_out size'); - var scriptLen = readIntv(p, 8); - var off = scriptLen.off; + scriptLen = readIntv(p, 8); + off = scriptLen.off; scriptLen = scriptLen.r; + if (off + scriptLen > p.length) return this._error('Invalid tx_out script length'); @@ -334,22 +371,30 @@ Parser.prototype.parseTXOut = function parseTXOut(p) { }; Parser.prototype.parseTX = function parseTX(p) { + var inCount, off, txIn, tx; + var outCount, off, txOut; + var i; + if (p.length < 10) return this._error('Invalid tx size'); - var inCount = readIntv(p, 4); - var off = inCount.off; + inCount = readIntv(p, 4); + off = inCount.off; inCount = inCount.r; + if (inCount < 0) return this._error('Invalid tx_in count (negative)'); + if (off + 41 * inCount + 5 > p.length) return this._error('Invalid tx_in count (too big)'); - var txIn = new Array(inCount); - for (var i = 0; i < inCount; i++) { - var tx = this.parseTXIn(p.slice(off)); + txIn = new Array(inCount); + for (i = 0; i < inCount; i++) { + tx = this.parseTXIn(p.slice(off)); + if (!tx) return; + txIn[i] = tx; off += tx.size; @@ -357,19 +402,21 @@ Parser.prototype.parseTX = function parseTX(p) { return this._error('Invalid tx_in offset'); } - var outCount = readIntv(p, off); - var off = outCount.off; + outCount = readIntv(p, off); + off = outCount.off; outCount = outCount.r; if (outCount < 0) return this._error('Invalid tx_out count (negative)'); if (off + 9 * outCount + 4 > p.length) return this._error('Invalid tx_out count (too big)'); - var txOut = new Array(outCount); - for (var i = 0; i < outCount; i++) { - var tx = this.parseTXOut(p.slice(off)); + txOut = new Array(outCount); + for (i = 0; i < outCount; i++) { + tx = this.parseTXOut(p.slice(off)); + if (!tx) return; + txOut[i] = tx; off += tx.size; @@ -389,32 +436,36 @@ Parser.prototype.parseTX = function parseTX(p) { }; Parser.prototype.parseReject = function parseReject(p) { + var messageLen, off, message, ccode, reasonLen, reason, data; + if (p.length < 3) return this._error('Invalid reject size'); - var messageLen = readIntv(p, 0); - var off = messageLen.off; + messageLen = readIntv(p, 0); + off = messageLen.off; messageLen = messageLen.r; + if (off + messageLen + 2 > p.length) return this._error('Invalid reject message'); - var message = utils.stringify(p.slice(off, off + messageLen)); + message = utils.stringify(p.slice(off, off + messageLen)); off += messageLen; - var ccode = p[off]; + ccode = p[off]; off++; - var reasonLen = readIntv(p, off); + reasonLen = readIntv(p, off); off = reasonLen.off; reasonLen = reasonLen.r; + if (off + reasonLen > p.length) return this._error('Invalid reject reason'); - var reason = utils.stringify(p.slice(off, off + reasonLen)); + reason = utils.stringify(p.slice(off, off + reasonLen)); off += reasonLen; - var data = p.slice(off, off + 32); + data = p.slice(off, off + 32); return { message: message, @@ -429,34 +480,35 @@ Parser.prototype.parseAddr = function parseAddr(p) { return this._error('Invalid addr size'); var addrs = []; + var i, len, off, count, ts, service, ipv6, ipv4, port; // count - var len = readIntv(p, 0); - var off = len.off; - var count = len.r; + len = readIntv(p, 0); + off = len.off; + count = len.r; p = p.slice(off); - for (var i = 0; i < count; i++) { + for (i = 0; i < count; i++) { // timestamp - LE - var ts = utils.readU32(p, 0); + ts = utils.readU32(p, 0); // NODE_NETWORK service - LE - var service = utils.readU64(p, 4); + service = utils.readU64(p, 4); // ipv6 - BE - var ipv6 = utils.toHex(p.slice(12, 24)); + ipv6 = utils.toHex(p.slice(12, 24)); ipv6 = '::' + ipv6.replace(/(.{4})/g, '$1:').slice(0, -1); // ipv4 - BE - var ipv4 = utils.readU32BE(p, 24); - ipv4 = ((ipv4 >> 24) & 0xff) + '.' + - ((ipv4 >> 16) & 0xff) + '.' + - ((ipv4 >> 8) & 0xff) + '.' + - ((ipv4 >> 0) & 0xff); + ipv4 = utils.readU32BE(p, 24); + ipv4 = ((ipv4 >> 24) & 0xff) + + '.' + ((ipv4 >> 16) & 0xff) + + '.' + ((ipv4 >> 8) & 0xff) + + '.' + ((ipv4 >> 0) & 0xff); // port - BE - var port = utils.readU16BE(p, 28); + port = utils.readU16BE(p, 28); addrs.push({ ts: ts, @@ -471,3 +523,5 @@ Parser.prototype.parseAddr = function parseAddr(p) { return addrs; }; + +module.exports = Parser; diff --git a/lib/bcoin/script.js b/lib/bcoin/script.js index 5a6fa6b0..34057745 100644 --- a/lib/bcoin/script.js +++ b/lib/bcoin/script.js @@ -8,9 +8,13 @@ var script = exports; script.decode = function decode(s) { if (!s) return []; + var opcodes = []; - for (var i = 0; i < s.length;) { - var b = s[i++]; + var i = 0; + var b, opcode, len; + + while (i < s.length) { + b = s[i++]; // Next `b` bytes should be pushed to stack if (b >= 0x01 && b <= 0x4b) { @@ -35,7 +39,7 @@ script.decode = function decode(s) { continue; } - var opcode = constants.opcodesByVal[b]; + opcode = constants.opcodesByVal[b]; if (i >= s.length) { opcodes.push(opcode || b); @@ -43,7 +47,7 @@ script.decode = function decode(s) { } if (opcode === 'pushdata1') { - var len = s[i]; + len = s[i]; i += 1; opcodes.push(s.slice(i, i + len)); i += len; @@ -52,7 +56,7 @@ script.decode = function decode(s) { len: len }); } else if (opcode === 'pushdata2') { - var len = utils.readU16(s, i); + len = utils.readU16(s, i); i += 2; opcodes.push(s.slice(i, i + len)); i += len; @@ -61,7 +65,7 @@ script.decode = function decode(s) { len: len }); } else if (opcode === 'pushdata4') { - var len = utils.readU32(s, i); + len = utils.readU32(s, i); i += 4; opcodes.push(s.slice(i, i + len)); i += len; @@ -85,8 +89,11 @@ script.encode = function encode(s) { var opcodes = constants.opcodes; var res = []; - for (var i = 0; i < s.length; i++) { - var instr = s[i]; + var i = 0; + var instr; + + for (i = 0; i < s.length; i++) { + instr = s[i]; // Push value to stack if (Array.isArray(instr)) { @@ -134,27 +141,30 @@ script.encode = function encode(s) { }; script.subscript = function subscript(s, lastSep) { + var i, res; + if (!s) return []; if (lastSep == null) { lastSep = -1; - for (var i = 0; i < s.length; i++) { + for (i = 0; i < s.length; i++) { if (s[i] === 'codesep') lastSep = i; - else if (s[i] === 'checksig' || - s[i] === 'checksigverify' || - s[i] === 'checkmultisig' || - s[i] === 'checkmultisigverify') { + else if (s[i] === 'checksig' + || s[i] === 'checksigverify' + || s[i] === 'checkmultisig' + || s[i] === 'checkmultisigverify') { break; } } } - var res = []; - for (var i = lastSep + 1; i < s.length; i++) + res = []; + for (i = lastSep + 1; i < s.length; i++) { if (s[i] !== 'codesep') res.push(s[i]); + } return res; }; @@ -179,8 +189,10 @@ script.verify = function verify(hash, sig, pub) { script._next = function(to, s, pc) { var depth = 0; + var o; + while (s[pc]) { - var o = s[pc]; + o = s[pc]; if (o === 'if_' || o === 'notif') depth++; else if (o === 'else_') @@ -195,6 +207,7 @@ script._next = function(to, s, pc) { depth++; pc++; } + return -1; }; @@ -205,11 +218,22 @@ script.execute = function execute(s, stack, tx, index, recurse) { return false; var lastSep = -1; + var pc = 0; + var o, val; + var if_, else_, endif; + var v, v1, v2, v3, v4; + var n, n1, n2, n3; + var res; + var pub, sig, type, subscript, hash; + var keys, i, key, m; + var succ; + var lock, threshold; + var evalScript; stack.alt = stack.alt || []; - for (var pc = 0; pc < s.length; pc++) { - var o = s[pc]; + for (pc = 0; pc < s.length; pc++) { + o = s[pc]; if (Array.isArray(o)) { if (o.length > constants.script.maxPush) @@ -237,16 +261,16 @@ script.execute = function execute(s, stack, tx, index, recurse) { } case 'if_': case 'notif': { - var val = false; + val = false; if (stack.length < 1) return false; - var v = stack.pop(); + v = stack.pop(); val = new bn(v).cmpn(0) !== 0; if (o === 'notif') val = !val; - var if_ = pc; - var else_ = script._next('else_', s, pc); - var endif = script._next('endif', s, pc); + if_ = pc; + else_ = script._next('else_', s, pc); + endif = script._next('endif', s, pc); // Splice out the statement blocks we don't need if (val) { if (endif === -1) @@ -339,10 +363,10 @@ script.execute = function execute(s, stack, tx, index, recurse) { case 'roll': { if (stack.length < 2) return false; - var n = new bn(stack.pop()).toNumber(); + n = new bn(stack.pop()).toNumber(); if (n < 0 || n >= stack.length) return false; - var v = stack[-n - 1]; + v = stack[-n - 1]; if (o === 'roll') stack.splice(stack.length - n - 1, 1); stack.push(v); @@ -351,9 +375,9 @@ script.execute = function execute(s, stack, tx, index, recurse) { case 'rot': { if (stack.length < 3) return false; - var v3 = stack[stack.length - 3]; - var v2 = stack[stack.length - 2]; - var v1 = stack[stack.length - 1]; + v3 = stack[stack.length - 3]; + v2 = stack[stack.length - 2]; + v1 = stack[stack.length - 1]; stack[stack.length - 3] = v2; stack[stack.length - 2] = v3; v2 = stack[stack.length - 2]; @@ -364,8 +388,8 @@ script.execute = function execute(s, stack, tx, index, recurse) { case 'swap': { if (stack.length < 2) return false; - var v2 = stack[stack.length - 2]; - var v1 = stack[stack.length - 1]; + v2 = stack[stack.length - 2]; + v1 = stack[stack.length - 1]; stack[stack.length - 2] = v1; stack[stack.length - 1] = v2; break; @@ -386,8 +410,8 @@ script.execute = function execute(s, stack, tx, index, recurse) { case 'dup2': { if (stack.length < 2) return false; - var v1 = stack[stack.length - 1]; - var v2 = stack[stack.length - 2]; + v1 = stack[stack.length - 1]; + v2 = stack[stack.length - 2]; stack.push(v1); stack.push(v2); break; @@ -395,9 +419,9 @@ script.execute = function execute(s, stack, tx, index, recurse) { case 'dup3': { if (stack.length < 3) return false; - var v1 = stack[stack.length - 1]; - var v2 = stack[stack.length - 2]; - var v3 = stack[stack.length - 3]; + v1 = stack[stack.length - 1]; + v2 = stack[stack.length - 2]; + v3 = stack[stack.length - 3]; stack.push(v1); stack.push(v2); stack.push(v3); @@ -406,8 +430,8 @@ script.execute = function execute(s, stack, tx, index, recurse) { case 'over2': { if (stack.length < 4) return false; - var v1 = stack[stack.length - 4]; - var v2 = stack[stack.length - 3]; + v1 = stack[stack.length - 4]; + v2 = stack[stack.length - 3]; stack.push(v1); stack.push(v2); break; @@ -415,8 +439,8 @@ script.execute = function execute(s, stack, tx, index, recurse) { case 'rot2': { if (stack.length < 6) return false; - var v1 = stack[stack.length - 6]; - var v2 = stack[stack.length - 5]; + v1 = stack[stack.length - 6]; + v2 = stack[stack.length - 5]; stack.splice(stack.length - 6, 2); stack.push(v1); stack.push(v2); @@ -425,10 +449,10 @@ script.execute = function execute(s, stack, tx, index, recurse) { case 'swap2': { if (stack.length < 4) return false; - var v4 = stack[stack.length - 4]; - var v3 = stack[stack.length - 3]; - var v2 = stack[stack.length - 2]; - var v1 = stack[stack.length - 1]; + v4 = stack[stack.length - 4]; + v3 = stack[stack.length - 3]; + v2 = stack[stack.length - 2]; + v1 = stack[stack.length - 1]; stack[stack.length - 4] = v2; stack[stack.length - 2] = v4; stack[stack.length - 3] = v1; @@ -449,7 +473,7 @@ script.execute = function execute(s, stack, tx, index, recurse) { case 'noteq0': { if (stack.length < 1) return false; - var n = new bn(stack.pop()); + n = new bn(stack.pop()); switch (o) { case 'add1': n.iadd(1); @@ -505,9 +529,9 @@ script.execute = function execute(s, stack, tx, index, recurse) { case 'max': if (stack.length < 2) return false; - var n2 = new bn(stack.pop()); - var n1 = new bn(stack.pop()); - var n = new bn(0); + n2 = new bn(stack.pop()); + n1 = new bn(stack.pop()); + n = new bn(0); switch (o) { case 'add': n = n1.add(b2); @@ -551,7 +575,7 @@ script.execute = function execute(s, stack, tx, index, recurse) { default: return false; } - var res = n.cmpn(0) !== 0; + res = n.cmpn(0) !== 0; if (o === 'numeqverify') { if (!res) return false; @@ -563,10 +587,10 @@ script.execute = function execute(s, stack, tx, index, recurse) { case 'within': if (stack.length < 3) return false; - var n3 = new bn(stack.pop()); - var n2 = new bn(stack.pop()); - var n1 = new bn(stack.pop()); - var val = n2.cmp(n1) <= 0 && n1.cmp(n3) < 0; + n3 = new bn(stack.pop()); + n2 = new bn(stack.pop()); + n1 = new bn(stack.pop()); + val = n2.cmp(n1) <= 0 && n1.cmp(n3) < 0; stack.push(val.cmpn(0) !== 0 ? [ 1 ] : []); break; } @@ -611,7 +635,7 @@ script.execute = function execute(s, stack, tx, index, recurse) { case 'eq': { if (stack.length < 2) return false; - var res = utils.isEqual(stack.pop(), stack.pop()); + res = utils.isEqual(stack.pop(), stack.pop()); if (o === 'eqverify') { if (!res) return false; @@ -625,19 +649,19 @@ script.execute = function execute(s, stack, tx, index, recurse) { if (!tx || stack.length < 2) return false; - var pub = stack.pop(); - var sig = stack.pop(); - var type = sig[sig.length - 1]; + pub = stack.pop(); + sig = stack.pop(); + type = sig[sig.length - 1]; if (!constants.rhashType[type & 0x1f]) return false; if (!script.isValidSig(sig)) return false; - var subscript = script.subscript(s, lastSep); - var hash = tx.subscriptHash(index, subscript, type); + subscript = script.subscript(s, lastSep); + hash = tx.subscriptHash(index, subscript, type); - var res = script.verify(hash, sig.slice(0, -1), pub); + res = script.verify(hash, sig.slice(0, -1), pub); if (o === 'checksigverify') { if (!res) return false; @@ -652,7 +676,7 @@ script.execute = function execute(s, stack, tx, index, recurse) { if (!tx || stack.length < 3) return false; - var n = stack.pop(); + n = stack.pop(); if (n.length !== 1 || !(1 <= n[0] && n[0] <= 15)) return false; n = n[0] || 0; @@ -660,16 +684,16 @@ script.execute = function execute(s, stack, tx, index, recurse) { if (stack.length < n + 1) return false; - var keys = []; - for (var i = 0; i < n; i++) { - var key = stack.pop(); + keys = []; + for (i = 0; i < n; i++) { + key = stack.pop(); if (!(33 <= key.length && key.length <= 65)) return false; keys.push(key); } - var m = stack.pop(); + m = stack.pop(); if (m.length !== 1 || !(1 <= m[0] && m[0] <= n)) return false; m = m[0] || 0; @@ -677,23 +701,23 @@ script.execute = function execute(s, stack, tx, index, recurse) { if (stack.length < m + 1) return false; - var subscript = script.subscript(s, lastSep); + subscript = script.subscript(s, lastSep); // Get signatures - var succ = 0; - for (var i = 0; i < m; i++) { - var sig = stack.pop(); - var type = sig[sig.length - 1]; + succ = 0; + for (i = 0; i < m; i++) { + sig = stack.pop(); + type = sig[sig.length - 1]; if (!constants.rhashType[type & 0x1f]) return false; if (!script.isValidSig(sig)) return false; - var hash = tx.subscriptHash(index, subscript, type); + hash = tx.subscriptHash(index, subscript, type); // Strict order: - var res = script.verify(hash, sig.slice(0, -1), keys.pop()); + res = script.verify(hash, sig.slice(0, -1), keys.pop()); if (res) succ++; } @@ -701,7 +725,7 @@ script.execute = function execute(s, stack, tx, index, recurse) { // Extra value stack.pop(); - var res = succ >= m; + res = succ >= m; if (o === 'checkmultisigverify') { if (!res) return false; @@ -716,15 +740,15 @@ script.execute = function execute(s, stack, tx, index, recurse) { if (!tx || stack.length === 0) return false; - var lock = new bn(stack[stack.length - 1]).toNumber(); + lock = new bn(stack[stack.length - 1]).toNumber(); if (lock < 0) return false; - var threshold = constants.locktimeThreshold; + threshold = constants.locktimeThreshold; if (!( - (tx.lock < threshold && lock < threshold) || - (tx.lock >= threshold && lock >= threshold) + (tx.lock < threshold && lock < threshold) + || (tx.lock >= threshold && lock >= threshold) )) { return false; } @@ -747,15 +771,16 @@ script.execute = function execute(s, stack, tx, index, recurse) { if (recurse++ > 2) return false; - var evalScript = stack.pop(); + evalScript = stack.pop(); if (!Array.isArray(evalScript)) return false; evalScript = script.decode(evalScript); - var res = evalScript.some(function(op) { + res = evalScript.some(function(op) { return op === 'codesep'; }); + if (res) return false; @@ -780,11 +805,16 @@ script.execute = function execute(s, stack, tx, index, recurse) { script.exec = function(input, output, tx, i, recurse) { var stack = []; + var res; + script.execute(input, stack, tx, i, recurse); - var res = script.execute(output, stack, tx, i, recurse); + + res = script.execute(output, stack, tx, i, recurse); + // if (!res || stack.length === 0 || new bn(stack.pop()).cmp(0) !== 0) if (!res || stack.length === 0 || utils.isEqual(stack.pop(), [ 0 ])) return false; + return true; }; @@ -847,8 +877,8 @@ script.isPubkey = function isPubkey(s, key) { if (key) return utils.isEqual(s[0], key); - else - return s[0]; + + return s[0]; }; script.isPubkeyhash = function isPubkeyhash(s, hash) { @@ -858,21 +888,24 @@ script.isPubkeyhash = function isPubkeyhash(s, hash) { if (s.length !== 5) return false; - var match = s[0] === 'dup' && - s[1] === 'hash160' && - Array.isArray(s[2]) && - s[3] === 'eqverify' && - s[4] === 'checksig'; + var match = s[0] === 'dup' + && s[1] === 'hash160' + && Array.isArray(s[2]) + && s[3] === 'eqverify' + && s[4] === 'checksig'; + if (!match) return false; if (hash) return utils.isEqual(s[2], hash); - else - return s[2]; + + return s[2]; }; script.isMultisig = function isMultisig(s, pubs) { + var m, n, keys, isArray, total; + if (script.lockTime(s)) s = s.slice(3); @@ -883,7 +916,7 @@ script.isMultisig = function isMultisig(s, pubs) { if (pubs && !Array.isArray(pubs[0])) pubs = [pubs]; - var m = s[0]; + m = s[0]; if (typeof m === 'number' && m >= 1 && m <= 15) m = [m]; if (!Array.isArray(m) || m.length !== 1) @@ -893,7 +926,7 @@ script.isMultisig = function isMultisig(s, pubs) { if (s[s.length - 1] !== 'checkmultisig') return false; - var n = s[s.length - 2]; + n = s[s.length - 2]; if (typeof n === 'number' && n >= 1 && n <= 15) n = [n]; if (!Array.isArray(n) || n.length !== 1) @@ -903,17 +936,19 @@ script.isMultisig = function isMultisig(s, pubs) { if (n + 3 !== s.length) return false; - var keys = s.slice(1, 1 + n); - var isArray = keys.every(function(k) { + keys = s.slice(1, 1 + n); + + isArray = keys.every(function(k) { return Array.isArray(k); }); + if (!isArray) return false; if (!pubs) return keys; - var total = keys.filter(function(k) { + total = keys.filter(function(k) { return pubs.some(function(pub) { return utils.isEqual(k, pub); }); @@ -929,10 +964,10 @@ script.isScripthash = function isScripthash(s, hash) { if (s.length !== 3) return false; - var res = s[0] === 'hash160' && - Array.isArray(s[1]) && - s[1].length === 20 && - s[2] === 'eq'; + var res = s[0] === 'hash160' + && Array.isArray(s[1]) + && s[1].length === 20 + && s[2] === 'eq'; if (!res) return false; @@ -947,9 +982,9 @@ script.isColored = function isColored(s) { if (s.length !== 2) return false; - return s[0] === 'ret' && - Array.isArray(s[1]) && - s[1].length <= 40; + return s[0] === 'ret' + && Array.isArray(s[1]) + && s[1].length <= 40; }; script.colored = function colored(s) { @@ -986,11 +1021,11 @@ script.isPubkeyhashInput = function isPubkeyhashInput(s, key) { if (s.length !== 2 || !Array.isArray(s[0]) || !Array.isArray(s[1])) return false; - // var res = script.isValidSig(s[0]) && - // 33 <= s[1].length && s[1].length <= 65; + // var res = script.isValidSig(s[0]) + // && 33 <= s[1].length && s[1].length <= 65; - var res = 9 <= s[0].length && s[0].length <= 73 && - 33 <= s[1].length && s[1].length <= 65; + var res = 9 <= s[0].length && s[0].length <= 73 + && 33 <= s[1].length && s[1].length <= 65; if (!res) return false; @@ -1002,21 +1037,23 @@ script.isPubkeyhashInput = function isPubkeyhashInput(s, key) { }; script.isMultisigInput = function isMultisigInput(s, pubs, tx, i) { + var i, res, o; + if (s.length < 3) return false; if (!Array.isArray(s[0]) || s[0].length !== 0) return false; - for (var i = 1; i < s.length; i++) { - // var res = Array.isArray(s[i]) && script.isValidSig(s[i]); - var res = Array.isArray(s[i]) && 9 <= s[i].length && s[i].length <= 73; + for (i = 1; i < s.length; i++) { + // res = Array.isArray(s[i]) && script.isValidSig(s[i]); + res = Array.isArray(s[i]) && 9 <= s[i].length && s[i].length <= 73; if (!res) return false; } if (pubs && pubs.length >= 2) { - var o = script.multisig(pubs, 2, pubs.length); + o = script.multisig(pubs, 2, pubs.length); return script.exec(s, o, tx, i); } @@ -1024,27 +1061,29 @@ script.isMultisigInput = function isMultisigInput(s, pubs, tx, i) { }; script.isScripthashInput = function isScripthashInput(s, redeem) { + var i, res, r, keys; + if (s.length < 4) return false; if (!Array.isArray(s[0]) || s[0].length !== 0) return false; - for (var i = 1; i < s.length - 1; i++) { - // var res = Array.isArray(s[i]) && script.isValidSig(s[i]); - var res = Array.isArray(s[i]) && 9 <= s[i].length && s[i].length <= 73; + for (i = 1; i < s.length - 1; i++) { + // res = Array.isArray(s[i]) && script.isValidSig(s[i]); + res = Array.isArray(s[i]) && 9 <= s[i].length && s[i].length <= 73; if (!res) return false; } - var r = Array.isArray(s[s.length - 1]) && s[s.length - 1]; + r = Array.isArray(s[s.length - 1]) && s[s.length - 1]; if (r[r.length - 1] !== constants.opcodes.checkmultisig) return false; if (redeem) return utils.isEqual(redeem, r); - var keys = script.decode(r).slice(1, -2); + keys = script.decode(r).slice(1, -2); return keys; }; @@ -1061,6 +1100,8 @@ script.isScripthashInput = function isScripthashInput(s, redeem) { * This function is consensus-critical since BIP66. */ script.isValidSig = function(sig) { + var lenR, lenS; + // Empty signature. Not strictly DER encoded, but allowed to provide a // compact way to provide an invalid signature for use with CHECK(MULTI)SIG if (sig.length === 0) @@ -1093,14 +1134,14 @@ script.isValidSig = function(sig) { return false; // Extract the length of the R element. - var lenR = sig[3]; + lenR = sig[3]; // Make sure the length of the S element is still inside the signature. if (5 + lenR >= sig.length) return false; // Extract the length of the S element. - var lenS = sig[5 + lenR]; + lenS = sig[5 + lenR]; // Verify that the length of the signature matches the sum of the length // of the elements. @@ -1146,14 +1187,15 @@ script.isValidSig = function(sig) { script.format = function(input, output) { var scripts = []; + var prev, redeem; if (input) { scripts.push(input.script); if (input.out.tx && input.out.tx.outputs[input.out.index]) { - var prev = input.out.tx.outputs[input.out.index].script; + prev = input.out.tx.outputs[input.out.index].script; scripts.push(prev); if (script.isScripthash(prev)) { - var redeem = script.decode(input.script[input.script.length - 1]); + redeem = script.decode(input.script[input.script.length - 1]); scripts.push(redeem); } } diff --git a/lib/bcoin/tx-pool.js b/lib/bcoin/tx-pool.js index 59d465e8..d6192358 100644 --- a/lib/bcoin/tx-pool.js +++ b/lib/bcoin/tx-pool.js @@ -22,27 +22,31 @@ function TXPool(wallet) { // Load TXs from storage this._init(); } + inherits(TXPool, EventEmitter); -module.exports = TXPool; TXPool.prototype._init = function init() { + var self = this; + if (!this._storage) { this._loaded = true; return; } - var self = this; var s = this._storage.createReadStream({ keys: false, start: this._prefix, end: this._prefix + 'z' }); + s.on('data', function(data) { self.add(bcoin.tx.fromJSON(data), true); }); + s.on('error', function(err) { self.emit('error', err); }); + s.on('end', function() { self._loaded = true; self.emit('load', self._lastTs); @@ -51,6 +55,9 @@ TXPool.prototype._init = function init() { TXPool.prototype.add = function add(tx, noWrite) { var hash = tx.hash('hex'); + var ownInput, ownOutput, updated; + var i, input, key, unspent, index, orphan; + var out, key, orphans, some; // Ignore stale pending transactions if (tx.ts === 0 && tx.ps + 2 * 24 * 3600 < +new Date() / 1000) { @@ -70,19 +77,19 @@ TXPool.prototype.add = function add(tx, noWrite) { } this._all[hash] = tx; - var ownInput = this._wallet.ownInput(tx); - var ownOutput = this._wallet.ownOutput(tx); - var updated = false; + ownInput = this._wallet.ownInput(tx); + ownOutput = this._wallet.ownOutput(tx); + updated = false; // Consume unspent money or add orphans - for (var i = 0; i < tx.inputs.length; i++) { - var input = tx.inputs[i]; - var key = input.out.hash + '/' + input.out.index; - var unspent = this._unspent[key]; + for (i = 0; i < tx.inputs.length; i++) { + input = tx.inputs[i]; + key = input.out.hash + '/' + input.out.index; + unspent = this._unspent[key]; if (unspent) { // Add TX to inputs and spend money - var index = tx._input(unspent.tx, unspent.index); + index = tx._input(unspent.tx, unspent.index); // Skip invalid transactions if (!tx.verify(index)) @@ -99,7 +106,7 @@ TXPool.prototype.add = function add(tx, noWrite) { continue; // Add orphan, if no parent transaction is yet known - var orphan = { tx: tx, index: input.out.index }; + orphan = { tx: tx, index: input.out.index }; if (this._orphans[key]) this._orphans[key].push(orphan); else @@ -130,19 +137,19 @@ TXPool.prototype.add = function add(tx, noWrite) { } // Add unspent outputs or fullfill orphans - for (var i = 0; i < tx.outputs.length; i++) { - var out = tx.outputs[i]; + for (i = 0; i < tx.outputs.length; i++) { + out = tx.outputs[i]; // Do not add unspents for outputs that aren't ours. if (!~ownOutput.indexOf(out)) continue; - var key = hash + '/' + i; - var orphans = this._orphans[key]; + key = hash + '/' + i; + orphans = this._orphans[key]; // Add input to orphan if (orphans) { - var some = orphans.some(checkOrphan, this); + some = orphans.some(checkOrphan, this); if (!some) orphans = null; } @@ -166,10 +173,11 @@ TXPool.prototype.add = function add(tx, noWrite) { }; TXPool.prototype._storeTX = function _storeTX(hash, tx, noWrite) { + var self = this; + if (!this._storage || noWrite) return; - var self = this; this._storage.put(this._prefix + hash, tx.toJSON(), function(err) { if (err) self.emit('error', err); @@ -177,13 +185,14 @@ TXPool.prototype._storeTX = function _storeTX(hash, tx, noWrite) { }; TXPool.prototype._removeTX = function _removeTX(tx, noWrite) { + var self = this; + for (var i = 0; i < tx.outputs.length; i++) delete this._unspent[tx.hash('hex') + '/' + i]; if (!this._storage || noWrite) return; - var self = this; this._storage.del(this._prefix + tx.hash('hex'), function(err) { if (err) self.emit('error', err); @@ -194,8 +203,8 @@ TXPool.prototype.all = function all() { return Object.keys(this._all).map(function(key) { return this._all[key]; }, this).filter(function(tx) { - return this._wallet.ownOutput(tx) || - this._wallet.ownInput(tx); + return this._wallet.ownOutput(tx) + || this._wallet.ownInput(tx); }, this); }; @@ -208,9 +217,11 @@ TXPool.prototype.unspent = function unspent() { }; TXPool.prototype.hasUnspent = function hasUnspent(hash, unspent) { + var has; + if (Array.isArray(hash) && hash.length && typeof hash[0] !== 'number') { unspent = this.unspent(); - var has = hash.map(function(hash) { + has = hash.map(function(hash) { var h = this.hasUnspent(hash, unspent); if (!h) return false; @@ -232,7 +243,7 @@ TXPool.prototype.hasUnspent = function hasUnspent(hash, unspent) { unspent = unspent || this.unspent(); - var has = unspent.filter(function(item) { + has = unspent.filter(function(item) { return item.tx.hash('hex') === hash; }); @@ -279,3 +290,5 @@ TXPool.prototype.fromJSON = function fromJSON(json) { this.add(bcoin.tx.fromJSON(tx)); }, this); }; + +module.exports = TXPool; diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index f9218ae2..1a8caa26 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -47,7 +47,6 @@ function TX(data, block) { this.changeAddress = data.changeAddress || null; } -module.exports = TX; TX.fee = 10000; TX.dust = 5460; @@ -77,12 +76,13 @@ TX.prototype.input = function input(i, index) { }; TX.prototype._input = function _input(i, index) { + var hash, input, prev, lock, index, ex; + if (i instanceof TX) i = { tx: i, index: index }; else if (typeof i === 'string' || Array.isArray(i)) i = { hash: i, index: index }; - var hash; if (i.tx) hash = i.tx.hash('hex'); else if (i.out) @@ -93,7 +93,7 @@ TX.prototype._input = function _input(i, index) { if (typeof hash !== 'string') hash = utils.toHex(hash); - var input = { + input = { out: { tx: (i.out ? i.out.tx : i.tx) || null, hash: utils.toHex(hash), @@ -107,8 +107,8 @@ TX.prototype._input = function _input(i, index) { utils.hidden(input.script, '_raw', i.script._raw); if (input.out.tx) { - var prev = input.out.tx.outputs[input.out.index].script; - var lock = bcoin.script.lockTime(prev); + prev = input.out.tx.outputs[input.out.index].script; + lock = bcoin.script.lockTime(prev); if (lock) { if (this._lock === 0) this.lock = Math.max(lock.toNumber(), this.lock); @@ -123,9 +123,9 @@ TX.prototype._input = function _input(i, index) { } // Try modifying existing input first - var index = this._inputIndex(hash, index); + index = this._inputIndex(hash, index); if (index !== -1) { - var ex = this.inputs[index]; + ex = this.inputs[index]; input.out.tx = input.out.tx || ex.out.tx; input.seq = input.seq || ex.seq; input.script = input.script.length ? input.script : ex.script; @@ -139,10 +139,13 @@ TX.prototype._input = function _input(i, index) { }; TX.prototype._inputIndex = function _inputIndex(hash, index) { + var i, ex; + if (hash instanceof TX) hash = hash.hash('hex'); - for (var i = 0; i < this.inputs.length; i++) { - var ex = this.inputs[i]; + + for (i = 0; i < this.inputs.length; i++) { + ex = this.inputs[i]; if (ex.out.hash === hash && ex.out.index === index) return i; } @@ -151,10 +154,12 @@ TX.prototype._inputIndex = function _inputIndex(hash, index) { }; TX.prototype.prevOut = function prevOut(i, def) { + var input; + if (typeof i === 'object') i = this.inputs.indexOf(i); - var input = this.inputs[i]; + input = this.inputs[i]; if (!input || !input.out.tx || input.out.index == null) return def; @@ -163,6 +168,8 @@ TX.prototype.prevOut = function prevOut(i, def) { }; TX.prototype.signatureHash = function signatureHash(i, type) { + var input, s, hash; + if (typeof i === 'object') i = this.inputs.indexOf(i); @@ -173,18 +180,20 @@ TX.prototype.signatureHash = function signatureHash(i, type) { type = constants.hashType[type]; // Get the current input. - var input = this.inputs[i]; + input = this.inputs[i]; // Get the previous output's subscript - var s = input.out.tx.getSubscript(input.out.index); + s = input.out.tx.getSubscript(input.out.index); // Get the hash of the current tx, minus the other inputs, plus the sighash. - var hash = this.subscriptHash(i, s, type); + hash = this.subscriptHash(i, s, type); return hash; }; TX.prototype.signature = function signature(i, key, type) { + var hash, signature; + if (typeof i === 'object') i = this.inputs.indexOf(i); @@ -195,10 +204,10 @@ TX.prototype.signature = function signature(i, key, type) { type = constants.hashType[type]; // Get the hash of the current tx, minus the other inputs, plus the sighash. - var hash = this.sigHash(i, type); + hash = this.sigHash(i, type); // Sign the transaction with our one input - var signature = bcoin.ecdsa.sign(hash, key.priv).toDER(); + signature = bcoin.ecdsa.sign(hash, key.priv).toDER(); // Add the sighash as a single byte to the signature signature = signature.concat(type); @@ -210,6 +219,7 @@ TX.prototype.signature = function signature(i, key, type) { TX.prototype.scriptInput = function scriptInput(input, pub) { // Get the previous output's subscript var s = input.out.tx.getSubscript(input.out.index); + var n, i, redeem; // Already has a script template (at least) if (input.script.length) @@ -236,11 +246,11 @@ TX.prototype.scriptInput = function scriptInput(input, pub) { // raw format: OP_FALSE [sig-1] [sig-2] ... if (bcoin.script.isMultisig(s)) { input.script = [ [] ]; - var n = s[s.length - 2]; + n = s[s.length - 2]; // If using pushdata instead of OP_1-16: if (Array.isArray(n)) n = n[0] || 0; - for (var i = 0; i < n; i++) + for (i = 0; i < n; i++) input.script[i + 1] = []; this._recalculateFee(); return; @@ -250,12 +260,12 @@ TX.prototype.scriptInput = function scriptInput(input, pub) { // p2sh format: OP_FALSE [sig-1] [sig-2] ... [redeem-script] if (bcoin.script.isScripthash(s)) { input.script = [ [] ]; - var redeem = bcoin.script.decode(pub); - var n = redeem[redeem.length - 2]; + redeem = bcoin.script.decode(pub); + n = redeem[redeem.length - 2]; // If using pushdata instead of OP_1-16: if (Array.isArray(n)) n = n[0] || 0; - for (var i = 0; i < n; i++) + for (i = 0; i < n; i++) input.script[i + 1] = []; // P2SH requires the redeem script after signatures input.script.push(pub); @@ -268,6 +278,9 @@ TX.prototype.scriptInput = function scriptInput(input, pub) { // Sign the now-built scriptSigs TX.prototype.signInput = function signInput(input, key, type) { + var s, hash, signature; + var len, redeem, m, keys, pub, pubn, ki, totalSigs, i; + if (!type) type = 'all'; @@ -275,13 +288,13 @@ TX.prototype.signInput = function signInput(input, key, type) { type = constants.hashType[type]; // Get the previous output's subscript - var s = input.out.tx.getSubscript(input.out.index); + s = input.out.tx.getSubscript(input.out.index); // Get the hash of the current tx, minus the other inputs, plus the sighash. - var hash = this.subscriptHash(this.inputs.indexOf(input), s, type); + hash = this.subscriptHash(this.inputs.indexOf(input), s, type); // Sign the transaction with our one input - var signature = bcoin.ecdsa.sign(hash, key.priv).toDER(); + signature = bcoin.ecdsa.sign(hash, key.priv).toDER(); // Add the sighash as a single byte to the signature signature = signature.concat(type); @@ -302,8 +315,7 @@ TX.prototype.signInput = function signInput(input, key, type) { // raw format: OP_FALSE [sig-1] [sig-2] ... // p2sh format: OP_FALSE [sig-1] [sig-2] ... [redeem-script] if (bcoin.script.isMultisig(s) || bcoin.script.isScripthash(s)) { - var len = input.script.length; - var redeem; + len = input.script.length; if (bcoin.script.isScripthash(s)) { len--; @@ -312,18 +324,18 @@ TX.prototype.signInput = function signInput(input, key, type) { redeem = s; } - var m = redeem[0]; + m = redeem[0]; // If using pushdata instead of OP_1-16: if (Array.isArray(m)) m = m[0] || 0; - var keys = redeem.slice(1, -2); - var pub = key.getPublic(true, 'array'); - var pubn = key.getPublic(false, 'array'); + keys = redeem.slice(1, -2); + pub = key.getPublic(true, 'array'); + pubn = key.getPublic(false, 'array'); // Find the key index so we can place // the signature in the same index. - for (var ki = 0; ki < keys.length; ki++) { + for (ki = 0; ki < keys.length; ki++) { if (utils.isEqual(pub, keys[ki]) || utils.isEqual(pubn, keys[ki])) break; } @@ -336,8 +348,8 @@ TX.prototype.signInput = function signInput(input, key, type) { // Add our signature to the correct slot // and count the total number of signatures. - var totalSigs = 0; - for (var i = 1; i < len; i++) { + totalSigs = 0; + for (i = 1; i < len; i++) { if (Array.isArray(input.script[i]) && input.script[i].length) { totalSigs++; continue; @@ -353,7 +365,7 @@ TX.prototype.signInput = function signInput(input, key, type) { // All signatures added. Finalize by removing empty slots. if (totalSigs >= m) { - for (var i = len - 1; i >= 1; i--) { + for (i = len - 1; i >= 1; i--) { if (Array.isArray(input.script[i]) && !input.script[i].length) input.script.splice(i, 1); } @@ -414,6 +426,8 @@ TX.prototype.scriptOutput = function scriptOutput(output, options) { options = options || output; var script = output.script ? output.script.slice() : []; + var keys, m, n; + var hash, color; if (output.script && output.script._raw) utils.hidden(script, '_raw', output.script._raw); @@ -424,7 +438,7 @@ TX.prototype.scriptOutput = function scriptOutput(output, options) { // https://github.com/bitcoin/bips/blob/master/bip-0011.mediawiki // https://github.com/bitcoin/bips/blob/master/bip-0019.mediawiki // [required-sigs] [pubkey-hash1] [pubkey-hash2] ... [number-of-keys] checkmultisig - var keys = options.keys || options.address; + keys = options.keys || options.address; if (keys === options.address) { keys = keys.map(function(address) { @@ -440,8 +454,8 @@ TX.prototype.scriptOutput = function scriptOutput(output, options) { // compat: options.m = options.minSignatures || options.m; - var m = options.m || keys.length; - var n = options.n || keys.length; + m = options.m || keys.length; + n = options.n || keys.length; assert(m >= 1 && m <= n); if (options.hash) @@ -453,7 +467,7 @@ TX.prototype.scriptOutput = function scriptOutput(output, options) { // make it p2sh if (options.scripthash) { - var hash = utils.ripesha(bcoin.script.encode(script)); + hash = utils.ripesha(bcoin.script.encode(script)); script = [ 'hash160', hash, @@ -480,7 +494,7 @@ TX.prototype.scriptOutput = function scriptOutput(output, options) { 'checksig' ]; } else if (options.color) { - var color = options.color; + color = options.color; if (typeof color === 'string') color = utils.ascii2array(color); assert(color.length <= 40); @@ -494,15 +508,13 @@ TX.prototype.scriptOutput = function scriptOutput(output, options) { }; TX.prototype.getSubscript = function getSubscript(index) { - var output = this.outputs[index]; - assert(output); - - var script = output.script; + var script = this.outputs[index].script; return bcoin.script.subscript(script); }; TX.prototype.subscriptHash = function subscriptHash(index, s, type) { var copy = this.clone(); + var verifyStr, hash; if (typeof type === 'string') type = constants.hashType[type]; @@ -550,11 +562,11 @@ TX.prototype.subscriptHash = function subscriptHash(index, s, type) { copy.inputs[0].script = s; } - var verifyStr = copy.render(); + verifyStr = copy.render(); utils.writeU32(verifyStr, type, verifyStr.length); - var hash = utils.dsha256(verifyStr); + hash = utils.dsha256(verifyStr); return hash; }; @@ -568,6 +580,8 @@ TX.prototype.verify = function verify(index, force) { return false; return this.inputs.every(function(input, i) { + var stack, prev, push, res, redeem; + if (index !== undefined && index !== i) return true; @@ -576,19 +590,19 @@ TX.prototype.verify = function verify(index, force) { assert(input.out.tx.outputs.length > input.out.index); - var stack = []; - var prev = input.out.tx.outputs[input.out.index].script; + stack = []; + prev = input.out.tx.outputs[input.out.index].script; if (bcoin.script.isScripthash(prev)) { // p2sh transactions cannot have anything // other than pushdata ops in the scriptSig - var push = input.script.slice(1).every(Array.isArray); + push = input.script.slice(1).every(Array.isArray); if (!push) return false; } bcoin.script.execute(input.script, stack, this, i); - var res = bcoin.script.execute(prev, stack, this, i); + res = bcoin.script.execute(prev, stack, this, i); if (!res) return false; @@ -598,7 +612,7 @@ TX.prototype.verify = function verify(index, force) { return false; if (bcoin.script.isScripthash(prev)) { - var redeem = input.script[input.script.length - 1]; + redeem = input.script[input.script.length - 1]; if (!Array.isArray(redeem)) return false; redeem = bcoin.script.decode(redeem); @@ -618,19 +632,23 @@ TX.prototype.isCoinbase = function isCoinbase() { TX.prototype.maxSize = function maxSize() { // Create copy with 0-script inputs var copy = this.clone(); + var size; + copy.inputs.forEach(function(input) { input.script = []; }); - var size = copy.render().length; + size = copy.render().length; // Add size for signatures and public keys copy.inputs.forEach(function(input, i) { + var s, m, n, script, redeem; + // Get the previous output's script - // var s = input.out.tx.outputs[input.out.index].script; + // s = input.out.tx.outputs[input.out.index].script; // Get the previous output's subscript - var s = input.out.tx.getSubscript(input.out.index); + s = input.out.tx.getSubscript(input.out.index); if (bcoin.script.isPubkey(s)) { // Signature + len @@ -651,7 +669,7 @@ TX.prototype.maxSize = function maxSize() { // Empty byte size += 1; // Signature + len - var m = s[0]; + m = s[0]; // If using pushdata instead of OP_1-16: if (Array.isArray(m)) m = m[0] || 0; @@ -661,8 +679,7 @@ TX.prototype.maxSize = function maxSize() { } if (bcoin.script.isScripthash(s)) { - var script = this.inputs[i].script; - var redeem, m, n; + script = this.inputs[i].script; if (script.length) { redeem = bcoin.script.decode(script[script.length - 1]); m = redeem[0]; @@ -724,6 +741,9 @@ TX.prototype.utxos = function utxos(unspent) { var utxos = []; var lastAdded = 0; + + var byteSize, addFee, change; + function addInput(unspent, i) { // Add new inputs until TX will have enough funds to cover both // minimum post cost and fee @@ -744,9 +764,9 @@ TX.prototype.utxos = function utxos(unspent) { // (10000 satoshi for every 1024 bytes) do { // Calculate maximum possible size after signing - var byteSize = this.maxSize(); + byteSize = this.maxSize(); - var addFee = Math.ceil(byteSize / 1024) - fee; + addFee = Math.ceil(byteSize / 1024) - fee; total.iadd(new bn(addFee * TX.fee)); fee += addFee; @@ -764,7 +784,7 @@ TX.prototype.utxos = function utxos(unspent) { } // How much money is left after sending outputs - var change = this.funds('in').sub(total); + change = this.funds('in').sub(total); // Clear the tx of everything we added. this.inputs = inputs; @@ -812,6 +832,7 @@ TX.prototype.fillUnspent = function fillUnspent(unspent, changeAddress) { TX.prototype._recalculateFee = function recalculateFee() { var output = this.changeOutput; + if (!output) { this.output({ address: this.changeAddress, @@ -864,26 +885,28 @@ TX.getInputData = function getInputData(input) { if (!input || !input.script) return; var script = input.script; + var scriptSig, pub, hash, addr, redeem, data; + var output; if (bcoin.script.isPubkeyhashInput(script)) { - var scriptSig = utils.toHex(script[0]); - var pubKey = script[1]; - var hash = utils.ripesha(pubKey); - var addr = bcoin.wallet.hash2addr(hash); + scriptSig = utils.toHex(script[0]); + pub = script[1]; + hash = utils.ripesha(pub); + addr = bcoin.wallet.hash2addr(hash); return { sig: scriptSig, - pub: pubKey, + pub: pub, hash: hash, addr: addr }; } if (bcoin.script.isScripthashInput(script)) { - var pub = script[script.length - 1]; - var hash = utils.ripesha(pub); - var addr = bcoin.wallet.hash2addr(hash, 'scripthash'); - var redeem = bcoin.script.decode(pub); - var data = TX.getOutputData({ script: redeem }); + pub = script[script.length - 1]; + hash = utils.ripesha(pub); + addr = bcoin.wallet.hash2addr(hash, 'scripthash'); + redeem = bcoin.script.decode(pub); + data = TX.getOutputData({ script: redeem }); data.pub = pub; data.hash = hash; data.addr = addr; @@ -904,7 +927,7 @@ TX.getInputData = function getInputData(input) { if (!input.out.tx) return; - var output = input.out.tx.outputs[input.out.index]; + output = input.out.tx.outputs[input.out.index]; return TX.getOutputData(output); }; @@ -913,22 +936,23 @@ TX.getOutputData = function getOutputData(output) { if (!output || !output.script) return; var script = output.script; + var pub, hash, addr, pubs; if (bcoin.script.isPubkey(script)) { - var pubKey = script[0]; - var hash = utils.ripesha(pubKey); - var addr = bcoin.wallet.hash2addr(hash); + pub = script[0]; + hash = utils.ripesha(pub); + addr = bcoin.wallet.hash2addr(hash); return { sig: null, - pub: pubKey, + pub: pub, hash: hash, addr: addr }; } if (bcoin.script.isPubkeyhash(script)) { - var hash = script[2]; - var addr = bcoin.wallet.hash2addr(hash); + hash = script[2]; + addr = bcoin.wallet.hash2addr(hash); return { sig: null, pub: null, @@ -937,10 +961,10 @@ TX.getOutputData = function getOutputData(output) { }; } - var pubs = bcoin.script.isMultisig(script); + pubs = bcoin.script.isMultisig(script); if (pubs) { - var hash = utils.ripesha(pubs[0]); - var addr = bcoin.wallet.hash2addr(hash); + hash = utils.ripesha(pubs[0]); + addr = bcoin.wallet.hash2addr(hash); return { sig: null, pub: pubs[0], @@ -963,8 +987,8 @@ TX.getOutputData = function getOutputData(output) { } if (bcoin.script.isScripthash(script)) { - var hash = utils.toHex(s[1]); - var addr = bcoin.wallet.hash2addr(hash, 'scripthash'); + hash = utils.toHex(s[1]); + addr = bcoin.wallet.hash2addr(hash, 'scripthash'); return { sig: null, pub: null, @@ -991,12 +1015,14 @@ TX.prototype.getFee = function getFee() { }; TX.prototype.funds = function funds(side) { + var acc = new bn(0); + var inputs; + if (side === 'in') { - var inputs = this.inputs.filter(function(input) { + inputs = this.inputs.filter(function(input) { return input.out.tx; }); - var acc = new bn(0); if (inputs.length === 0) return acc; @@ -1008,7 +1034,6 @@ TX.prototype.funds = function funds(side) { } // Output - var acc = new bn(0); if (this.outputs.length === 0) return acc; @@ -1043,3 +1068,5 @@ TX.fromJSON = function fromJSON(json) { return tx; }; + +module.exports = TX; diff --git a/lib/bcoin/utils.js b/lib/bcoin/utils.js index b63aeea9..0b770197 100644 --- a/lib/bcoin/utils.js +++ b/lib/bcoin/utils.js @@ -7,15 +7,19 @@ var util = require('util'); function toArray(msg, enc) { if (Array.isArray(msg)) return msg.slice(); + if (!msg) return []; + var res = []; + var i, c, hi, lo, slice, num; + if (typeof msg === 'string') { if (!enc) { - for (var i = 0; i < msg.length; i++) { - var c = msg.charCodeAt(i); - var hi = c >> 8; - var lo = c & 0xff; + for (i = 0; i < msg.length; i++) { + c = msg.charCodeAt(i); + hi = c >> 8; + lo = c & 0xff; if (hi) res.push(hi, lo); else @@ -25,9 +29,10 @@ function toArray(msg, enc) { msg = msg.replace(/[^a-z0-9]+/ig, ''); if (msg.length % 2 !== 0) msg = '0' + msg; - for (var i = 0; i < msg.length; i += 8) { - var slice = msg.slice(i, i + 8); - var num = parseInt(slice, 16); + + for (i = 0; i < msg.length; i += 8) { + slice = msg.slice(i, i + 8); + num = parseInt(slice, 16); if (slice.length === 8) res.push((num >>> 24) & 0xff); @@ -39,15 +44,16 @@ function toArray(msg, enc) { } } } else { - for (var i = 0; i < msg.length; i++) + for (i = 0; i < msg.length; i++) res[i] = msg[i] | 0; } + return res; } utils.toArray = toArray; -var base58 = '123456789ABCDEFGHJKLMNPQRSTUVWXYZ' + - 'abcdefghijkmnopqrstuvwxyz'; +var base58 = '123456789ABCDEFGHJKLMNPQRSTUVWXYZ' + + 'abcdefghijkmnopqrstuvwxyz'; utils.toBase58 = function toBase58(arr) { var n = new bn(arr, 16); @@ -56,17 +62,20 @@ utils.toBase58 = function toBase58(arr) { var mod = new bn(0xacad10); var res = ''; + + var r, end, i, c; + do { - var r = n.mod(mod); + r = n.mod(mod); n = n.div(mod); - var end = n.cmpn(0) === 0; + end = n.cmpn(0) === 0; utils.assert.equal(r.length, 1); r = r.words[0]; - for (var i = 0; i < 4; i++) { - var c = r % 58; + for (i = 0; i < 4; i++) { + c = r % 58; r = (r - c) / 58; if (c === 0 && r === 0 && end) @@ -77,7 +86,7 @@ utils.toBase58 = function toBase58(arr) { } while (!end); // Add leading "zeroes" - for (var i = 0; i < arr.length; i++) { + for (i = 0; i < arr.length; i++) { if (arr[i] !== 0) break; res = '1' + res; @@ -87,18 +96,20 @@ utils.toBase58 = function toBase58(arr) { }; utils.fromBase58 = function fromBase58(str) { + var i, zeroes, q, w, res, c, z; + // Count leading "zeroes" - for (var i = 0; i < str.length; i++) + for (i = 0; i < str.length; i++) if (str[i] !== '1') break; - var zeroes = i; + zeroes = i; // Read 4-char words and add them to bignum - var q = 1; - var w = 0; - var res = new bn(0); - for (var i = zeroes; i < str.length; i++) { - var c = base58.indexOf(str[i]); + q = 1; + w = 0; + res = new bn(0); + for (i = zeroes; i < str.length; i++) { + c = base58.indexOf(str[i]); if (!(c >= 0 && c < 58)) return []; @@ -113,9 +124,10 @@ utils.fromBase58 = function fromBase58(str) { } // Add leading "zeroes" - var z = []; - for (var i = 0; i < zeroes; i++) + z = []; + for (i = 0; i < zeroes; i++) z.push(0); + return z.concat(res.toArray()); }; @@ -152,12 +164,15 @@ utils.readU16 = function readU16(arr, off) { utils.readU32 = function readU32(arr, off) { if (!off) off = 0; - var r = arr[off] | - (arr[off + 1] << 8) | - (arr[off + 2] << 16) | - (arr[off + 3] << 24); + + var r = arr[off] + | (arr[off + 1] << 8) + | (arr[off + 2] << 16) + | (arr[off + 3] << 24); + if (r < 0) r += 0x100000000; + return r; }; @@ -186,6 +201,8 @@ utils.writeU32 = function writeU32(dst, num, off) { }; utils.writeU64 = function writeU64(dst, num, off) { + var i = 0; + if (!off) off = 0; @@ -197,7 +214,8 @@ utils.writeU64 = function writeU64(dst, num, off) { dst[off++] = ch; }); - var i = num.length; + i = num.length; + while (i--) dst[off++] = num[i]; @@ -223,6 +241,8 @@ utils.writeU32BE = function writeU32BE(dst, num, off) { }; utils.writeU64BE = function writeU64BE(dst, num, off) { + var i = 0; + if (!off) off = 0; @@ -230,7 +250,7 @@ utils.writeU64BE = function writeU64BE(dst, num, off) { while (num.length < 8) num.unshift(0); - for (var i = 0; i < num.length; i++) + for (; i < num.length; i++) dst[off++] = num[i]; return 8; @@ -245,12 +265,15 @@ utils.readU16BE = function readU16BE(arr, off) { utils.readU32BE = function readU32BE(arr, off) { if (!off) off = 0; - var r = (arr[off] << 24) | - (arr[off + 1] << 16) | - (arr[off + 2] << 8) | - arr[off + 3]; + + var r = (arr[off] << 24) + | (arr[off + 1] << 16) + | (arr[off + 2] << 8) + | arr[off + 3]; + if (r < 0) r += 0x100000000; + return r; }; @@ -261,19 +284,27 @@ utils.readU64BE = function readU64BE(arr, off) { }; utils.writeAscii = function writeAscii(dst, str, off) { - for (var i = 0; i < str.length; i++) { - var c = str.charCodeAt(i); + var i = 0; + var c; + + for (; i < str.length; i++) { + c = str.charCodeAt(i); dst[off + i] = c & 0xff; } + return i; }; utils.readAscii = function readAscii(arr, off, len) { var str = ''; - for (var i = off; i < off + len; i++) { - var c = String.fromCharCode(arr[i] & 0xff); + var i = off; + var c; + + for (i = off; i < off + len; i++) { + c = String.fromCharCode(arr[i] & 0xff); str += c; } + return str; }; @@ -290,17 +321,24 @@ utils.array2ascii = function array2ascii(arr) { utils.copy = function copy(src, dst, off, force) { var len = src.length; + var i = 0; + if (!force) len = Math.min(dst.length - off, len); - for (var i = 0; i < len; i++) + + for (; i < len; i++) dst[i + off] = src[i]; + return i; }; utils.stringify = function stringify(arr) { var res = ''; - for (var i = 0; i < arr.length; i++) + var i = 0; + + for (; i < arr.length; i++) res += String.fromCharCode(arr[i]); + return res; }; @@ -312,23 +350,28 @@ function zero2(word) { } function toHex(msg) { + var res = ''; + var i = 0; + if (typeof msg === 'string') return msg; - var res = ''; - for (var i = 0; i < msg.length; i++) + for (; i < msg.length; i++) res += zero2(msg[i].toString(16)); + return res; } + utils.toHex = toHex; function binaryInsert(list, item, compare, search) { - var start = 0, - end = list.length; + var start = 0; + var end = list.length; + var pos, cmp; while (start < end) { - var pos = (start + end) >> 1; - var cmp = compare(item, list[pos]); + pos = (start + end) >> 1; + cmp = compare(item, list[pos]); if (cmp === 0) { start = pos; @@ -343,8 +386,10 @@ function binaryInsert(list, item, compare, search) { if (!search) list.splice(start, 0, item); + return start; } + utils.binaryInsert = binaryInsert; function bitsToTarget(bits) { @@ -352,26 +397,31 @@ function bitsToTarget(bits) { var hi = (bits >>> 16) & 0xff; var mid = (bits >>> 8) & 0xff; var lo = bits & 0xff; - var res = new Array(len); - for (var i = 0; i < len - 3; i++) + var i = 0; + + for (; i < len - 3; i++) res[i] = 0; + res[i++] = lo; res[i++] = mid; res[i++] = hi; if (hi === 0) res.pop(); + if (hi === 0 && mid === 0) res.pop(); return res; } + utils.bitsToTarget = bitsToTarget; function testTarget(target, hash) { if (typeof target === 'number') target = bitsToTarget(target); + hash = utils.toArray(hash, 'hex'); for (var i = hash.length - 1; i >= target.length; i--) @@ -388,13 +438,16 @@ function testTarget(target, hash) { return true; } + utils.testTarget = testTarget; utils.isEqual = function isEqual(a, b) { + var i = 0; + if (a.length !== b.length) return false; - for (var i = 0; i < a.length; i++) + for (; i < a.length; i++) if (a[i] !== b[i]) return false; @@ -433,12 +486,14 @@ RequestCache.prototype.add = function add(id, cb) { }; RequestCache.prototype.fullfill = function fullfill(id, err, data) { + var cbs = this.map[id]; + if (!this.map[id]) return; - var cbs = this.map[id]; delete this.map[id]; this.count--; + cbs.forEach(function(cb) { cb(err, data); }); @@ -456,8 +511,11 @@ utils.asyncify = function asyncify(fn) { utils.revHex = function revHex(s) { var r = ''; - for (var i = 0; i < s.length; i += 2) + var i = 0; + + for (; i < s.length; i += 2) r = s.slice(i, i + 2) + r; + return r; }; @@ -472,6 +530,9 @@ utils.assert.equal = function assertEqual(l, r, msg) { }; utils.toBTC = function toBTC(satoshi, strict) { + var m = new bn(10000000).mul(new bn(10)); + var lo = satoshi.mod(m); + if (typeof satoshi === 'string' && /^\d+(?:\.\d+)?$/.test(satoshi)) { satoshi = new bn(satoshi, 10); } else if (typeof satoshi === 'number') { @@ -483,8 +544,6 @@ utils.toBTC = function toBTC(satoshi, strict) { if (!(satoshi instanceof bn)) throw new Error('could not calculate btc'); - var m = new bn(10000000).mul(new bn(10)); - var lo = satoshi.mod(m); if (lo.cmpn(0) !== 0) { lo = lo.toString(10); while (lo.length < 8) @@ -498,10 +557,13 @@ utils.toBTC = function toBTC(satoshi, strict) { }; utils.fromBTC = function(btc, strict) { + var m = new bn(10000000).mul(new bn(10)); + var satoshi; + var parts; if (typeof btc === 'string' && /^\d+(?:\.\d+)?$/.test(btc)) { - var parts = btc.split('.'); + parts = btc.split('.'); parts[0] = parts[0] || '0'; parts[1] = parts[1] || '0'; while (parts[1].length < 8) @@ -517,8 +579,8 @@ utils.fromBTC = function(btc, strict) { if (!(satoshi instanceof bn)) throw new Error('could not calculate satoshis'); - var m = new bn(10000000).mul(new bn(10)); satoshi.imuln(m); + return satoshi; }; diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index 2b3e6b9c..b5b905fd 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -82,18 +82,19 @@ function Wallet(options, passphrase) { this._init(); } + inherits(Wallet, EventEmitter); -module.exports = Wallet; Wallet.prototype._init = function init() { + var self = this; + var prevBalance = null; + if (this.tx._loaded) { this.loaded = true; return; } // Notify owners about new accepted transactions - var self = this; - var prevBalance = null; this.tx.on('update', function(lastTs, tx) { var b = this.balance(); if (prevBalance && prevBalance.cmp(b) !== 0) @@ -203,35 +204,43 @@ Wallet.prototype.derive = function derive() { Wallet.prototype.getPrivateKey = function getPrivateKey(enc) { var priv = this.key.getPrivate(); + var arr, chk; + if (priv) priv = priv.toArray(); else return; + if (!enc) return priv; if (enc === 'base58') { // We'll be using ncompressed public key as an address - var arr = [ network.prefixes.privkey ]; + arr = [ network.prefixes.privkey ]; // 0-pad key while (arr.length + priv.length < 33) arr.push(0); + arr = arr.concat(priv); + if (this.compressed) arr.push(1); - var chk = utils.checksum(arr); + + chk = utils.checksum(arr); + return utils.toBase58(arr.concat(chk)); - } else { - return priv; } + + return priv; }; Wallet.prototype.getFullPublicKey = function getFullPublicKey(enc) { var pub = this.getOwnPublicKey(); + var keys; if (this.type === 'scripthash') { - var keys = this.getPublicKeys(); + keys = this.getPublicKeys(); pub = bcoin.script.encode(bcoin.script.multisig(keys, this.m, this.n)); } @@ -297,12 +306,14 @@ Wallet.key2hash = function key2hash(key) { }; Wallet.hash2addr = function hash2addr(hash, prefix) { + var addr; + hash = utils.toArray(hash, 'hex'); prefix = network.prefixes[prefix || 'pubkeyhash']; hash = [ prefix ].concat(hash); - var addr = hash.concat(utils.checksum(hash)); + addr = hash.concat(utils.checksum(hash)); return utils.toBase58(addr); }; @@ -317,6 +328,8 @@ Wallet.__defineGetter__('prefixes', function() { }); Wallet.addr2hash = function addr2hash(addr, prefix) { + var chk; + if (prefix == null && typeof addr === 'string') prefix = Wallet.prefixes[addr[0]]; @@ -330,7 +343,7 @@ Wallet.addr2hash = function addr2hash(addr, prefix) { if (addr[0] !== prefix) return []; - var chk = utils.checksum(addr.slice(0, -4)); + chk = utils.checksum(addr.slice(0, -4)); if (utils.readU32(chk, 0) !== utils.readU32(addr, 21)) return []; @@ -352,11 +365,11 @@ Wallet.prototype.ownOutput = function ownOutput(tx, index) { var keys = this.getPublicKeys(); var outputs = tx.outputs.filter(function(output, i) { + var s = output.script; + if (index !== undefined && index !== i) return false; - var s = output.script; - if (bcoin.script.isPubkey(s, key)) return true; @@ -386,6 +399,8 @@ Wallet.prototype.ownInput = function ownInput(tx, index) { var keys = this.getPublicKeys(); var inputs = tx.inputs.filter(function(input, i) { + var s; + if (index !== undefined && index !== i) return false; @@ -404,7 +419,7 @@ Wallet.prototype.ownInput = function ownInput(tx, index) { if (!input.out.tx) return false; - var s = input.out.tx.outputs[input.out.index].script; + s = input.out.tx.outputs[input.out.index].script; if (bcoin.script.isPubkey(s, key)) return true; @@ -468,11 +483,11 @@ Wallet.prototype.scriptInputs = function scriptInputs(tx, inputs) { }; Wallet.prototype.signInputs = function(tx, type, inputs) { + var key = this.key; + if (!type) type = 'all'; - var key = this.key; - inputs = inputs || tx.inputs; inputs = inputs.filter(function(input) { @@ -531,16 +546,22 @@ Wallet.prototype.balance = function balance() { }; Wallet.prototype.fill = function fill(tx, cb) { - cb = utils.asyncify(cb); var result = tx.fillUnspent(this.unspent(), this.getAddress()); + var err; + + cb = utils.asyncify(cb); + if (!result) { - var err = new Error('Not enough funds'); + err = new Error('Not enough funds'); err.minBalance = tx.total; cb(err); return null; } + this.sign(tx); + cb(null, tx); + return tx; }; @@ -590,12 +611,10 @@ Wallet.fromJSON = function fromJSON(json, decrypt) { if (decrypt) json.priv = decrypt(json.priv); - var priv; - var pub; - var compressed; + var priv, pub, compressed, key, w; if (json.priv) { - var key = bcoin.utils.fromBase58(json.priv); + key = bcoin.utils.fromBase58(json.priv); assert(utils.isEqual(key.slice(-4), utils.checksum(key.slice(0, -4)))); assert.equal(key[0], network.prefixes.privkey); @@ -621,7 +640,7 @@ Wallet.fromJSON = function fromJSON(json, decrypt) { priv = new hd.priv(json.hd); } - var w = new Wallet({ + w = new Wallet({ label: json.label, priv: priv, pub: pub, @@ -633,3 +652,5 @@ Wallet.fromJSON = function fromJSON(json, decrypt) { return w; }; + +module.exports = Wallet;