style consistency.

This commit is contained in:
Christopher Jeffrey 2015-12-18 22:37:02 -08:00
parent 9fe8afa7d3
commit 5ece45091b
16 changed files with 1072 additions and 661 deletions

View File

@ -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 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). 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 ## Prerequisites
``` ```

View File

@ -3,6 +3,8 @@ var utils = bcoin.utils;
var constants = bcoin.protocol.constants; var constants = bcoin.protocol.constants;
function Block(data, subtype) { function Block(data, subtype) {
var self = this;
if (!(this instanceof Block)) if (!(this instanceof Block))
return new Block(data, subtype); return new Block(data, subtype);
@ -28,7 +30,6 @@ function Block(data, subtype) {
this.invalid = false; this.invalid = false;
if (this.subtype === 'block') { if (this.subtype === 'block') {
var self = this;
this.txs = data.txs || []; this.txs = data.txs || [];
this.txs = this.txs.map(function(tx) { this.txs = this.txs.map(function(tx) {
tx._network = self._network; tx._network = self._network;
@ -49,7 +50,6 @@ function Block(data, subtype) {
// Verify partial merkle tree and fill `ts` array // Verify partial merkle tree and fill `ts` array
this._verifyMerkle(); this._verifyMerkle();
} }
module.exports = Block;
Block.prototype.hash = function hash(enc) { Block.prototype.hash = function hash(enc) {
// Hash it // Hash it
@ -91,33 +91,30 @@ Block.prototype.hasTX = function hasTX(hash) {
Block.prototype._verifyMerkle = function verifyMerkle() { Block.prototype._verifyMerkle = function verifyMerkle() {
var height = 0; 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 tx = [];
var i = 0; var i = 0;
var j = 0; var j = 0;
var hashes = this.hashes; var hashes = this.hashes;
var flags = this.flags; var flags = this.flags;
var i, root;
var root = visit(1); if (this.subtype === 'block')
if (!root || root !== this.merkleRoot) {
this.invalid = true;
return; 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) { function visit(depth) {
var flag, left, right;
if (i === flags.length * 8 || j === hashes.length) if (i === flags.length * 8 || j === hashes.length)
return null; return null;
var flag = (flags[i >> 3] >>> (i & 7)) & 1; flag = (flags[i >> 3] >>> (i & 7)) & 1;
i++; i++;
if (flag === 0 || depth === height) { if (flag === 0 || depth === height) {
@ -127,34 +124,49 @@ Block.prototype._verifyMerkle = function verifyMerkle() {
} }
// Go deeper // Go deeper
var left = visit(depth + 1); left = visit(depth + 1);
if (!left) if (!left)
return null; return null;
var right = visit(depth + 1);
right = visit(depth + 1);
if (right === left) if (right === left)
return null; return null;
if (!right) if (!right)
right = left; right = left;
return utils.toHex(utils.dsha256(left + right, 'hex')); 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() { Block.prototype.getMerkleRoot = function getMerkleRoot() {
var merkleTree = []; 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')); merkleTree.push(this.txs[i].hash('hex'));
} }
var j = 0; j = 0;
for (var size = this.txs.length; size > 1; size = ((size + 1) / 2) | 0) { size = this.txs.length;
for (var i = 0; i < size; i += 2) {
var i2 = Math.min(i + 1, size - 1); 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 if (i2 === i + 1 && i2 + 1 === size
&& merkleTree[j + i] === merkleTree[j + i2]) { && merkleTree[j + i] === merkleTree[j + i2]) {
return utils.toHex(constants.zeroHash); 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)); merkleTree.push(utils.toHex(hash));
} }
j += size; j += size;
@ -169,6 +181,8 @@ Block.prototype.getMerkleRoot = function getMerkleRoot() {
// This mimics the behavior of CheckBlockHeader() // This mimics the behavior of CheckBlockHeader()
// and CheckBlock() in bitcoin/src/main.cpp. // and CheckBlock() in bitcoin/src/main.cpp.
Block.prototype._checkBlock = function checkBlock() { Block.prototype._checkBlock = function checkBlock() {
var i, unique, hash, merkleRoot;
// Check proof of work matches claimed amount // Check proof of work matches claimed amount
if (!utils.testTarget(this.bits, this.hash())) if (!utils.testTarget(this.bits, this.hash()))
return false; return false;
@ -184,29 +198,29 @@ Block.prototype._checkBlock = function checkBlock() {
} }
// First TX must be a coinbase // First TX must be a coinbase
if (!this.txs.length || if (!this.txs.length
this.txs[0].inputs.length !== 1 || || this.txs[0].inputs.length !== 1
+this.txs[0].inputs[0].out.hash !== 0) || +this.txs[0].inputs[0].out.hash !== 0)
return false; return false;
// The rest of the txs must not be coinbases // The rest of the txs must not be coinbases
for (var i = 1; i < this.txs.length; i++) { for (i = 1; i < this.txs.length; i++) {
if (this.txs[i].inputs.length === 1 && if (this.txs[i].inputs.length === 1
+this.txs[i].inputs[0].out.hash === 0) && +this.txs[i].inputs[0].out.hash === 0)
return false; return false;
} }
// Check for duplicate tx ids // Check for duplicate tx ids
var unique = {}; unique = {};
for (var i = 0; i < this.txs.length; i++) { for (i = 0; i < this.txs.length; i++) {
var hash = this.txs[i].hash('hex'); hash = this.txs[i].hash('hex');
if (unique[hash]) if (unique[hash])
return false; return false;
unique[hash] = true; unique[hash] = true;
} }
// Build MerkleTree // Build MerkleTree
var merkleRoot = this.getMerkleRoot(); merkleRoot = this.getMerkleRoot();
// Check merkle root // Check merkle root
if (merkleRoot !== this.merkleRoot) if (merkleRoot !== this.merkleRoot)
@ -228,22 +242,26 @@ Block.prototype.toJSON = function toJSON() {
}; };
Block.fromJSON = function fromJSON(json) { Block.fromJSON = function fromJSON(json) {
var raw, parser, data, block;
utils.assert.equal(json.v, 1); utils.assert.equal(json.v, 1);
utils.assert.equal(json.type, 'block'); 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.parseMerkleBlock(raw) :
parser.parseBlock(raw); parser.parseBlock(raw);
data._network = json._network; data._network = json._network;
var block = new Block(data, json.subtype); block = new Block(data, json.subtype);
block._hash = json.hash; block._hash = json.hash;
return block; return block;
}; };
module.exports = Block;

View File

@ -12,7 +12,6 @@ function Bloom(size, n, tweak) {
this.reset(); this.reset();
} }
module.exports = Bloom;
Bloom.prototype.hash = function hash(val, n) { Bloom.prototype.hash = function hash(val, n) {
return Bloom.hash(val, sum32(mul32(n, 0xfba4c795), this.tweak)) % this.size; 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) { Bloom.prototype.add = function add(val, enc) {
var i, bit, pos, shift;
val = utils.toArray(val, enc); val = utils.toArray(val, enc);
for (var i = 0; i < this.n; i++) { for (i = 0; i < this.n; i++) {
var bit = this.hash(val, i); bit = this.hash(val, i);
var pos = 1 << (bit & 0x1f); pos = 1 << (bit & 0x1f);
var shift = bit >> 5; shift = bit >> 5;
this.filter[shift] |= pos; this.filter[shift] |= pos;
} }
}; };
Bloom.prototype.test = function test(val, enc) { Bloom.prototype.test = function test(val, enc) {
var i, bit, pos, shift;
val = utils.toArray(val, enc); val = utils.toArray(val, enc);
for (var i = 0; i < this.n; i++) { for (i = 0; i < this.n; i++) {
var bit = this.hash(val, i); bit = this.hash(val, i);
var pos = 1 << (bit & 0x1f); pos = 1 << (bit & 0x1f);
var shift = bit >> 5; shift = bit >> 5;
if ((this.filter[shift] & pos) === 0) if ((this.filter[shift] & pos) === 0)
return false; return false;
@ -53,8 +56,10 @@ Bloom.prototype.test = function test(val, enc) {
Bloom.prototype.toArray = function toArray() { Bloom.prototype.toArray = function toArray() {
var bytes = Math.ceil(this.size / 8); var bytes = Math.ceil(this.size / 8);
var res = new Array(this.filter.length * 4); var res = new Array(this.filter.length * 4);
for (var i = 0; i < this.filter.length; i++) { var i, w;
var w = this.filter[i];
for (i = 0; i < this.filter.length; i++) {
w = this.filter[i];
res[i * 4] = w & 0xff; res[i * 4] = w & 0xff;
res[i * 4 + 1] = (w >> 8) & 0xff; res[i * 4 + 1] = (w >> 8) & 0xff;
res[i * 4 + 2] = (w >> 16) & 0xff; res[i * 4 + 2] = (w >> 16) & 0xff;
@ -69,22 +74,26 @@ function mul32(a, b) {
var blo = b & 0xffff; var blo = b & 0xffff;
var ahi = a >>> 16; var ahi = a >>> 16;
var bhi = b >>> 16; var bhi = b >>> 16;
var r;
var lo = alo * blo; var lo = alo * blo;
var hi = (ahi * blo + bhi * alo) & 0xffff; var hi = (ahi * blo + bhi * alo) & 0xffff;
hi += lo >>> 16; hi += lo >>> 16;
lo &= 0xffff; lo &= 0xffff;
var r = (hi << 16) | lo; r = (hi << 16) | lo;
if (r < 0) if (r < 0)
r += 0x100000000; r += 0x100000000;
return r; return r;
} }
function sum32(a, b) { function sum32(a, b) {
var r = (a + b) & 0xffffffff; var r = (a + b) & 0xffffffff;
if (r < 0) if (r < 0)
r += 0x100000000; r += 0x100000000;
return r; return r;
} }
@ -103,11 +112,14 @@ function hash(data, seed) {
var n = 0xe6546b64; var n = 0xe6546b64;
var hash = seed; var hash = seed;
for (var i = 0; i + 4 <= data.length; i += 4) {
var w = data[i] | var i, w, r, j;
(data[i + 1] << 8) |
(data[i + 2] << 16) | for (i = 0; i + 4 <= data.length; i += 4) {
(data[i + 3] << 24); w = data[i]
| (data[i + 1] << 8)
| (data[i + 2] << 16)
| (data[i + 3] << 24);
w = mul32(w, c1); w = mul32(w, c1);
w = rotl32(w, r1); w = rotl32(w, r1);
@ -120,8 +132,8 @@ function hash(data, seed) {
} }
if (i !== data.length) { if (i !== data.length) {
var r = 0; r = 0;
for (var j = data.length - 1; j >= i; j--) for (j = data.length - 1; j >= i; j--)
r = (r << 8) | data[j]; r = (r << 8) | data[j];
r = mul32(r, c1); r = mul32(r, c1);
@ -145,4 +157,7 @@ function hash(data, seed) {
return hash; return hash;
} }
Bloom.hash = hash; Bloom.hash = hash;
module.exports = Bloom;

View File

@ -8,6 +8,8 @@ var utils = bcoin.utils;
var assert = utils.assert; var assert = utils.assert;
function Chain(options) { function Chain(options) {
var preload = network.preload;
if (!(this instanceof Chain)) if (!(this instanceof Chain))
return new Chain(options); return new Chain(options);
@ -38,8 +40,6 @@ function Chain(options) {
}; };
this.request = new utils.RequestCache(); this.request = new utils.RequestCache();
var preload = network.preload;
// Start from the genesis block // Start from the genesis block
// if we're a full node. // if we're a full node.
if (this.options.fullNode) { if (this.options.fullNode) {
@ -60,8 +60,8 @@ function Chain(options) {
this.loading = false; this.loading = false;
this._init(); this._init();
} }
inherits(Chain, EventEmitter); inherits(Chain, EventEmitter);
module.exports = Chain;
function compareTs(a, b) { function compareTs(a, b) {
return a -b; return a -b;
@ -69,6 +69,7 @@ function compareTs(a, b) {
Chain.prototype._init = function _init() { Chain.prototype._init = function _init() {
var self = this; var self = this;
var s;
if (!this.storage) if (!this.storage)
return; return;
@ -79,7 +80,7 @@ Chain.prototype._init = function _init() {
this.loading = true; this.loading = true;
var s = this.storage.createReadStream({ s = this.storage.createReadStream({
start: this.prefix, start: this.prefix,
end: this.prefix + 'z' end: this.prefix + 'z'
}); });
@ -103,18 +104,19 @@ Chain.prototype._init = function _init() {
Chain.prototype._getRange = function _getRange(hash, ts, futureOnly) { Chain.prototype._getRange = function _getRange(hash, ts, futureOnly) {
var pos = utils.binaryInsert(this.index.ts, ts, compareTs, true); var pos = utils.binaryInsert(this.index.ts, ts, compareTs, true);
var start = Math.min(Math.max(0, pos), this.index.ts.length - 1); 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) while (start > 0 && this.index.ts[start] > ts)
start--; start--;
var curr = this.index.ts[start]; curr = this.index.ts[start];
var wnd = 2 * 3600; wnd = 2 * 3600;
if (!futureOnly) if (!futureOnly)
while (start > 0 && this.index.ts[start] + wnd > curr) while (start > 0 && this.index.ts[start] + wnd > curr)
start--; 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) while (end < this.index.ts.length - 1 && this.index.ts[end] - wnd < ts)
end++; end++;
@ -127,13 +129,15 @@ Chain.prototype._probeIndex = function _probeIndex(hash, ts) {
var start = 0; var start = 0;
var end = this.index.ts.length; var end = this.index.ts.length;
var range, i;
if (ts) { if (ts) {
var range = this._getRange(hash, ts); range = this._getRange(hash, ts);
start = range.start; start = range.start;
end = range.end; end = range.end;
} }
for (var i = start; i <= end; i++) for (i = start; i <= end; i++)
if (this.index.hashes[i] === hash) if (this.index.hashes[i] === hash)
return { i: i, height: this.index.heights[i], ts: this.index.ts[i] }; 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) { Chain.prototype._addIndex = function _addIndex(hash, ts, height) {
var self = this;
if (this._probeIndex(hash, ts)) if (this._probeIndex(hash, ts))
return new Error('Already added.'); return new Error('Already added.');
var pos = utils.binaryInsert(this.index.ts, ts, compareTs, true); var pos = utils.binaryInsert(this.index.ts, ts, compareTs, true);
var checkpoint, obj;
// Avoid duplicates // Avoid duplicates
if (this.index.hashes[pos] === hash || if (this.index.hashes[pos] === hash
this.index.hashes[pos - 1] === hash || || this.index.hashes[pos - 1] === hash
this.index.hashes[pos + 1] === hash) { || this.index.hashes[pos + 1] === hash) {
return new Error('Duplicate height.'); return new Error('Duplicate height.');
} }
var checkpoint = network.checkpoints[height]; checkpoint = network.checkpoints[height];
if (checkpoint) { if (checkpoint) {
this.emit('checkpoint', height, hash, checkpoint); this.emit('checkpoint', height, hash, checkpoint);
if (hash !== checkpoint) { if (hash !== checkpoint) {
@ -171,8 +178,8 @@ Chain.prototype._addIndex = function _addIndex(hash, ts, height) {
if (!this.storage) if (!this.storage)
return; return;
var self = this; obj = { ts: ts, height: height };
var obj = { ts: ts, height: height };
this.storage.put(this.prefix + hash, obj, function(err) { this.storage.put(this.prefix + hash, obj, function(err) {
if (err) if (err)
self.emit('error', err); self.emit('error', err);
@ -195,14 +202,14 @@ Chain.prototype.resetHeight = function resetHeight(height) {
if (index < 0) if (index < 0)
throw new Error('Cannot reset to height of ' + height); throw new Error('Cannot reset to height of ' + height);
this.block.list = []; this.block.list.length = 0;
this.block.bloom.reset(); this.block.bloom.reset();
this.orphan.map = {}; this.orphan.map = {};
this.orphan.bmap = {}; this.orphan.bmap = {};
this.orphan.count = 0; this.orphan.count = 0;
this.index.ts = this.index.ts.slice(0, index + 1); this.index.ts.length = index + 1;
this.index.hashes = this.index.hashes.slice(0, index + 1); this.index.hashes.length = index + 1;
this.index.heights = this.index.heights.slice(0, index + 1); this.index.heights.length = index + 1;
this.index.bloom.reset(); this.index.bloom.reset();
this.index.hashes.forEach(function(hash) { this.index.hashes.forEach(function(hash) {
self.index.bloom.add(hash, 'hex'); self.index.bloom.add(hash, 'hex');
@ -211,13 +218,15 @@ Chain.prototype.resetHeight = function resetHeight(height) {
}; };
Chain.prototype._killFork = function _killFork(probe) { Chain.prototype._killFork = function _killFork(probe) {
var self = this;
var delta = 2 * 3600; var delta = 2 * 3600;
var upper = probe.ts + delta; var upper = probe.ts + delta;
var lower = probe.ts - delta; var lower = probe.ts - delta;
var index, i, len, hash;
// Search duplicate heights down // Search duplicate heights down
var index = -1; index = -1;
for (var i = probe.i - 1; i > 0 && this.index.ts[i] > lower; i--) { for (i = probe.i - 1; i > 0 && this.index.ts[i] > lower; i--) {
if (probe.height === this.index.heights[i]) { if (probe.height === this.index.heights[i]) {
index = i; index = i;
break; break;
@ -226,8 +235,8 @@ Chain.prototype._killFork = function _killFork(probe) {
// And up // And up
if (index === -1) { if (index === -1) {
var len = this.index.ts.length; len = this.index.ts.length;
for (var i = probe.i + 1; i < len && this.index.ts[i] < upper; i++) { for (i = probe.i + 1; i < len && this.index.ts[i] < upper; i++) {
if (probe.height === this.index.heights[i]) { if (probe.height === this.index.heights[i]) {
index = i; index = i;
break; break;
@ -238,7 +247,7 @@ Chain.prototype._killFork = function _killFork(probe) {
if (index === -1) if (index === -1)
return false; return false;
var hash = this.index.hashes[index]; hash = this.index.hashes[index];
this.index.hashes.splice(index, 1); this.index.hashes.splice(index, 1);
this.index.ts.splice(index, 1); this.index.ts.splice(index, 1);
this.index.heights.splice(index, 1); this.index.heights.splice(index, 1);
@ -247,7 +256,6 @@ Chain.prototype._killFork = function _killFork(probe) {
if (!this.storage) if (!this.storage)
return true; return true;
var self = this;
this.storage.del(this.prefix + hash, function(err) { this.storage.del(this.prefix + hash, function(err) {
if (err) if (err)
self.emit('error', err); self.emit('error', err);
@ -267,6 +275,8 @@ Chain.prototype.add = function add(block) {
var res = false; var res = false;
var err = null; var err = null;
var initial = block; var initial = block;
var hash, prev, prevProbe, range, hashes;
do { do {
// No need to revalidate orphans // No need to revalidate orphans
if (!res && !block.verify()) { if (!res && !block.verify()) {
@ -274,8 +284,8 @@ Chain.prototype.add = function add(block) {
break; break;
} }
var hash = block.hash('hex'); hash = block.hash('hex');
var prev = block.prevBlock; prev = block.prevBlock;
// If the block is already known to be an orphan // If the block is already known to be an orphan
if (this.orphan.map[prev]) { if (this.orphan.map[prev]) {
@ -283,7 +293,7 @@ Chain.prototype.add = function add(block) {
break; break;
} }
var prevProbe = this._probeIndex(prev, block.ts); prevProbe = this._probeIndex(prev, block.ts);
// Remove forked nodes from storage, if shorter chain is detected // Remove forked nodes from storage, if shorter chain is detected
if (this._killFork(prevProbe)) { if (this._killFork(prevProbe)) {
@ -297,8 +307,9 @@ Chain.prototype.add = function add(block) {
this.orphan.map[prev] = block; this.orphan.map[prev] = block;
this.orphan.bmap[hash] = block; this.orphan.bmap[hash] = block;
var range = this._getRange(hash, block.ts, true); range = this._getRange(hash, block.ts, true);
var hashes = this.index.hashes.slice(range.start, range.end + 1); hashes = this.index.hashes.slice(range.start, range.end + 1);
this.emit('missing', prev, hashes, block); this.emit('missing', prev, hashes, block);
break; break;
} }
@ -334,6 +345,8 @@ Chain.prototype.add = function add(block) {
}; };
Chain.prototype._compress = function compress() { Chain.prototype._compress = function compress() {
var i;
// Keep at least 1000 blocks and at most 2000 by default // Keep at least 1000 blocks and at most 2000 by default
if (this.block.list.length < this.cacheLimit) if (this.block.list.length < this.cacheLimit)
return; return;
@ -342,7 +355,7 @@ Chain.prototype._compress = function compress() {
this.block.list = this.block.list.slice(-(this.cacheLimit / 2 | 0)); this.block.list = this.block.list.slice(-(this.cacheLimit / 2 | 0));
this.block.bloom.reset(); 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]); this._bloomBlock(this.block.list[i]);
}; };
@ -351,21 +364,25 @@ Chain.prototype._bloomBlock = function _bloomBlock(block) {
}; };
Chain.prototype.has = function has(hash, noProbe, cb) { Chain.prototype.has = function has(hash, noProbe, cb) {
var i;
if (typeof noProbe === 'function') { if (typeof noProbe === 'function') {
cb = noProbe; cb = noProbe;
noProbe = false; noProbe = false;
} }
if (this.loading) { if (this.loading) {
this.once('load', function() { this.once('load', function() {
this.has(hash, noProbe, cb); this.has(hash, noProbe, cb);
}); });
return; return;
} }
cb = utils.asyncify(cb); cb = utils.asyncify(cb);
if (this.block.bloom.test(hash, 'hex')) { if (this.block.bloom.test(hash, 'hex')) {
if (this.strict) { 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) if (this.block.list[i].hash('hex') === hash)
return cb(true); return cb(true);
} else { } else {
@ -406,6 +423,8 @@ Chain.prototype.hasOrphan = function hasOrphan(hash) {
}; };
Chain.prototype.get = function get(hash, force, cb) { Chain.prototype.get = function get(hash, force, cb) {
var i, block;
if (typeof force === 'function') { if (typeof force === 'function') {
cb = force; cb = force;
force = false; force = false;
@ -413,11 +432,11 @@ Chain.prototype.get = function get(hash, force, cb) {
// Cached block found // Cached block found
if (!force && this.block.bloom.test(hash, 'hex')) { 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) { if (this.block.list[i].hash('hex') === hash) {
// NOTE: we return right after the statement - so `block` should be // NOTE: we return right after the statement - so `block` should be
// valid at the time of nextTick call // valid at the time of nextTick call
var block = this.block.list[i]; block = this.block.list[i];
bcoin.utils.nextTick(function() { bcoin.utils.nextTick(function() {
cb(block); cb(block);
}); });
@ -437,6 +456,7 @@ Chain.prototype.isFull = function isFull() {
return false; return false;
var delta = (+new Date() / 1000) - this.index.ts[this.index.ts.length - 1]; var delta = (+new Date() / 1000) - this.index.ts[this.index.ts.length - 1];
return delta < 40 * 60; return delta < 40 * 60;
}; };
@ -447,34 +467,42 @@ Chain.prototype.fillPercent = function fillPercent() {
}; };
Chain.prototype.hashesInRange = function hashesInRange(start, end, cb) { Chain.prototype.hashesInRange = function hashesInRange(start, end, cb) {
var ts, hashes, heights, zip, i, count;
if (this.loading) { if (this.loading) {
this.once('load', function() { this.once('load', function() {
this.hashesInRange(start, end, cb); this.hashesInRange(start, end, cb);
}); });
return; return;
} }
cb = utils.asyncify(cb); cb = utils.asyncify(cb);
var ts = this.index.ts; ts = this.index.ts;
start = utils.binaryInsert(ts, start, compareTs, true); start = utils.binaryInsert(ts, start, compareTs, true);
if (start > 0 && ts[start - 1] >= start) if (start > 0 && ts[start - 1] >= start)
start--; start--;
end = utils.binaryInsert(ts, end, compareTs, true); end = utils.binaryInsert(ts, end, compareTs, true);
// Zip hashes and heights together and sort them by height // Zip hashes and heights together and sort them by height
var hashes = this.index.hashes.slice(start, end); hashes = this.index.hashes.slice(start, end);
var heights = this.index.heights.slice(start, end); heights = this.index.heights.slice(start, end);
var zip = []; zip = [];
for (var i = 0; i < hashes.length; i++)
for (i = 0; i < hashes.length; i++)
zip.push({ hash: hashes[i], height: heights[i] }); zip.push({ hash: hashes[i], height: heights[i] });
zip = zip.sort(function(a, b) { zip = zip.sort(function(a, b) {
return a.height - b.height; return a.height - b.height;
}); });
var hashes = zip.map(function(a) {
hashes = zip.map(function(a) {
return a.hash; 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); return cb(hashes, count);
}; };
@ -538,14 +566,13 @@ Chain.prototype.locatorHashes = function(start) {
Chain.prototype.getOrphanRoot = function getOrphanRoot(hash) { Chain.prototype.getOrphanRoot = function getOrphanRoot(hash) {
var self = this; var self = this;
var root = hash;
if (Array.isArray(hash)) if (Array.isArray(hash))
hash = utils.toHex(hash); hash = utils.toHex(hash);
else if (hash.hash) else if (hash.hash)
hash = hash.hash('hex'); hash = hash.hash('hex');
var root = hash;
while (this.orphan.bmap[hash]) { while (this.orphan.bmap[hash]) {
root = hash; root = hash;
hash = this.orphan.bmap[hash].prevBlock; hash = this.orphan.bmap[hash].prevBlock;
@ -580,11 +607,17 @@ Chain.prototype.toJSON = function toJSON() {
var lastTs = 0; var lastTs = 0;
var lastHeight = -1000; var lastHeight = -1000;
for (var i = 0; i < this.index.ts.length - keep; i++) { var i, ts, delta, hdelta;
var ts = this.index.ts[i];
var delta = ts < 1356984000 ? delta1 : for (i = 0; i < this.index.ts.length - keep; i++) {
ts < 1388520000 ? delta2 : delta3; ts = this.index.ts[i];
var hdelta = this.index.heights[i] - lastHeight;
delta = ts < 1356984000
? delta1
: ts < 1388520000 ? delta2 : delta3;
hdelta = this.index.heights[i] - lastHeight;
if (ts - lastTs < delta && hdelta < 250) if (ts - lastTs < delta && hdelta < 250)
continue; continue;
@ -606,13 +639,18 @@ Chain.prototype.toJSON = function toJSON() {
}; };
Chain.prototype.fromJSON = function fromJSON(json) { Chain.prototype.fromJSON = function fromJSON(json) {
var i;
assert.equal(json.v, 1); assert.equal(json.v, 1);
assert.equal(json.type, 'chain'); assert.equal(json.type, 'chain');
if (json.network) if (json.network)
assert.equal(json.network, network.type); assert.equal(json.network, network.type);
this.index.hashes = json.hashes.slice(); this.index.hashes = json.hashes.slice();
this.index.ts = json.ts.slice(); this.index.ts = json.ts.slice();
this.index.heights = json.heights.slice(); this.index.heights = json.heights.slice();
if (this.index.bloom) if (this.index.bloom)
this.index.bloom.reset(); this.index.bloom.reset();
else else
@ -621,7 +659,8 @@ Chain.prototype.fromJSON = function fromJSON(json) {
if (this.index.hashes.length === 0) if (this.index.hashes.length === 0)
this.add(new bcoin.block(network.genesis, 'block')); 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'); this.index.bloom.add(this.index.hashes[i], 'hex');
}
}; };
module.exports = Chain;

View File

@ -96,13 +96,14 @@ HDSeed._entropy = function(size) {
HDSeed._mnemonic = function(entropy) { HDSeed._mnemonic = function(entropy) {
var bin = ''; var bin = '';
for (var i = 0; i < entropy.length; i++) {
bin = bin + ('00000000' + entropy[i].toString(2)).slice(-8);
}
var mnemonic = []; 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++) { 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]); mnemonic.push(english[wi]);
} }
@ -231,6 +232,7 @@ HDPriv.prototype._unbuild = function(xkey) {
var raw = utils.fromBase58(xkey); var raw = utils.fromBase58(xkey);
var data = {}; var data = {};
var off = 0; var off = 0;
var hash;
data.version = utils.readU32BE(raw, off); data.version = utils.readU32BE(raw, off);
off += 4; off += 4;
@ -248,7 +250,7 @@ HDPriv.prototype._unbuild = function(xkey) {
data.checksum = utils.readU32BE(raw, off); data.checksum = utils.readU32BE(raw, off);
off += 4; off += 4;
var hash = utils.dsha256(raw.slice(0, -4)); hash = utils.dsha256(raw.slice(0, -4));
if (data.checksum !== utils.readU32BE(hash, 0)) if (data.checksum !== utils.readU32BE(hash, 0))
throw new Error('checksum mismatch'); throw new Error('checksum mismatch');
@ -258,6 +260,7 @@ HDPriv.prototype._unbuild = function(xkey) {
HDPriv.prototype._build = function(data) { HDPriv.prototype._build = function(data) {
var sequence = []; var sequence = [];
var off = 0; var off = 0;
var checksum, xprivkey, pair, privateKey, publicKey, size, fingerPrint;
utils.writeU32BE(sequence, data.version, off); utils.writeU32BE(sequence, data.version, off);
off += 4; off += 4;
@ -273,18 +276,18 @@ HDPriv.prototype._build = function(data) {
off += 1; off += 1;
utils.copy(data.privateKey, sequence, off, true); utils.copy(data.privateKey, sequence, off, true);
off += data.privateKey.length; 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); utils.copy(checksum, sequence, off, true);
off += 4; off += 4;
var xprivkey = utils.toBase58(sequence); xprivkey = utils.toBase58(sequence);
var pair = bcoin.ecdsa.keyPair({ priv: data.privateKey }); pair = bcoin.ecdsa.keyPair({ priv: data.privateKey });
var privateKey = pair.getPrivate().toArray(); privateKey = pair.getPrivate().toArray();
var publicKey = pair.getPublic(true, 'array'); publicKey = pair.getPublic(true, 'array');
var size = PARENT_FINGER_PRINT_SIZE; size = PARENT_FINGER_PRINT_SIZE;
var fingerPrint = utils.ripesha(publicKey).slice(0, size); fingerPrint = utils.ripesha(publicKey).slice(0, size);
this.version = data.version; this.version = data.version;
this.depth = data.depth; this.depth = data.depth;
@ -307,22 +310,24 @@ HDPriv.prototype.derive = function(index, hard) {
if (typeof index === 'string') if (typeof index === 'string')
return this.deriveString(index); return this.deriveString(index);
var index_ = [];
var data, hash, leftPart, chainCode, privateKey;
hard = index >= HARDENED ? true : hard; hard = index >= HARDENED ? true : hard;
if (index < HARDENED && hard === true) if (index < HARDENED && hard === true)
index += HARDENED; index += HARDENED;
var index_ = [];
utils.writeU32BE(index_, index, 0); utils.writeU32BE(index_, index, 0);
var data = hard data = hard
? [0].concat(this.privateKey).concat(index_) ? [0].concat(this.privateKey).concat(index_)
: data = [].concat(this.publicKey).concat(index_); : data = [].concat(this.publicKey).concat(index_);
var hash = sha512hmac(data, this.chainCode); hash = sha512hmac(data, this.chainCode);
var leftPart = new bn(hash.slice(0, 32)); leftPart = new bn(hash.slice(0, 32));
var chainCode = hash.slice(32, 64); 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({ return new HDPriv({
master: this.master, master: this.master,
@ -340,6 +345,7 @@ HDPriv._getIndexes = function(path) {
var steps = path.split('/'); var steps = path.split('/');
var root = steps.shift(); var root = steps.shift();
var indexes = []; var indexes = [];
var i, step, hard, index;
if (~PATH_ROOTS.indexOf(path)) if (~PATH_ROOTS.indexOf(path))
return indexes; return indexes;
@ -347,9 +353,9 @@ HDPriv._getIndexes = function(path) {
if (!~PATH_ROOTS.indexOf(root)) if (!~PATH_ROOTS.indexOf(root))
return null; return null;
for (var i = 0; i < steps.length; i++) { for (i = 0; i < steps.length; i++) {
var step = steps[i]; step = steps[i];
var hard = step[step.length - 1] === '\''; hard = step[step.length - 1] === '\'';
if (hard) if (hard)
step = step.slice(0, -1); step = step.slice(0, -1);
@ -357,7 +363,7 @@ HDPriv._getIndexes = function(path) {
if (!step || step[0] === '-') if (!step || step[0] === '-')
return null; return null;
var index = +step; index = +step;
if (hard) if (hard)
index += HARDENED; index += HARDENED;
@ -399,18 +405,17 @@ HDPriv.prototype.deriveString = function(path) {
*/ */
function HDPub(options) { function HDPub(options) {
var data;
if (!(this instanceof HDPub)) if (!(this instanceof HDPub))
return new HDPub(options); return new HDPub(options);
var data;
if (typeof options === 'string' && options.indexOf('xpub') === 0) if (typeof options === 'string' && options.indexOf('xpub') === 0)
options = { xkey: options }; options = { xkey: options };
if (options.xkey) data = options.xkey
data = this._unbuild(options.xkey); ? this._unbuild(options.xkey)
else : options;
data = options;
data = this._normalize(data, network.prefixes.xpubkey); data = this._normalize(data, network.prefixes.xpubkey);
@ -425,6 +430,7 @@ HDPub.prototype._unbuild = function(xkey) {
var raw = utils.fromBase58(xkey); var raw = utils.fromBase58(xkey);
var data = {}; var data = {};
var off = 0; var off = 0;
var hash;
data.version = utils.readU32BE(raw, off); data.version = utils.readU32BE(raw, off);
off += 4; off += 4;
@ -441,7 +447,7 @@ HDPub.prototype._unbuild = function(xkey) {
data.checksum = utils.readU32BE(raw, off); data.checksum = utils.readU32BE(raw, off);
off += 4; off += 4;
var hash = utils.dsha256(raw.slice(0, -4)); hash = utils.dsha256(raw.slice(0, -4));
if (data.checksum !== utils.readU32BE(hash, 0)) if (data.checksum !== utils.readU32BE(hash, 0))
throw new Error('checksum mismatch'); throw new Error('checksum mismatch');
@ -451,6 +457,7 @@ HDPub.prototype._unbuild = function(xkey) {
HDPub.prototype._build = function(data) { HDPub.prototype._build = function(data) {
var sequence = []; var sequence = [];
var off = 0; var off = 0;
var checksum, xpubkey, publicKey, size, fingerPrint;
utils.writeU32BE(sequence, data.version, off); utils.writeU32BE(sequence, data.version, off);
off += 4; off += 4;
@ -464,7 +471,7 @@ HDPub.prototype._build = function(data) {
off += data.chainCode.length; off += data.chainCode.length;
utils.copy(data.publicKey, sequence, off, true); utils.copy(data.publicKey, sequence, off, true);
off += data.publicKey.length; 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); utils.copy(checksum, sequence, off, true);
off += 4; off += 4;
@ -473,11 +480,11 @@ HDPub.prototype._build = function(data) {
else if (utils.toHex(checksum) !== utils.toHex(data.checksum)) else if (utils.toHex(checksum) !== utils.toHex(data.checksum))
throw new Error('checksum mismatch'); throw new Error('checksum mismatch');
var xpubkey = utils.toBase58(sequence); xpubkey = utils.toBase58(sequence);
var publicKey = data.publicKey; publicKey = data.publicKey;
var size = PARENT_FINGER_PRINT_SIZE; size = PARENT_FINGER_PRINT_SIZE;
var fingerPrint = utils.ripesha(publicKey).slice(0, size); fingerPrint = utils.ripesha(publicKey).slice(0, size);
this.version = data.version; this.version = data.version;
this.depth = data.depth; this.depth = data.depth;
@ -495,6 +502,9 @@ HDPub.prototype._build = function(data) {
}; };
HDPub.prototype.derive = function(index, hard) { HDPub.prototype.derive = function(index, hard) {
var index_ = [];
var data, hash, leftPart, chainCode, pair, pubkeyPoint, publicKey;
if (typeof index === 'string') if (typeof index === 'string')
return this.deriveString(index); return this.deriveString(index);
@ -504,17 +514,16 @@ HDPub.prototype.derive = function(index, hard) {
if (index < 0) if (index < 0)
throw new Error('invalid path'); throw new Error('invalid path');
var index_ = [];
utils.writeU32BE(index_, index, 0); utils.writeU32BE(index_, index, 0);
var data = [].concat(this.publicKey).concat(index_); data = [].concat(this.publicKey).concat(index_);
var hash = sha512hmac(data, this.chainCode); hash = sha512hmac(data, this.chainCode);
var leftPart = new bn(hash.slice(0, 32)); leftPart = new bn(hash.slice(0, 32));
var chainCode = hash.slice(32, 64); chainCode = hash.slice(32, 64);
var pair = bcoin.ecdsa.keyPair({ pub: this.publicKey }); pair = bcoin.ecdsa.keyPair({ pub: this.publicKey });
var pubkeyPoint = ec.curve.g.mul(leftPart).add(pair.pub); pubkeyPoint = ec.curve.g.mul(leftPart).add(pair.pub);
var publicKey = bcoin.ecdsa.keyFromPublic(pubkeyPoint).getPublic(true, 'array'); publicKey = bcoin.ecdsa.keyFromPublic(pubkeyPoint).getPublic(true, 'array');
return new HDPub({ return new HDPub({
// version: network.prefixes.xpubkey, // version: network.prefixes.xpubkey,
@ -610,8 +619,8 @@ function sha512hmac(data, salt) {
function randomBytes(size) { function randomBytes(size) {
if (isBrowser) { if (isBrowser) {
var a = Uint8Array(size); var a = Uint8Array(size);
(window.crypto || window.msCrypto).getRandomValues(a);
var buf = new Array(size); var buf = new Array(size);
(window.crypto || window.msCrypto).getRandomValues(a);
utils.copy(a, buf, 0); utils.copy(a, buf, 0);
return buf; return buf;
} }
@ -629,6 +638,7 @@ function pbkdf2(key, salt, iterations, dkLen) {
'use strict'; 'use strict';
var hLen = 64; var hLen = 64;
if (dkLen > (Math.pow(2, 32) - 1) * hLen) if (dkLen > (Math.pow(2, 32) - 1) * hLen)
throw Error('Requested key length too long'); throw Error('Requested key length too long');
@ -652,9 +662,11 @@ function pbkdf2(key, salt, iterations, dkLen) {
var l = Math.ceil(dkLen / hLen); var l = Math.ceil(dkLen / hLen);
var r = dkLen - (l - 1) * hLen; var r = dkLen - (l - 1) * hLen;
var i, j, k, destPos, len;
utils.copy(salt.slice(0, salt.length), block1, 0); 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 + 0] = (i >> 24 & 0xff);
block1[salt.length + 1] = (i >> 16 & 0xff); block1[salt.length + 1] = (i >> 16 & 0xff);
block1[salt.length + 2] = (i >> 8 & 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); 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); U = sha512hmac(U, key);
for (var k = 0; k < hLen; k++) for (k = 0; k < hLen; k++)
T[k] ^= U[k]; T[k] ^= U[k];
} }
var destPos = (i - 1) * hLen; destPos = (i - 1) * hLen;
var len = (i === l ? r : hLen); len = (i === l ? r : hLen);
utils.copy(T.slice(0, len), DK, 0); utils.copy(T.slice(0, len), DK, 0);
} }

View File

@ -13,6 +13,8 @@ try {
} }
function Peer(pool, createSocket, options) { function Peer(pool, createSocket, options) {
var self = this;
if (!(this instanceof Peer)) if (!(this instanceof Peer))
return new Peer(pool, createSocket, options); return new Peer(pool, createSocket, options);
@ -31,7 +33,6 @@ function Peer(pool, createSocket, options) {
this.ts = this.options.ts || 0; this.ts = this.options.ts || 0;
if (this.options.backoff) { if (this.options.backoff) {
var self = this;
setTimeout(function() { setTimeout(function() {
self.socket = createSocket(self); self.socket = createSocket(self);
self.emit('socket'); self.emit('socket');
@ -65,26 +66,32 @@ function Peer(pool, createSocket, options) {
else else
this.once('socket', this._init); this.once('socket', this._init);
} }
inherits(Peer, EventEmitter); inherits(Peer, EventEmitter);
module.exports = Peer;
Peer.prototype._init = function init() { Peer.prototype._init = function init() {
var self = this; var self = this;
this.socket.once('connect', function() { this.socket.once('connect', function() {
self.ts = Date.now() / 1000 | 0; self.ts = Date.now() / 1000 | 0;
}); });
this.socket.once('error', function(err) { this.socket.once('error', function(err) {
self._error(err); self._error(err);
}); });
this.socket.once('close', function() { this.socket.once('close', function() {
self._error('socket hangup'); self._error('socket hangup');
}); });
this.socket.on('data', function(chunk) { this.socket.on('data', function(chunk) {
self.parser.feed(chunk); self.parser.feed(chunk);
}); });
this.parser.on('packet', function(packet) { this.parser.on('packet', function(packet) {
self._onPacket(packet); self._onPacket(packet);
}); });
this.parser.on('error', function(err) { this.parser.on('error', function(err) {
self._error(err); self._error(err);
}); });
@ -134,13 +141,16 @@ Peer.prototype.startSync = function startSync() {
}; };
Peer.prototype.broadcast = function broadcast(items) { Peer.prototype.broadcast = function broadcast(items) {
var self = this;
var result;
if (this.destroyed) if (this.destroyed)
return; return;
if (!Array.isArray(items)) if (!Array.isArray(items))
items = [ items ]; items = [ items ];
var self = this; result = items.map(function(item) {
var result = items.map(function(item) {
var key = item.hash('hex'); var key = item.hash('hex');
var old = this._broadcast.map[key]; var old = this._broadcast.map[key];
if (old) { if (old) {
@ -195,8 +205,11 @@ Peer.prototype.updateWatch = function updateWatch() {
}; };
Peer.prototype.destroy = function destroy() { Peer.prototype.destroy = function destroy() {
var i;
if (this.destroyed) if (this.destroyed)
return; return;
if (!this.socket) if (!this.socket)
return this.once('socket', this.destroy); return this.once('socket', this.destroy);
@ -214,7 +227,7 @@ Peer.prototype.destroy = function destroy() {
clearInterval(this._ping.timer); clearInterval(this._ping.timer);
this._ping.timer = null; 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); clearTimeout(this._request.queue[i].timer);
}; };
@ -223,11 +236,13 @@ Peer.prototype.destroy = function destroy() {
Peer.prototype._write = function write(chunk) { Peer.prototype._write = function write(chunk) {
if (this.destroyed) if (this.destroyed)
return; return;
if (!this.socket) { if (!this.socket) {
return this.once('socket', function() { return this.once('socket', function() {
this._write(chunk); this._write(chunk);
}); });
} }
if (NodeBuffer) if (NodeBuffer)
this.socket.write(new NodeBuffer(chunk)); this.socket.write(new NodeBuffer(chunk));
else else
@ -237,15 +252,17 @@ Peer.prototype._write = function write(chunk) {
Peer.prototype._error = function error(err) { Peer.prototype._error = function error(err) {
if (this.destroyed) if (this.destroyed)
return; return;
this.destroy(); this.destroy();
this.emit('error', typeof err === 'string' ? new Error(err) : err); this.emit('error', typeof err === 'string' ? new Error(err) : err);
}; };
Peer.prototype._req = function _req(cmd, cb) { Peer.prototype._req = function _req(cmd, cb) {
var self = this;
if (this.destroyed) if (this.destroyed)
return cb(new Error('Destroyed, sorry')); return cb(new Error('Destroyed, sorry'));
var self = this;
var entry = { var entry = {
cmd: cmd, cmd: cmd,
cb: cb, cb: cb,
@ -258,19 +275,24 @@ Peer.prototype._req = function _req(cmd, cb) {
}, },
timer: null timer: null
}; };
entry.timer = setTimeout(entry.ontimeout, this._request.timeout); entry.timer = setTimeout(entry.ontimeout, this._request.timeout);
this._request.queue.push(entry); this._request.queue.push(entry);
return entry; return entry;
}; };
Peer.prototype._res = function _res(cmd, payload) { Peer.prototype._res = function _res(cmd, payload) {
for (var i = 0; i < this._request.queue.length; i++) { var i, entry, res;
var entry = this._request.queue[i];
for (i = 0; i < this._request.queue.length; i++) {
entry = this._request.queue[i];
if (!entry || entry.cmd && entry.cmd !== cmd) if (!entry || entry.cmd && entry.cmd !== cmd)
return false; return false;
var res = entry.cb(null, payload, cmd); res = entry.cb(null, payload, cmd);
if (res === this._request.cont) { if (res === this._request.cont) {
assert(!entry.cmd); assert(!entry.cmd);
@ -388,6 +410,7 @@ Peer.prototype._handleGetAddr = function handleGetAddr() {
this.pool.peers.block, this.pool.peers.block,
this.pool.peers.load this.pool.peers.load
).filter(Boolean); ).filter(Boolean);
var addrs;
// NOTE: For IPv6 BTC uses: // NOTE: For IPv6 BTC uses:
// '0000:0000:0000:0000:0000:xxxx:xxxx:ffff' // '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) { if (peer.ver === 6) {
while (peer.ipv6.split(':').length < 8) while (peer.ipv6.split(':').length < 8)
peer.ipv6 = '0000:' + peer.ipv6; peer.ipv6 = '0000:' + peer.ipv6;
@ -450,16 +473,18 @@ Peer.prototype._handleInv = function handleInv(items) {
return item.hash; return item.hash;
}); });
var req, i, block, hash;
if (blocks.length === 1) if (blocks.length === 1)
this.bestBlock = utils.toHex(blocks[0]); this.bestBlock = utils.toHex(blocks[0]);
this.emit('blocks', blocks); this.emit('blocks', blocks);
if (this.pool.options.fullNode) { if (this.pool.options.fullNode) {
var req = []; req = [];
for (var i = 0; i < blocks.length; i++) { for (i = 0; i < blocks.length; i++) {
var block = blocks[i]; block = blocks[i];
var hash = utils.toHex(block); hash = utils.toHex(block);
if (this.chain.hasOrphan(hash)) { if (this.chain.hasOrphan(hash)) {
this.loadBlocks(this.chain.locatorHashes(), this.chain.getOrphanRoot(hash)); this.loadBlocks(this.chain.locatorHashes(), this.chain.getOrphanRoot(hash));
continue; continue;
@ -507,3 +532,5 @@ Peer.prototype.loadHeaders = function loadHeaders(hashes, stop) {
Peer.prototype.loadBlocks = function loadBlocks(hashes, stop) { Peer.prototype.loadBlocks = function loadBlocks(hashes, stop) {
this._write(this.framer.getBlocks(hashes, stop)); this._write(this.framer.getBlocks(hashes, stop));
}; };
module.exports = Peer;

View File

@ -29,10 +29,12 @@ function Pool(options) {
this.size = options.size || 32; this.size = options.size || 32;
this.parallel = options.parallel || 2000; this.parallel = options.parallel || 2000;
this.redundancy = options.redundancy || 2; this.redundancy = options.redundancy || 2;
this.backoff = { this.backoff = {
delta: options.backoffDelta || 500, delta: options.backoffDelta || 500,
max: options.backoffMax || 5000 max: options.backoffMax || 5000
}; };
this.load = { this.load = {
timeout: options.loadTimeout || 3000, timeout: options.loadTimeout || 3000,
interval: options.loadInterval || 5000, interval: options.loadInterval || 5000,
@ -44,8 +46,10 @@ function Pool(options) {
hwm: options.hwm || this.parallel * 8, hwm: options.hwm || this.parallel * 8,
hiReached: false hiReached: false
}; };
this.maxRetries = options.maxRetries || 42; this.maxRetries = options.maxRetries || 42;
this.requestTimeout = options.requestTimeout || 10000; this.requestTimeout = options.requestTimeout || 10000;
this.chain = new bcoin.chain({ this.chain = new bcoin.chain({
storage: this.storage, storage: this.storage,
// Since regular blocks contain transactions and full merkle // Since regular blocks contain transactions and full merkle
@ -54,10 +58,14 @@ function Pool(options) {
fullNode: this.options.fullNode, fullNode: this.options.fullNode,
startHeight: this.options.startHeight startHeight: this.options.startHeight
}); });
this.watchMap = {}; this.watchMap = {};
this.bloom = new bcoin.bloom(8 * 1024,
10, this.bloom = new bcoin.bloom(
(Math.random() * 0xffffffff) | 0); 8 * 1024,
10,
(Math.random() * 0xffffffff) | 0
);
this.bestHeight = 0; this.bestHeight = 0;
this.bestBlock = null; this.bestBlock = null;
@ -72,14 +80,17 @@ function Pool(options) {
// Peers that are loading block ids // Peers that are loading block ids
load: null load: null
}; };
this.block = { this.block = {
lastHash: null lastHash: null
}; };
this.request = { this.request = {
map: {}, map: {},
active: 0, active: 0,
queue: [] queue: []
}; };
this.validate = { this.validate = {
// 5 days scan delta for obtaining TXs // 5 days scan delta for obtaining TXs
delta: 5 * 24 * 3600, delta: 5 * 24 * 3600,
@ -117,15 +128,18 @@ function Pool(options) {
}); });
} }
} }
inherits(Pool, EventEmitter); inherits(Pool, EventEmitter);
module.exports = Pool;
Pool.prototype._init = function _init() { Pool.prototype._init = function _init() {
var self = this; var self = this;
var i;
this._addLoader(); this._addLoader();
for (var i = 0; i < this.size; i++)
for (i = 0; i < this.size; i++)
this._addPeer(0); this._addPeer(0);
this._load(); this._load();
this.chain.on('missing', function(hash, preload, parent) { this.chain.on('missing', function(hash, preload, parent) {
@ -141,19 +155,22 @@ Pool.prototype._init = function _init() {
}; };
Pool.prototype._addLoader = function _addLoader() { Pool.prototype._addLoader = function _addLoader() {
var self = this;
var peer, interval, timer;
if (this.destroyed) if (this.destroyed)
return; return;
if (this.peers.load !== null) if (this.peers.load !== null)
return; return;
var peer = new bcoin.peer(this, this.createSocket, { peer = new bcoin.peer(this, this.createSocket, {
backoff: 750 * Math.random(), backoff: 750 * Math.random(),
startHeight: this.options.startHeight, startHeight: this.options.startHeight,
relay: this.options.relay relay: this.options.relay
}); });
this.peers.load = peer; this.peers.load = peer;
var self = this;
peer.on('error', function(err) { peer.on('error', function(err) {
self.emit('error', err, peer); self.emit('error', err, peer);
}); });
@ -168,7 +185,7 @@ Pool.prototype._addLoader = function _addLoader() {
self._addLoader(); self._addLoader();
} }
var interval = setInterval(function() { interval = setInterval(function() {
self._load(); self._load();
}, this.load.interval); }, this.load.interval);
@ -178,7 +195,8 @@ Pool.prototype._addLoader = function _addLoader() {
clearTimeout(timer); clearTimeout(timer);
}); });
if (this.options.fullNode) return; if (this.options.fullNode)
return;
function destroy() { function destroy() {
// Chain is full and up-to-date // Chain is full and up-to-date
@ -191,7 +209,8 @@ Pool.prototype._addLoader = function _addLoader() {
peer.destroy(); peer.destroy();
} }
var timer = setTimeout(destroy, this.load.timeout);
timer = setTimeout(destroy, this.load.timeout);
// Split blocks and request them using multiple peers // Split blocks and request them using multiple peers
peer.on('blocks', function(hashes) { peer.on('blocks', function(hashes) {
@ -236,7 +255,11 @@ Pool.prototype.isFull = function isFull() {
}; };
Pool.prototype._loadRange = function _loadRange(hashes, force) { 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) if (!hashes)
return; return;
@ -245,29 +268,33 @@ Pool.prototype._loadRange = function _loadRange(hashes, force) {
return; return;
// Limit number of requests // Limit number of requests
var now = +new Date();
if (!force && now - this.load.lastRange < this.load.rangeWindow) if (!force && now - this.load.lastRange < this.load.rangeWindow)
return; return;
this.load.lastRange = now; this.load.lastRange = now;
if (!this.peers.load) if (!this.peers.load)
this._addLoader(); this._addLoader();
var last = hashes[hashes.length - 1]; last = hashes[hashes.length - 1];
hashes.slice(0, -1).forEach(function(hash) { hashes.slice(0, -1).forEach(function(hash) {
this.peers.load.loadBlocks([ hash ], last); this.peers.load.loadBlocks([ hash ], last);
}, this); }, this);
}; };
Pool.prototype._load = function _load() { 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) { if (this.request.queue.length >= this.load.hwm) {
this.load.hiReached = true; this.load.hiReached = true;
return false; return false;
} }
this.load.hiReached = false; this.load.hiReached = false;
var self = this;
// Load more blocks, starting from last hash // Load more blocks, starting from last hash
if (this.block.lastHash) if (this.block.lastHash)
@ -286,12 +313,16 @@ Pool.prototype._load = function _load() {
}; };
Pool.prototype._addPeer = function _addPeer(backoff) { Pool.prototype._addPeer = function _addPeer(backoff) {
var self = this;
var peer;
if (this.destroyed) if (this.destroyed)
return; return;
if (this.peers.block.length + this.peers.pending.length >= this.size) if (this.peers.block.length + this.peers.pending.length >= this.size)
return; return;
var peer = new bcoin.peer(this, this.createSocket, { peer = new bcoin.peer(this, this.createSocket, {
backoff: backoff, backoff: backoff,
startHeight: this.options.startHeight, startHeight: this.options.startHeight,
relay: this.options.relay relay: this.options.relay
@ -302,7 +333,6 @@ Pool.prototype._addPeer = function _addPeer(backoff) {
peer._retry = 0; peer._retry = 0;
// Create new peer on failure // Create new peer on failure
var self = this;
peer.on('error', function(err) { peer.on('error', function(err) {
self.emit('error', err, peer); self.emit('error', err, peer);
}); });
@ -315,10 +345,12 @@ Pool.prototype._addPeer = function _addPeer(backoff) {
}); });
peer.once('ack', function() { peer.once('ack', function() {
var i;
if (self.destroyed) if (self.destroyed)
return; return;
var i = self.peers.pending.indexOf(peer); i = self.peers.pending.indexOf(peer);
if (i !== -1) { if (i !== -1) {
self.peers.pending.splice(i, 1); self.peers.pending.splice(i, 1);
self.peers.block.push(peer); self.peers.block.push(peer);
@ -349,6 +381,9 @@ Pool.prototype._addPeer = function _addPeer(backoff) {
}); });
} else { } else {
peer.on('block', function(block) { peer.on('block', function(block) {
var hashes = self.chain.index.hashes;
var hash, len, orphan, err;
if (self.syncPeer !== peer) if (self.syncPeer !== peer)
return; return;
@ -356,11 +391,11 @@ Pool.prototype._addPeer = function _addPeer(backoff) {
self._response(block); self._response(block);
var hash = block.hash('hex'); hash = block.hash('hex');
var len = self.chain.index.hashes.length; len = hashes.length;
var orphan = self.chain.hasOrphan(block); orphan = self.chain.hasOrphan(block);
var err = self.chain.add(block); err = self.chain.add(block);
if (err) if (err)
self.emit('chain-error', err, peer); self.emit('chain-error', err, peer);
@ -373,12 +408,10 @@ Pool.prototype._addPeer = function _addPeer(backoff) {
return; return;
} }
if (self.chain.index.hashes.length === len) if (hashes.length === len)
return; return;
var top = self.chain.index.hashes[self.chain.index.hashes.length - 1]; self.needSync = hashes[hashes.length - 1] !== self.bestBlock;
self.needSync = top !== self.bestBlock;
self.emit('chain-progress', self.chain.fillPercent(), peer); self.emit('chain-progress', self.chain.fillPercent(), peer);
self.emit('block', block, peer); self.emit('block', block, peer);
@ -386,9 +419,9 @@ Pool.prototype._addPeer = function _addPeer(backoff) {
} }
this.chain.on('fork', function(height, hash, checkpoint) { this.chain.on('fork', function(height, hash, checkpoint) {
if (!self.syncPeer)
return;
var peer = self.syncPeer; var peer = self.syncPeer;
if (!peer)
return;
delete self.syncPeer; delete self.syncPeer;
peer.destroy(); peer.destroy();
self.startSync(); self.startSync();
@ -450,6 +483,7 @@ Pool.prototype.bestPeer = function bestPeer() {
this.peers.block.forEach(function(peer) { this.peers.block.forEach(function(peer) {
if (!peer.version || !peer.socket) if (!peer.version || !peer.socket)
return; return;
if (!best || peer.version.height > best.version.height) if (!best || peer.version.height > best.version.height)
best = peer; best = peer;
}); });
@ -495,13 +529,15 @@ Pool.prototype._removePeer = function _removePeer(peer) {
}; };
Pool.prototype.watch = function watch(id) { Pool.prototype.watch = function watch(id) {
var hid, i;
if (id instanceof bcoin.wallet) { if (id instanceof bcoin.wallet) {
this.watchWallet(id); this.watchWallet(id);
return; return;
} }
if (id) { if (id) {
var hid = utils.toHex(id); hid = utils.toHex(id);
if (this.watchMap[hid]) if (this.watchMap[hid])
this.watchMap[hid]++; this.watchMap[hid]++;
else else
@ -515,11 +551,14 @@ Pool.prototype.watch = function watch(id) {
if (this.peers.load) if (this.peers.load)
this.peers.load.updateWatch(); 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(); this.peers.block[i].updateWatch();
}; };
Pool.prototype.unwatch = function unwatch(id) { Pool.prototype.unwatch = function unwatch(id) {
var i;
if (!this.bloom.test(id, 'hex')) if (!this.bloom.test(id, 'hex'))
return; return;
@ -538,17 +577,21 @@ Pool.prototype.unwatch = function unwatch(id) {
// Resend it to peers // Resend it to peers
if (this.peers.load) if (this.peers.load)
this.peers.load.updateWatch(); 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(); this.peers.block[i].updateWatch();
}; };
Pool.prototype.addWallet = function addWallet(w, defaultTs) { Pool.prototype.addWallet = function addWallet(w, defaultTs) {
var self = this;
var e;
if (this.wallets.indexOf(w) !== -1) if (this.wallets.indexOf(w) !== -1)
return false; return false;
this.watchWallet(w); this.watchWallet(w);
var self = this; e = new EventEmitter();
var e = new EventEmitter();
if (w.loaded) if (w.loaded)
search(w.lastTs); search(w.lastTs);
else else
@ -606,16 +649,19 @@ Pool.prototype.unwatchWallet = function unwatchWallet(w) {
}; };
Pool.prototype.search = function search(id, range, e) { Pool.prototype.search = function search(id, range, e) {
var self = this;
e = e || new EventEmitter(); e = e || new EventEmitter();
// Optional id argument // Optional id argument
if (id !== null && if (id !== null
typeof id === 'object' && && typeof id === 'object'
!Array.isArray(id) || && !Array.isArray(id)
typeof id === 'number') { || typeof id === 'number') {
range = id; range = id;
id = null; id = null;
} }
if (typeof id === 'string') if (typeof id === 'string')
id = utils.toArray(id, 'hex'); id = utils.toArray(id, 'hex');
@ -633,7 +679,6 @@ Pool.prototype.search = function search(id, range, e) {
if (!range.start) if (!range.start)
range.start = +new Date() / 1000 - 432000; range.start = +new Date() / 1000 - 432000;
var self = this;
this.chain.hashesInRange(range.start, range.end, function(hashes, count) { this.chain.hashesInRange(range.start, range.end, function(hashes, count) {
var waiting = 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) { Pool.prototype._request = function _request(type, hash, options, cb) {
var self = this;
// Optional `force` // Optional `force`
if (typeof options === 'function') { if (typeof options === 'function') {
cb = options; cb = options;
@ -703,8 +750,6 @@ Pool.prototype._request = function _request(type, hash, options, cb) {
if (this.request.map[hash]) if (this.request.map[hash])
return this.request.map[hash].addCallback(cb); return this.request.map[hash].addCallback(cb);
var self = this;
// Block should be not in chain, or be requested // Block should be not in chain, or be requested
if (!options.force && type === 'block') if (!options.force && type === 'block')
return this.chain.has(hash, true, next); return this.chain.has(hash, true, next);
@ -732,8 +777,11 @@ Pool.prototype._response = function _response(entity) {
}; };
Pool.prototype._scheduleRequests = function _scheduleRequests() { Pool.prototype._scheduleRequests = function _scheduleRequests() {
var self = this;
if (this.destroyed) if (this.destroyed)
return; return;
if (this.request.active > this.parallel / 2) if (this.request.active > this.parallel / 2)
return; return;
@ -749,7 +797,6 @@ Pool.prototype._scheduleRequests = function _scheduleRequests() {
if (this.load.timer !== null) if (this.load.timer !== null)
return; return;
var self = this;
this.load.timer = setTimeout(function() { this.load.timer = setTimeout(function() {
self.load.timer = null; self.load.timer = null;
self._doRequests(); self._doRequests();
@ -757,6 +804,9 @@ Pool.prototype._scheduleRequests = function _scheduleRequests() {
}; };
Pool.prototype._doRequests = function _doRequests() { Pool.prototype._doRequests = function _doRequests() {
var queue, above, items, below;
var red, count, split, i, off, req, j;
if (this.request.active >= this.parallel) if (this.request.active >= this.parallel)
return; return;
@ -764,11 +814,11 @@ Pool.prototype._doRequests = function _doRequests() {
if (this.peers.block.length === 0) if (this.peers.block.length === 0)
return; return;
var queue = this.request.queue; queue = this.request.queue;
var above = queue.length >= this.load.lwm; above = queue.length >= this.load.lwm;
var items = queue.slice(0, this.parallel - this.request.active); items = queue.slice(0, this.parallel - this.request.active);
this.request.queue = queue.slice(items.length); 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 // Watermark boundary crossed, load more blocks
if (above && below && this.load.hiReached) if (above && below && this.load.hiReached)
@ -779,19 +829,20 @@ Pool.prototype._doRequests = function _doRequests() {
} }
// Split list between peers // Split list between peers
var red = this.redundancy; red = this.redundancy;
var count = this.peers.block.length; count = this.peers.block.length;
var split = Math.ceil(items.length * red / count); split = Math.ceil(items.length * red / count);
for (var i = 0, off = 0; i < count; i += red, off += split) { for (i = 0, off = 0; i < count; i += red, off += split) {
var req = items.slice(off, off + split).map(mapReq, this); req = items.slice(off, off + split).map(mapReq, this);
for (j = 0; j < red && i + j < count; j++)
for (var j = 0; j < red && i + j < count; j++) {
this.peers.block[i + j].getData(req); this.peers.block[i + j].getData(req);
}
} }
}; };
Pool.prototype.getTX = function getTX(hash, range, cb) { Pool.prototype.getTX = function getTX(hash, range, cb) {
var self = this;
var cbs, tx, finished, req, delta;
hash = utils.toHex(hash); hash = utils.toHex(hash);
if (typeof range === 'function') { if (typeof range === 'function') {
@ -802,19 +853,20 @@ Pool.prototype.getTX = function getTX(hash, range, cb) {
// Do not perform duplicate searches // Do not perform duplicate searches
if (this.validate.map[hash]) if (this.validate.map[hash])
return this.validate.map[hash].push(cb); return this.validate.map[hash].push(cb);
var cbs = [ cb ];
cbs = [ cb ];
this.validate.map[hash] = cbs; this.validate.map[hash] = cbs;
// Add request without queueing it to get notification at the time of load // Add request without queueing it to get notification at the time of load
var tx = null; tx = null;
var finished = false; finished = false;
var req = this._request('tx', hash, { noQueue: true }, function(t) { req = this._request('tx', hash, { noQueue: true }, function(t) {
finished = true; finished = true;
tx = t; tx = t;
}); });
// Do incremental search until the TX is found // Do incremental search until the TX is found
var delta = this.validate.delta; delta = this.validate.delta;
// Start from the existing range if given // Start from the existing range if given
if (range) if (range)
@ -822,9 +874,6 @@ Pool.prototype.getTX = function getTX(hash, range, cb) {
else else
range = { start: (+new Date() / 1000) - delta, end: 0 }; range = { start: (+new Date() / 1000) - delta, end: 0 };
var self = this;
doSearch();
function doSearch() { function doSearch() {
var e = self.search(hash, range); var e = self.search(hash, range);
e.on('end', function(empty) { e.on('end', function(empty) {
@ -849,12 +898,14 @@ Pool.prototype.getTX = function getTX(hash, range, cb) {
doSearch(); doSearch();
}); });
} }
doSearch();
}; };
Pool.prototype.sendTX = function sendTX(tx) { Pool.prototype.sendTX = function sendTX(tx) {
var self = this;
var e = new EventEmitter(); var e = new EventEmitter();
var self = this;
var entry = { var entry = {
tx: tx, tx: tx,
e: e, e: e,
@ -864,6 +915,7 @@ Pool.prototype.sendTX = function sendTX(tx) {
self.tx.list.splice(i, 1); self.tx.list.splice(i, 1);
}, this.tx.timeout) }, this.tx.timeout)
}; };
this.tx.list.push(entry); this.tx.list.push(entry);
this.peers.block.forEach(function(peer) { this.peers.block.forEach(function(peer) {
@ -880,6 +932,7 @@ Pool.prototype.sendTX = function sendTX(tx) {
Pool.prototype.destroy = function destroy() { Pool.prototype.destroy = function destroy() {
if (this.destroyed) if (this.destroyed)
return; return;
this.destroyed = true; this.destroyed = true;
if (this.peers.load) if (this.peers.load)
@ -888,18 +941,23 @@ Pool.prototype.destroy = function destroy() {
this.request.queue.slice().forEach(function(item) { this.request.queue.slice().forEach(function(item) {
item.finish(null); item.finish(null);
}); });
this.tx.list.forEach(function(tx) { this.tx.list.forEach(function(tx) {
clearTimeout(tx.timer); clearTimeout(tx.timer);
tx.timer = null; tx.timer = null;
}); });
this.peers.pending.slice().forEach(function(peer) { this.peers.pending.slice().forEach(function(peer) {
peer.destroy(); peer.destroy();
}); });
this.peers.block.slice().forEach(function(peer) { this.peers.block.slice().forEach(function(peer) {
peer.destroy(); peer.destroy();
}); });
if (this.load.timer) if (this.load.timer)
clearTimeout(this.load.timer); clearTimeout(this.load.timer);
this.load.timer = null; this.load.timer = null;
}; };
@ -920,6 +978,8 @@ Pool.prototype.fromJSON = function fromJSON(json) {
}; };
function LoadRequest(pool, type, hash, cb) { function LoadRequest(pool, type, hash, cb) {
var self = this;
this.pool = pool; this.pool = pool;
this.type = type; this.type = type;
this.hash = hash; this.hash = hash;
@ -930,7 +990,6 @@ function LoadRequest(pool, type, hash, cb) {
this.active = false; this.active = false;
this.noQueue = false; this.noQueue = false;
var self = this;
this.onclose = function onclose() { this.onclose = function onclose() {
if (self.pool.destroyed) if (self.pool.destroyed)
self.clear(); self.clear();
@ -941,8 +1000,9 @@ function LoadRequest(pool, type, hash, cb) {
LoadRequest.prototype.start = function start(peer) { LoadRequest.prototype.start = function start(peer) {
var self = this; var self = this;
assert(!this.active); var reqType;
assert(!this.active);
this.active = true; this.active = true;
this.pool.request.active++; this.pool.request.active++;
@ -956,7 +1016,6 @@ LoadRequest.prototype.start = function start(peer) {
this.peer = peer; this.peer = peer;
this.peer.once('close', this.onclose); this.peer.once('close', this.onclose);
var reqType;
if (this.type === 'block') if (this.type === 'block')
reqType = 'filtered'; reqType = 'filtered';
else if (this.type === 'tx') else if (this.type === 'tx')
@ -991,9 +1050,11 @@ LoadRequest.prototype.clear = function clear() {
}; };
LoadRequest.prototype.retry = function retry() { LoadRequest.prototype.retry = function retry() {
var peer = this.peer;
// Put block into the queue, ensure that the queue is always sorted by ts // Put block into the queue, ensure that the queue is always sorted by ts
utils.binaryInsert(this.pool.request.queue, this, LoadRequest.compare); utils.binaryInsert(this.pool.request.queue, this, LoadRequest.compare);
var peer = this.peer;
this.clear(); this.clear();
// Kill peer, if it misbehaves // Kill peer, if it misbehaves
@ -1005,12 +1066,14 @@ LoadRequest.prototype.retry = function retry() {
}; };
LoadRequest.prototype.finish = function finish(entity) { LoadRequest.prototype.finish = function finish(entity) {
var index;
if (this.active) { if (this.active) {
this.clear(); this.clear();
} else { } else {
// It could be that request was never sent to the node, remove it from // It could be that request was never sent to the node, remove it from
// queue and forget about it // 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); assert(index !== -1 || this.noQueue);
if (!this.noQueue) if (!this.noQueue)
this.pool.request.queue.splice(index, 1); this.pool.request.queue.splice(index, 1);
@ -1031,3 +1094,5 @@ LoadRequest.prototype.addCallback = function addCallback(cb) {
if (cb) if (cb)
this.cbs.push(cb); this.cbs.push(cb);
}; };
module.exports = Pool;

View File

@ -1,6 +1,8 @@
var bcoin = require('../../bcoin'); var bcoin = require('../../bcoin');
var utils = bcoin.utils; var utils = bcoin.utils;
var i;
exports.minVersion = 70001; exports.minVersion = 70001;
exports.version = 70002; exports.version = 70002;
@ -119,10 +121,11 @@ exports.opcodes = {
}; };
exports.opcodes['-1'] = 0x50 + -1; exports.opcodes['-1'] = 0x50 + -1;
for (var i = 1; i <= 16; i++)
for (i = 1; i <= 16; i++)
exports.opcodes[i] = 0x50 + 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.opcodes['nop' + (i + 3)] = 0xb2 + i;
exports.opcodesByVal = new Array(256); exports.opcodesByVal = new Array(256);

View File

@ -19,20 +19,20 @@ function Framer(options) {
this.agent = utils.toArray(options.agent || '/bcoin:' + version + '/'); this.agent = utils.toArray(options.agent || '/bcoin:' + version + '/');
this.agent = this.agent.slice(0, 0xfc); this.agent = this.agent.slice(0, 0xfc);
} }
module.exports = Framer;
Framer.prototype.header = function header(cmd, payload) { Framer.prototype.header = function header(cmd, payload) {
var h = new Array(24);
var len, i;
assert(cmd.length < 12); assert(cmd.length < 12);
assert(payload.length <= 0xffffffff); assert(payload.length <= 0xffffffff);
var h = new Array(24);
// Magic value // Magic value
writeU32(h, network.magic, 0); writeU32(h, network.magic, 0);
// Command // Command
var len = writeAscii(h, cmd, 4); len = writeAscii(h, cmd, 4);
for (var i = 4 + len; i < 4 + 12; i++) for (i = 4 + len; i < 4 + 12; i++)
h[i] = 0; h[i] = 0;
// Payload length // Payload length
@ -64,6 +64,7 @@ Framer.prototype._addr = function addr(buf, off) {
Framer.prototype.version = function version(packet) { Framer.prototype.version = function version(packet) {
var p = new Array(86 + this.agent.length); var p = new Array(86 + this.agent.length);
var off = 0; var off = 0;
var ts, i;
if (!packet) if (!packet)
packet = {}; packet = {};
@ -76,7 +77,7 @@ Framer.prototype.version = function version(packet) {
off += writeU32(p, 0, off); off += writeU32(p, 0, off);
// Timestamp // Timestamp
var ts = ((+new Date()) / 1000) | 0; ts = ((+new Date()) / 1000) | 0;
off += writeU32(p, ts, off); off += writeU32(p, ts, off);
off += writeU32(p, 0, off); off += writeU32(p, 0, off);
@ -94,7 +95,7 @@ Framer.prototype.version = function version(packet) {
p[off++] = 0; p[off++] = 0;
} else { } else {
off += varint(p, this.agent.length, off); 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]; p[off++] = this.agent[i];
} }
@ -139,14 +140,16 @@ function varint(arr, value, off) {
Framer.prototype._inv = function _inv(command, items) { Framer.prototype._inv = function _inv(command, items) {
var res = []; var res = [];
var off = varint(res, items.length, 0); var off = varint(res, items.length, 0);
var i, hash;
assert(items.length <= 50000); assert(items.length <= 50000);
for (var i = 0; i < items.length; i++) { for (i = 0; i < items.length; i++) {
// Type // Type
off += writeU32(res, constants.inv[items[i].type], off); off += writeU32(res, constants.inv[items[i].type], off);
// Hash // Hash
var hash = items[i].hash; hash = items[i].hash;
if (typeof hash === 'string') if (typeof hash === 'string')
hash = utils.toArray(hash, 'hex'); hash = utils.toArray(hash, 'hex');
assert.equal(hash.length, 32); assert.equal(hash.length, 32);
@ -181,10 +184,10 @@ Framer.prototype.pong = function pong(nonce) {
Framer.prototype.filterLoad = function filterLoad(bloom, update) { Framer.prototype.filterLoad = function filterLoad(bloom, update) {
var filter = bloom.toArray(); var filter = bloom.toArray();
var before = []; var before = [];
varint(before, filter.length, 0);
var after = new Array(9); var after = new Array(9);
varint(before, filter.length, 0);
// Number of hash functions // Number of hash functions
writeU32(after, bloom.n, 0); writeU32(after, bloom.n, 0);
@ -194,8 +197,7 @@ Framer.prototype.filterLoad = function filterLoad(bloom, update) {
// nFlags // nFlags
after[8] = constants.filterFlags[update]; after[8] = constants.filterFlags[update];
var r = this.packet('filterload', before.concat(filter, after)); return this.packet('filterload', before.concat(filter, after));
return r;
}; };
Framer.prototype.filterClear = function filterClear() { Framer.prototype.filterClear = function filterClear() {
@ -212,28 +214,36 @@ Framer.prototype.getBlocks = function getBlocks(hashes, stop) {
Framer.prototype._getBlocks = function _getBlocks(cmd, hashes, stop) { Framer.prototype._getBlocks = function _getBlocks(cmd, hashes, stop) {
var p = []; var p = [];
var off, i, hash, len;
writeU32(p, constants.version, 0); 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); p.length = off + 32 * (hashes.length + 1);
for (var i = 0; i < hashes.length; i++) { for (i = 0; i < hashes.length; i++) {
var hash = hashes[i]; hash = hashes[i];
if (typeof hash === 'string') if (typeof hash === 'string')
hash = utils.toArray(hash, 'hex'); hash = utils.toArray(hash, 'hex');
var len = utils.copy(hash, p, off);
len = utils.copy(hash, p, off);
for (; len < 32; len++) for (; len < 32; len++)
p[off + len] = 0; p[off + len] = 0;
off += len; off += len;
} }
if (stop) { if (stop) {
stop = utils.toArray(stop, 'hex'); stop = utils.toArray(stop, 'hex');
var len = utils.copy(stop, p, off); len = utils.copy(stop, p, off);
} else { } else {
var len = 0; len = 0;
} }
for (; len < 32; len++) for (; len < 32; len++)
p[off + len] = 0; p[off + len] = 0;
assert.equal(off + len, p.length); assert.equal(off + len, p.length);
return this.packet(cmd, p); return this.packet(cmd, p);
@ -241,16 +251,18 @@ Framer.prototype._getBlocks = function _getBlocks(cmd, hashes, stop) {
Framer.tx = function tx(tx) { Framer.tx = function tx(tx) {
var p = []; 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); off += varint(p, tx.inputs.length, off);
for (var i = 0; i < tx.inputs.length; i++) { for (i = 0; i < tx.inputs.length; i++) {
var input = tx.inputs[i]; input = tx.inputs[i];
off += utils.copy(utils.toArray(input.out.hash, 'hex'), p, off, true); off += utils.copy(utils.toArray(input.out.hash, 'hex'), p, off, true);
off += writeU32(p, input.out.index, off); 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 += varint(p, s.length, off);
off += utils.copy(s, p, off, true); off += utils.copy(s, p, off, true);
@ -258,17 +270,19 @@ Framer.tx = function tx(tx) {
} }
off += varint(p, tx.outputs.length, off); off += varint(p, tx.outputs.length, off);
for (var i = 0; i < tx.outputs.length; i++) { for (i = 0; i < tx.outputs.length; i++) {
var output = tx.outputs[i]; output = tx.outputs[i];
// Put LE value // Put LE value
var value = output.value.toArray().slice().reverse(); value = output.value.toArray().slice().reverse();
assert(value.length <= 8); assert(value.length <= 8);
off += utils.copy(value, p, off, true); 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; p[off] = 0;
var s = bcoin.script.encode(output.script); s = bcoin.script.encode(output.script);
off += varint(p, s.length, off); off += varint(p, s.length, off);
off += utils.copy(s, p, off, true); off += utils.copy(s, p, off, true);
} }
@ -356,15 +370,15 @@ Framer.prototype.merkleBlock = function merkleBlock(block) {
Framer.prototype.addr = function addr(peers) { Framer.prototype.addr = function addr(peers) {
var p = []; var p = [];
var i = 0;
var off = 0; var off = 0;
var peer; var peer;
var start = (Date.now() / 1000 | 0) - process.uptime(); var start = (Date.now() / 1000 | 0) - process.uptime();
var i;
// count // count
off += varint(p, peers.length, off); off += varint(p, peers.length, off);
for (; i < peers.length; i++) { for (i = 0; i < peers.length; i++) {
peer = peers[i]; peer = peers[i];
// timestamp // timestamp
@ -387,3 +401,5 @@ Framer.prototype.addr = function addr(peers) {
return this.packet('addr', p); return this.packet('addr', p);
}; };
module.exports = Framer;

View File

@ -6,6 +6,7 @@ var utils = bcoin.utils;
*/ */
var network = exports; var network = exports;
var main, testnet;
network.set = function(type) { network.set = function(type) {
var net = network[type]; var net = network[type];
@ -16,7 +17,7 @@ network.set = function(type) {
* Main * Main
*/ */
var main = network.main = {}; main = network.main = {};
main.prefixes = { main.prefixes = {
pubkey: 0, pubkey: 0,
@ -103,7 +104,7 @@ main.preload = require('./preload');
* https://en.bitcoin.it/wiki/Testnet * https://en.bitcoin.it/wiki/Testnet
*/ */
var testnet = network.testnet = {}; testnet = network.testnet = {};
testnet.type = 'testnet'; testnet.type = 'testnet';

View File

@ -22,20 +22,28 @@ function Parser() {
this.waiting = 24; this.waiting = 24;
this.packet = null; this.packet = null;
} }
inherits(Parser, EventEmitter); inherits(Parser, EventEmitter);
module.exports = Parser;
Parser.prototype._error = function _error(str) { Parser.prototype._error = function _error(str) {
this.emit('error', new Error(str)); this.emit('error', new Error(str));
}; };
Parser.prototype.feed = function feed(data) { Parser.prototype.feed = function feed(data) {
var chunk, i, off, len;
this.pendingTotal += data.length; this.pendingTotal += data.length;
this.pending.push(data); this.pending.push(data);
while (this.pendingTotal >= this.waiting) { while (this.pendingTotal >= this.waiting) {
// Concat chunks // Concat chunks
var chunk = new Array(this.waiting); chunk = new Array(this.waiting);
for (var i = 0, off = 0, len = 0; off < chunk.length; i++) {
i = 0;
off = 0;
len = 0;
for (; off < chunk.length; i++) {
len = utils.copy(this.pending[0], chunk, off); len = utils.copy(this.pending[0], chunk, off);
if (len === this.pending[0].length) if (len === this.pending[0].length)
this.pending.shift(); this.pending.shift();
@ -45,6 +53,7 @@ Parser.prototype.feed = function feed(data) {
this.pending[0] = this.pending[0].slice(len); this.pending[0] = this.pending[0].slice(len);
off += len; off += len;
} }
assert.equal(off, chunk.length); assert.equal(off, chunk.length);
// Slice buffers // Slice buffers
@ -56,32 +65,39 @@ Parser.prototype.feed = function feed(data) {
Parser.prototype.parse = function parse(chunk) { Parser.prototype.parse = function parse(chunk) {
if (this.packet === null) { if (this.packet === null) {
this.packet = this.parseHeader(chunk) || {}; this.packet = this.parseHeader(chunk) || {};
} else { 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;
} }
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) { Parser.prototype.parseHeader = function parseHeader(h) {
var magic = readU32(h, 0); var i, magic, cmd;
magic = readU32(h, 0);
if (magic !== network.magic) { if (magic !== network.magic) {
return this._error('Invalid magic value: ' + magic.toString(16)); return this._error('Invalid magic value: ' + magic.toString(16));
} }
// Count length of the cmd // 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) if (i === 12)
return this._error('Not NULL-terminated cmd'); 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); this.waiting = readU32(h, 16);
return { return {
@ -113,30 +129,32 @@ Parser.prototype.parsePayload = function parsePayload(cmd, p) {
}; };
Parser.prototype.parseVersion = function parseVersion(p) { Parser.prototype.parseVersion = function parseVersion(p) {
var v, services, ts, nonce, result, off, agent, height, relay;
if (p.length < 85) if (p.length < 85)
return this._error('version packet is too small'); return this._error('version packet is too small');
var v = readU32(p, 0); v = readU32(p, 0);
var services = readU64(p, 4); services = readU64(p, 4);
// Timestamp // Timestamp
var ts = readU64(p, 12); ts = readU64(p, 12);
// Nonce, very dramatic // 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 // User agent length
var result = readIntv(p, 80); result = readIntv(p, 80);
var off = result.off; off = result.off;
var agent = p.slice(off, off + result.r); agent = p.slice(off, off + result.r);
off += result.r; off += result.r;
// Start height // Start height
var height = readU32(p, off); height = readU32(p, off);
off += 4; off += 4;
// Relay // Relay
var relay = p.length > off ? p[off] === 1 : true; relay = p.length > off ? p[off] === 1 : true;
return { return {
v: v, v: v,
@ -150,10 +168,11 @@ Parser.prototype.parseVersion = function parseVersion(p) {
}; };
function readIntv(p, off) { function readIntv(p, off) {
var r, bytes;
if (!off) if (!off)
off = 0; off = 0;
var r, bytes;
if (p[off] < 0xfd) { if (p[off] < 0xfd) {
r = p[off]; r = p[off];
bytes = 1; bytes = 1;
@ -172,45 +191,54 @@ function readIntv(p, off) {
} }
Parser.prototype.parseInvList = function parseInvList(p) { 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); p = p.slice(count.off);
count = count.r; count = count.r;
if (p.length < count * 36) if (p.length < count * 36)
return this._error('Invalid getdata size'); return this._error('Invalid getdata size');
var items = []; for (i = 0; i < count; i++, off += 36) {
for (var i = 0, off = 0; i < count; i++, off += 36) {
items.push({ items.push({
type: constants.invByVal[readU32(p, off)], type: constants.invByVal[readU32(p, off)],
hash: p.slice(off + 4, off + 36) hash: p.slice(off + 4, off + 36)
}); });
} }
return items; return items;
}; };
Parser.prototype.parseMerkleBlock = function parseMerkleBlock(p) { Parser.prototype.parseMerkleBlock = function parseMerkleBlock(p) {
var i, hashCount, off, hashes, flagCount, flags;
if (p.length < 86) if (p.length < 86)
return this._error('Invalid merkleblock size'); return this._error('Invalid merkleblock size');
var hashCount = readIntv(p, 84); hashCount = readIntv(p, 84);
var off = hashCount.off; off = hashCount.off;
hashCount = hashCount.r; hashCount = hashCount.r;
if (off + 32 * hashCount + 1 > p.length) if (off + 32 * hashCount + 1 > p.length)
return this._error('Invalid hash count'); return this._error('Invalid hash count');
var hashes = new Array(hashCount); hashes = new Array(hashCount);
for (var i = 0; i < hashCount; i++)
for (i = 0; i < hashCount; i++)
hashes[i] = p.slice(off + i * 32, off + (i + 1) * 32); hashes[i] = p.slice(off + i * 32, off + (i + 1) * 32);
off = off + 32 * hashCount; off = off + 32 * hashCount;
var flagCount = readIntv(p, off); flagCount = readIntv(p, off);
off = flagCount.off; off = flagCount.off;
flagCount = flagCount.r; flagCount = flagCount.r;
if (off + flagCount > p.length) if (off + flagCount > p.length)
return this._error('Invalid flag count'); return this._error('Invalid flag count');
var flags = p.slice(off, off + flagCount); flags = p.slice(off, off + flagCount);
return { return {
version: readU32(p, 0), version: readU32(p, 0),
@ -228,19 +256,20 @@ Parser.prototype.parseMerkleBlock = function parseMerkleBlock(p) {
}; };
Parser.prototype.parseHeaders = function parseHeaders(p) { Parser.prototype.parseHeaders = function parseHeaders(p) {
var headers = [];
var i, result, off, count, header, start, r;
if (p.length < 81) if (p.length < 81)
return this._error('Invalid headers size'); return this._error('Invalid headers size');
var result = readIntv(p, 0); result = readIntv(p, 0);
var off = result.off; off = result.off;
var count = result.r; count = result.r;
var headers = [];
if (p.length >= off + 81) { if (p.length >= off + 81) {
for (var i = 0; i < count && off + 81 < p.length; i++) { for (i = 0; i < count && off + 81 < p.length; i++) {
var header = {}; header = {};
var start = off; start = off;
header.version = readU32(p, off); header.version = readU32(p, off);
off += 4; off += 4;
header.prevBlock = p.slice(off, off + 32); header.prevBlock = p.slice(off, off + 32);
@ -253,7 +282,7 @@ Parser.prototype.parseHeaders = function parseHeaders(p) {
off += 4; off += 4;
header.nonce = readU32(p, off); header.nonce = readU32(p, off);
off += 4; off += 4;
var r = readIntv(p, off); r = readIntv(p, off);
header.totalTX = r.r; header.totalTX = r.r;
off = r.off; off = r.off;
header._raw = p.slice(start, start + 80); header._raw = p.slice(start, start + 80);
@ -265,17 +294,19 @@ Parser.prototype.parseHeaders = function parseHeaders(p) {
}; };
Parser.prototype.parseBlock = function parseBlock(p) { Parser.prototype.parseBlock = function parseBlock(p) {
var txs = [];
var i, result, off, totalTX, tx;
if (p.length < 81) if (p.length < 81)
return this._error('Invalid block size'); return this._error('Invalid block size');
var result = readIntv(p, 80); result = readIntv(p, 80);
var off = result.off; off = result.off;
var totalTX = result.r; totalTX = result.r;
var txs = [];
if (p.length >= off + 10) { if (p.length >= off + 10) {
for (var i = 0; i < totalTX; i++) { for (i = 0; i < totalTX; i++) {
var tx = this.parseTX(p.slice(off)); tx = this.parseTX(p.slice(off));
off += tx._off; off += tx._off;
txs.push(tx); txs.push(tx);
} }
@ -296,12 +327,15 @@ Parser.prototype.parseBlock = function parseBlock(p) {
}; };
Parser.prototype.parseTXIn = function parseTXIn(p) { Parser.prototype.parseTXIn = function parseTXIn(p) {
var scriptLen, off;
if (p.length < 41) if (p.length < 41)
return this._error('Invalid tx_in size'); return this._error('Invalid tx_in size');
var scriptLen = readIntv(p, 36); scriptLen = readIntv(p, 36);
var off = scriptLen.off; off = scriptLen.off;
scriptLen = scriptLen.r; scriptLen = scriptLen.r;
if (off + scriptLen + 4 > p.length) if (off + scriptLen + 4 > p.length)
return this._error('Invalid tx_in script 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) { Parser.prototype.parseTXOut = function parseTXOut(p) {
var scriptLen, off;
if (p.length < 9) if (p.length < 9)
return this._error('Invalid tx_out size'); return this._error('Invalid tx_out size');
var scriptLen = readIntv(p, 8); scriptLen = readIntv(p, 8);
var off = scriptLen.off; off = scriptLen.off;
scriptLen = scriptLen.r; scriptLen = scriptLen.r;
if (off + scriptLen > p.length) if (off + scriptLen > p.length)
return this._error('Invalid tx_out script 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) { Parser.prototype.parseTX = function parseTX(p) {
var inCount, off, txIn, tx;
var outCount, off, txOut;
var i;
if (p.length < 10) if (p.length < 10)
return this._error('Invalid tx size'); return this._error('Invalid tx size');
var inCount = readIntv(p, 4); inCount = readIntv(p, 4);
var off = inCount.off; off = inCount.off;
inCount = inCount.r; inCount = inCount.r;
if (inCount < 0) if (inCount < 0)
return this._error('Invalid tx_in count (negative)'); return this._error('Invalid tx_in count (negative)');
if (off + 41 * inCount + 5 > p.length) if (off + 41 * inCount + 5 > p.length)
return this._error('Invalid tx_in count (too big)'); return this._error('Invalid tx_in count (too big)');
var txIn = new Array(inCount); txIn = new Array(inCount);
for (var i = 0; i < inCount; i++) { for (i = 0; i < inCount; i++) {
var tx = this.parseTXIn(p.slice(off)); tx = this.parseTXIn(p.slice(off));
if (!tx) if (!tx)
return; return;
txIn[i] = tx; txIn[i] = tx;
off += tx.size; off += tx.size;
@ -357,19 +402,21 @@ Parser.prototype.parseTX = function parseTX(p) {
return this._error('Invalid tx_in offset'); return this._error('Invalid tx_in offset');
} }
var outCount = readIntv(p, off); outCount = readIntv(p, off);
var off = outCount.off; off = outCount.off;
outCount = outCount.r; outCount = outCount.r;
if (outCount < 0) if (outCount < 0)
return this._error('Invalid tx_out count (negative)'); return this._error('Invalid tx_out count (negative)');
if (off + 9 * outCount + 4 > p.length) if (off + 9 * outCount + 4 > p.length)
return this._error('Invalid tx_out count (too big)'); return this._error('Invalid tx_out count (too big)');
var txOut = new Array(outCount); txOut = new Array(outCount);
for (var i = 0; i < outCount; i++) { for (i = 0; i < outCount; i++) {
var tx = this.parseTXOut(p.slice(off)); tx = this.parseTXOut(p.slice(off));
if (!tx) if (!tx)
return; return;
txOut[i] = tx; txOut[i] = tx;
off += tx.size; off += tx.size;
@ -389,32 +436,36 @@ Parser.prototype.parseTX = function parseTX(p) {
}; };
Parser.prototype.parseReject = function parseReject(p) { Parser.prototype.parseReject = function parseReject(p) {
var messageLen, off, message, ccode, reasonLen, reason, data;
if (p.length < 3) if (p.length < 3)
return this._error('Invalid reject size'); return this._error('Invalid reject size');
var messageLen = readIntv(p, 0); messageLen = readIntv(p, 0);
var off = messageLen.off; off = messageLen.off;
messageLen = messageLen.r; messageLen = messageLen.r;
if (off + messageLen + 2 > p.length) if (off + messageLen + 2 > p.length)
return this._error('Invalid reject message'); 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; off += messageLen;
var ccode = p[off]; ccode = p[off];
off++; off++;
var reasonLen = readIntv(p, off); reasonLen = readIntv(p, off);
off = reasonLen.off; off = reasonLen.off;
reasonLen = reasonLen.r; reasonLen = reasonLen.r;
if (off + reasonLen > p.length) if (off + reasonLen > p.length)
return this._error('Invalid reject reason'); 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; off += reasonLen;
var data = p.slice(off, off + 32); data = p.slice(off, off + 32);
return { return {
message: message, message: message,
@ -429,34 +480,35 @@ Parser.prototype.parseAddr = function parseAddr(p) {
return this._error('Invalid addr size'); return this._error('Invalid addr size');
var addrs = []; var addrs = [];
var i, len, off, count, ts, service, ipv6, ipv4, port;
// count // count
var len = readIntv(p, 0); len = readIntv(p, 0);
var off = len.off; off = len.off;
var count = len.r; count = len.r;
p = p.slice(off); p = p.slice(off);
for (var i = 0; i < count; i++) { for (i = 0; i < count; i++) {
// timestamp - LE // timestamp - LE
var ts = utils.readU32(p, 0); ts = utils.readU32(p, 0);
// NODE_NETWORK service - LE // NODE_NETWORK service - LE
var service = utils.readU64(p, 4); service = utils.readU64(p, 4);
// ipv6 - BE // 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); ipv6 = '::' + ipv6.replace(/(.{4})/g, '$1:').slice(0, -1);
// ipv4 - BE // ipv4 - BE
var ipv4 = utils.readU32BE(p, 24); ipv4 = utils.readU32BE(p, 24);
ipv4 = ((ipv4 >> 24) & 0xff) + '.' + ipv4 = ((ipv4 >> 24) & 0xff)
((ipv4 >> 16) & 0xff) + '.' + + '.' + ((ipv4 >> 16) & 0xff)
((ipv4 >> 8) & 0xff) + '.' + + '.' + ((ipv4 >> 8) & 0xff)
((ipv4 >> 0) & 0xff); + '.' + ((ipv4 >> 0) & 0xff);
// port - BE // port - BE
var port = utils.readU16BE(p, 28); port = utils.readU16BE(p, 28);
addrs.push({ addrs.push({
ts: ts, ts: ts,
@ -471,3 +523,5 @@ Parser.prototype.parseAddr = function parseAddr(p) {
return addrs; return addrs;
}; };
module.exports = Parser;

View File

@ -8,9 +8,13 @@ var script = exports;
script.decode = function decode(s) { script.decode = function decode(s) {
if (!s) if (!s)
return []; return [];
var opcodes = []; var opcodes = [];
for (var i = 0; i < s.length;) { var i = 0;
var b = s[i++]; var b, opcode, len;
while (i < s.length) {
b = s[i++];
// Next `b` bytes should be pushed to stack // Next `b` bytes should be pushed to stack
if (b >= 0x01 && b <= 0x4b) { if (b >= 0x01 && b <= 0x4b) {
@ -35,7 +39,7 @@ script.decode = function decode(s) {
continue; continue;
} }
var opcode = constants.opcodesByVal[b]; opcode = constants.opcodesByVal[b];
if (i >= s.length) { if (i >= s.length) {
opcodes.push(opcode || b); opcodes.push(opcode || b);
@ -43,7 +47,7 @@ script.decode = function decode(s) {
} }
if (opcode === 'pushdata1') { if (opcode === 'pushdata1') {
var len = s[i]; len = s[i];
i += 1; i += 1;
opcodes.push(s.slice(i, i + len)); opcodes.push(s.slice(i, i + len));
i += len; i += len;
@ -52,7 +56,7 @@ script.decode = function decode(s) {
len: len len: len
}); });
} else if (opcode === 'pushdata2') { } else if (opcode === 'pushdata2') {
var len = utils.readU16(s, i); len = utils.readU16(s, i);
i += 2; i += 2;
opcodes.push(s.slice(i, i + len)); opcodes.push(s.slice(i, i + len));
i += len; i += len;
@ -61,7 +65,7 @@ script.decode = function decode(s) {
len: len len: len
}); });
} else if (opcode === 'pushdata4') { } else if (opcode === 'pushdata4') {
var len = utils.readU32(s, i); len = utils.readU32(s, i);
i += 4; i += 4;
opcodes.push(s.slice(i, i + len)); opcodes.push(s.slice(i, i + len));
i += len; i += len;
@ -85,8 +89,11 @@ script.encode = function encode(s) {
var opcodes = constants.opcodes; var opcodes = constants.opcodes;
var res = []; var res = [];
for (var i = 0; i < s.length; i++) { var i = 0;
var instr = s[i]; var instr;
for (i = 0; i < s.length; i++) {
instr = s[i];
// Push value to stack // Push value to stack
if (Array.isArray(instr)) { if (Array.isArray(instr)) {
@ -134,27 +141,30 @@ script.encode = function encode(s) {
}; };
script.subscript = function subscript(s, lastSep) { script.subscript = function subscript(s, lastSep) {
var i, res;
if (!s) if (!s)
return []; return [];
if (lastSep == null) { if (lastSep == null) {
lastSep = -1; lastSep = -1;
for (var i = 0; i < s.length; i++) { for (i = 0; i < s.length; i++) {
if (s[i] === 'codesep') if (s[i] === 'codesep')
lastSep = i; lastSep = i;
else if (s[i] === 'checksig' || else if (s[i] === 'checksig'
s[i] === 'checksigverify' || || s[i] === 'checksigverify'
s[i] === 'checkmultisig' || || s[i] === 'checkmultisig'
s[i] === 'checkmultisigverify') { || s[i] === 'checkmultisigverify') {
break; break;
} }
} }
} }
var res = []; res = [];
for (var i = lastSep + 1; i < s.length; i++) for (i = lastSep + 1; i < s.length; i++) {
if (s[i] !== 'codesep') if (s[i] !== 'codesep')
res.push(s[i]); res.push(s[i]);
}
return res; return res;
}; };
@ -179,8 +189,10 @@ script.verify = function verify(hash, sig, pub) {
script._next = function(to, s, pc) { script._next = function(to, s, pc) {
var depth = 0; var depth = 0;
var o;
while (s[pc]) { while (s[pc]) {
var o = s[pc]; o = s[pc];
if (o === 'if_' || o === 'notif') if (o === 'if_' || o === 'notif')
depth++; depth++;
else if (o === 'else_') else if (o === 'else_')
@ -195,6 +207,7 @@ script._next = function(to, s, pc) {
depth++; depth++;
pc++; pc++;
} }
return -1; return -1;
}; };
@ -205,11 +218,22 @@ script.execute = function execute(s, stack, tx, index, recurse) {
return false; return false;
var lastSep = -1; 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 || []; stack.alt = stack.alt || [];
for (var pc = 0; pc < s.length; pc++) { for (pc = 0; pc < s.length; pc++) {
var o = s[pc]; o = s[pc];
if (Array.isArray(o)) { if (Array.isArray(o)) {
if (o.length > constants.script.maxPush) if (o.length > constants.script.maxPush)
@ -237,16 +261,16 @@ script.execute = function execute(s, stack, tx, index, recurse) {
} }
case 'if_': case 'if_':
case 'notif': { case 'notif': {
var val = false; val = false;
if (stack.length < 1) if (stack.length < 1)
return false; return false;
var v = stack.pop(); v = stack.pop();
val = new bn(v).cmpn(0) !== 0; val = new bn(v).cmpn(0) !== 0;
if (o === 'notif') if (o === 'notif')
val = !val; val = !val;
var if_ = pc; if_ = pc;
var else_ = script._next('else_', s, pc); else_ = script._next('else_', s, pc);
var endif = script._next('endif', s, pc); endif = script._next('endif', s, pc);
// Splice out the statement blocks we don't need // Splice out the statement blocks we don't need
if (val) { if (val) {
if (endif === -1) if (endif === -1)
@ -339,10 +363,10 @@ script.execute = function execute(s, stack, tx, index, recurse) {
case 'roll': { case 'roll': {
if (stack.length < 2) if (stack.length < 2)
return false; return false;
var n = new bn(stack.pop()).toNumber(); n = new bn(stack.pop()).toNumber();
if (n < 0 || n >= stack.length) if (n < 0 || n >= stack.length)
return false; return false;
var v = stack[-n - 1]; v = stack[-n - 1];
if (o === 'roll') if (o === 'roll')
stack.splice(stack.length - n - 1, 1); stack.splice(stack.length - n - 1, 1);
stack.push(v); stack.push(v);
@ -351,9 +375,9 @@ script.execute = function execute(s, stack, tx, index, recurse) {
case 'rot': { case 'rot': {
if (stack.length < 3) if (stack.length < 3)
return false; return false;
var v3 = stack[stack.length - 3]; v3 = stack[stack.length - 3];
var v2 = stack[stack.length - 2]; v2 = stack[stack.length - 2];
var v1 = stack[stack.length - 1]; v1 = stack[stack.length - 1];
stack[stack.length - 3] = v2; stack[stack.length - 3] = v2;
stack[stack.length - 2] = v3; stack[stack.length - 2] = v3;
v2 = stack[stack.length - 2]; v2 = stack[stack.length - 2];
@ -364,8 +388,8 @@ script.execute = function execute(s, stack, tx, index, recurse) {
case 'swap': { case 'swap': {
if (stack.length < 2) if (stack.length < 2)
return false; return false;
var v2 = stack[stack.length - 2]; v2 = stack[stack.length - 2];
var v1 = stack[stack.length - 1]; v1 = stack[stack.length - 1];
stack[stack.length - 2] = v1; stack[stack.length - 2] = v1;
stack[stack.length - 1] = v2; stack[stack.length - 1] = v2;
break; break;
@ -386,8 +410,8 @@ script.execute = function execute(s, stack, tx, index, recurse) {
case 'dup2': { case 'dup2': {
if (stack.length < 2) if (stack.length < 2)
return false; return false;
var v1 = stack[stack.length - 1]; v1 = stack[stack.length - 1];
var v2 = stack[stack.length - 2]; v2 = stack[stack.length - 2];
stack.push(v1); stack.push(v1);
stack.push(v2); stack.push(v2);
break; break;
@ -395,9 +419,9 @@ script.execute = function execute(s, stack, tx, index, recurse) {
case 'dup3': { case 'dup3': {
if (stack.length < 3) if (stack.length < 3)
return false; return false;
var v1 = stack[stack.length - 1]; v1 = stack[stack.length - 1];
var v2 = stack[stack.length - 2]; v2 = stack[stack.length - 2];
var v3 = stack[stack.length - 3]; v3 = stack[stack.length - 3];
stack.push(v1); stack.push(v1);
stack.push(v2); stack.push(v2);
stack.push(v3); stack.push(v3);
@ -406,8 +430,8 @@ script.execute = function execute(s, stack, tx, index, recurse) {
case 'over2': { case 'over2': {
if (stack.length < 4) if (stack.length < 4)
return false; return false;
var v1 = stack[stack.length - 4]; v1 = stack[stack.length - 4];
var v2 = stack[stack.length - 3]; v2 = stack[stack.length - 3];
stack.push(v1); stack.push(v1);
stack.push(v2); stack.push(v2);
break; break;
@ -415,8 +439,8 @@ script.execute = function execute(s, stack, tx, index, recurse) {
case 'rot2': { case 'rot2': {
if (stack.length < 6) if (stack.length < 6)
return false; return false;
var v1 = stack[stack.length - 6]; v1 = stack[stack.length - 6];
var v2 = stack[stack.length - 5]; v2 = stack[stack.length - 5];
stack.splice(stack.length - 6, 2); stack.splice(stack.length - 6, 2);
stack.push(v1); stack.push(v1);
stack.push(v2); stack.push(v2);
@ -425,10 +449,10 @@ script.execute = function execute(s, stack, tx, index, recurse) {
case 'swap2': { case 'swap2': {
if (stack.length < 4) if (stack.length < 4)
return false; return false;
var v4 = stack[stack.length - 4]; v4 = stack[stack.length - 4];
var v3 = stack[stack.length - 3]; v3 = stack[stack.length - 3];
var v2 = stack[stack.length - 2]; v2 = stack[stack.length - 2];
var v1 = stack[stack.length - 1]; v1 = stack[stack.length - 1];
stack[stack.length - 4] = v2; stack[stack.length - 4] = v2;
stack[stack.length - 2] = v4; stack[stack.length - 2] = v4;
stack[stack.length - 3] = v1; stack[stack.length - 3] = v1;
@ -449,7 +473,7 @@ script.execute = function execute(s, stack, tx, index, recurse) {
case 'noteq0': { case 'noteq0': {
if (stack.length < 1) if (stack.length < 1)
return false; return false;
var n = new bn(stack.pop()); n = new bn(stack.pop());
switch (o) { switch (o) {
case 'add1': case 'add1':
n.iadd(1); n.iadd(1);
@ -505,9 +529,9 @@ script.execute = function execute(s, stack, tx, index, recurse) {
case 'max': case 'max':
if (stack.length < 2) if (stack.length < 2)
return false; return false;
var n2 = new bn(stack.pop()); n2 = new bn(stack.pop());
var n1 = new bn(stack.pop()); n1 = new bn(stack.pop());
var n = new bn(0); n = new bn(0);
switch (o) { switch (o) {
case 'add': case 'add':
n = n1.add(b2); n = n1.add(b2);
@ -551,7 +575,7 @@ script.execute = function execute(s, stack, tx, index, recurse) {
default: default:
return false; return false;
} }
var res = n.cmpn(0) !== 0; res = n.cmpn(0) !== 0;
if (o === 'numeqverify') { if (o === 'numeqverify') {
if (!res) if (!res)
return false; return false;
@ -563,10 +587,10 @@ script.execute = function execute(s, stack, tx, index, recurse) {
case 'within': case 'within':
if (stack.length < 3) if (stack.length < 3)
return false; return false;
var n3 = new bn(stack.pop()); n3 = new bn(stack.pop());
var n2 = new bn(stack.pop()); n2 = new bn(stack.pop());
var n1 = new bn(stack.pop()); n1 = new bn(stack.pop());
var val = n2.cmp(n1) <= 0 && n1.cmp(n3) < 0; val = n2.cmp(n1) <= 0 && n1.cmp(n3) < 0;
stack.push(val.cmpn(0) !== 0 ? [ 1 ] : []); stack.push(val.cmpn(0) !== 0 ? [ 1 ] : []);
break; break;
} }
@ -611,7 +635,7 @@ script.execute = function execute(s, stack, tx, index, recurse) {
case 'eq': { case 'eq': {
if (stack.length < 2) if (stack.length < 2)
return false; return false;
var res = utils.isEqual(stack.pop(), stack.pop()); res = utils.isEqual(stack.pop(), stack.pop());
if (o === 'eqverify') { if (o === 'eqverify') {
if (!res) if (!res)
return false; return false;
@ -625,19 +649,19 @@ script.execute = function execute(s, stack, tx, index, recurse) {
if (!tx || stack.length < 2) if (!tx || stack.length < 2)
return false; return false;
var pub = stack.pop(); pub = stack.pop();
var sig = stack.pop(); sig = stack.pop();
var type = sig[sig.length - 1]; type = sig[sig.length - 1];
if (!constants.rhashType[type & 0x1f]) if (!constants.rhashType[type & 0x1f])
return false; return false;
if (!script.isValidSig(sig)) if (!script.isValidSig(sig))
return false; return false;
var subscript = script.subscript(s, lastSep); subscript = script.subscript(s, lastSep);
var hash = tx.subscriptHash(index, subscript, type); 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 (o === 'checksigverify') {
if (!res) if (!res)
return false; return false;
@ -652,7 +676,7 @@ script.execute = function execute(s, stack, tx, index, recurse) {
if (!tx || stack.length < 3) if (!tx || stack.length < 3)
return false; return false;
var n = stack.pop(); n = stack.pop();
if (n.length !== 1 || !(1 <= n[0] && n[0] <= 15)) if (n.length !== 1 || !(1 <= n[0] && n[0] <= 15))
return false; return false;
n = n[0] || 0; n = n[0] || 0;
@ -660,16 +684,16 @@ script.execute = function execute(s, stack, tx, index, recurse) {
if (stack.length < n + 1) if (stack.length < n + 1)
return false; return false;
var keys = []; keys = [];
for (var i = 0; i < n; i++) { for (i = 0; i < n; i++) {
var key = stack.pop(); key = stack.pop();
if (!(33 <= key.length && key.length <= 65)) if (!(33 <= key.length && key.length <= 65))
return false; return false;
keys.push(key); keys.push(key);
} }
var m = stack.pop(); m = stack.pop();
if (m.length !== 1 || !(1 <= m[0] && m[0] <= n)) if (m.length !== 1 || !(1 <= m[0] && m[0] <= n))
return false; return false;
m = m[0] || 0; m = m[0] || 0;
@ -677,23 +701,23 @@ script.execute = function execute(s, stack, tx, index, recurse) {
if (stack.length < m + 1) if (stack.length < m + 1)
return false; return false;
var subscript = script.subscript(s, lastSep); subscript = script.subscript(s, lastSep);
// Get signatures // Get signatures
var succ = 0; succ = 0;
for (var i = 0; i < m; i++) { for (i = 0; i < m; i++) {
var sig = stack.pop(); sig = stack.pop();
var type = sig[sig.length - 1]; type = sig[sig.length - 1];
if (!constants.rhashType[type & 0x1f]) if (!constants.rhashType[type & 0x1f])
return false; return false;
if (!script.isValidSig(sig)) if (!script.isValidSig(sig))
return false; return false;
var hash = tx.subscriptHash(index, subscript, type); hash = tx.subscriptHash(index, subscript, type);
// Strict order: // 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) if (res)
succ++; succ++;
} }
@ -701,7 +725,7 @@ script.execute = function execute(s, stack, tx, index, recurse) {
// Extra value // Extra value
stack.pop(); stack.pop();
var res = succ >= m; res = succ >= m;
if (o === 'checkmultisigverify') { if (o === 'checkmultisigverify') {
if (!res) if (!res)
return false; return false;
@ -716,15 +740,15 @@ script.execute = function execute(s, stack, tx, index, recurse) {
if (!tx || stack.length === 0) if (!tx || stack.length === 0)
return false; return false;
var lock = new bn(stack[stack.length - 1]).toNumber(); lock = new bn(stack[stack.length - 1]).toNumber();
if (lock < 0) if (lock < 0)
return false; return false;
var threshold = constants.locktimeThreshold; threshold = constants.locktimeThreshold;
if (!( if (!(
(tx.lock < threshold && lock < threshold) || (tx.lock < threshold && lock < threshold)
(tx.lock >= threshold && lock >= threshold) || (tx.lock >= threshold && lock >= threshold)
)) { )) {
return false; return false;
} }
@ -747,15 +771,16 @@ script.execute = function execute(s, stack, tx, index, recurse) {
if (recurse++ > 2) if (recurse++ > 2)
return false; return false;
var evalScript = stack.pop(); evalScript = stack.pop();
if (!Array.isArray(evalScript)) if (!Array.isArray(evalScript))
return false; return false;
evalScript = script.decode(evalScript); evalScript = script.decode(evalScript);
var res = evalScript.some(function(op) { res = evalScript.some(function(op) {
return op === 'codesep'; return op === 'codesep';
}); });
if (res) if (res)
return false; return false;
@ -780,11 +805,16 @@ script.execute = function execute(s, stack, tx, index, recurse) {
script.exec = function(input, output, tx, i, recurse) { script.exec = function(input, output, tx, i, recurse) {
var stack = []; var stack = [];
var res;
script.execute(input, stack, tx, i, recurse); 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 || new bn(stack.pop()).cmp(0) !== 0)
if (!res || stack.length === 0 || utils.isEqual(stack.pop(), [ 0 ])) if (!res || stack.length === 0 || utils.isEqual(stack.pop(), [ 0 ]))
return false; return false;
return true; return true;
}; };
@ -847,8 +877,8 @@ script.isPubkey = function isPubkey(s, key) {
if (key) if (key)
return utils.isEqual(s[0], key); return utils.isEqual(s[0], key);
else
return s[0]; return s[0];
}; };
script.isPubkeyhash = function isPubkeyhash(s, hash) { script.isPubkeyhash = function isPubkeyhash(s, hash) {
@ -858,21 +888,24 @@ script.isPubkeyhash = function isPubkeyhash(s, hash) {
if (s.length !== 5) if (s.length !== 5)
return false; return false;
var match = s[0] === 'dup' && var match = s[0] === 'dup'
s[1] === 'hash160' && && s[1] === 'hash160'
Array.isArray(s[2]) && && Array.isArray(s[2])
s[3] === 'eqverify' && && s[3] === 'eqverify'
s[4] === 'checksig'; && s[4] === 'checksig';
if (!match) if (!match)
return false; return false;
if (hash) if (hash)
return utils.isEqual(s[2], hash); return utils.isEqual(s[2], hash);
else
return s[2]; return s[2];
}; };
script.isMultisig = function isMultisig(s, pubs) { script.isMultisig = function isMultisig(s, pubs) {
var m, n, keys, isArray, total;
if (script.lockTime(s)) if (script.lockTime(s))
s = s.slice(3); s = s.slice(3);
@ -883,7 +916,7 @@ script.isMultisig = function isMultisig(s, pubs) {
if (pubs && !Array.isArray(pubs[0])) if (pubs && !Array.isArray(pubs[0]))
pubs = [pubs]; pubs = [pubs];
var m = s[0]; m = s[0];
if (typeof m === 'number' && m >= 1 && m <= 15) if (typeof m === 'number' && m >= 1 && m <= 15)
m = [m]; m = [m];
if (!Array.isArray(m) || m.length !== 1) if (!Array.isArray(m) || m.length !== 1)
@ -893,7 +926,7 @@ script.isMultisig = function isMultisig(s, pubs) {
if (s[s.length - 1] !== 'checkmultisig') if (s[s.length - 1] !== 'checkmultisig')
return false; return false;
var n = s[s.length - 2]; n = s[s.length - 2];
if (typeof n === 'number' && n >= 1 && n <= 15) if (typeof n === 'number' && n >= 1 && n <= 15)
n = [n]; n = [n];
if (!Array.isArray(n) || n.length !== 1) if (!Array.isArray(n) || n.length !== 1)
@ -903,17 +936,19 @@ script.isMultisig = function isMultisig(s, pubs) {
if (n + 3 !== s.length) if (n + 3 !== s.length)
return false; return false;
var keys = s.slice(1, 1 + n); keys = s.slice(1, 1 + n);
var isArray = keys.every(function(k) {
isArray = keys.every(function(k) {
return Array.isArray(k); return Array.isArray(k);
}); });
if (!isArray) if (!isArray)
return false; return false;
if (!pubs) if (!pubs)
return keys; return keys;
var total = keys.filter(function(k) { total = keys.filter(function(k) {
return pubs.some(function(pub) { return pubs.some(function(pub) {
return utils.isEqual(k, pub); return utils.isEqual(k, pub);
}); });
@ -929,10 +964,10 @@ script.isScripthash = function isScripthash(s, hash) {
if (s.length !== 3) if (s.length !== 3)
return false; return false;
var res = s[0] === 'hash160' && var res = s[0] === 'hash160'
Array.isArray(s[1]) && && Array.isArray(s[1])
s[1].length === 20 && && s[1].length === 20
s[2] === 'eq'; && s[2] === 'eq';
if (!res) if (!res)
return false; return false;
@ -947,9 +982,9 @@ script.isColored = function isColored(s) {
if (s.length !== 2) if (s.length !== 2)
return false; return false;
return s[0] === 'ret' && return s[0] === 'ret'
Array.isArray(s[1]) && && Array.isArray(s[1])
s[1].length <= 40; && s[1].length <= 40;
}; };
script.colored = function colored(s) { 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])) if (s.length !== 2 || !Array.isArray(s[0]) || !Array.isArray(s[1]))
return false; return false;
// var res = script.isValidSig(s[0]) && // var res = script.isValidSig(s[0])
// 33 <= s[1].length && s[1].length <= 65; // && 33 <= s[1].length && s[1].length <= 65;
var res = 9 <= s[0].length && s[0].length <= 73 && var res = 9 <= s[0].length && s[0].length <= 73
33 <= s[1].length && s[1].length <= 65; && 33 <= s[1].length && s[1].length <= 65;
if (!res) if (!res)
return false; return false;
@ -1002,21 +1037,23 @@ script.isPubkeyhashInput = function isPubkeyhashInput(s, key) {
}; };
script.isMultisigInput = function isMultisigInput(s, pubs, tx, i) { script.isMultisigInput = function isMultisigInput(s, pubs, tx, i) {
var i, res, o;
if (s.length < 3) if (s.length < 3)
return false; return false;
if (!Array.isArray(s[0]) || s[0].length !== 0) if (!Array.isArray(s[0]) || s[0].length !== 0)
return false; return false;
for (var i = 1; i < s.length; i++) { for (i = 1; i < s.length; i++) {
// var res = Array.isArray(s[i]) && script.isValidSig(s[i]); // res = Array.isArray(s[i]) && script.isValidSig(s[i]);
var res = Array.isArray(s[i]) && 9 <= s[i].length && s[i].length <= 73; res = Array.isArray(s[i]) && 9 <= s[i].length && s[i].length <= 73;
if (!res) if (!res)
return false; return false;
} }
if (pubs && pubs.length >= 2) { 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); 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) { script.isScripthashInput = function isScripthashInput(s, redeem) {
var i, res, r, keys;
if (s.length < 4) if (s.length < 4)
return false; return false;
if (!Array.isArray(s[0]) || s[0].length !== 0) if (!Array.isArray(s[0]) || s[0].length !== 0)
return false; return false;
for (var i = 1; i < s.length - 1; i++) { for (i = 1; i < s.length - 1; i++) {
// var res = Array.isArray(s[i]) && script.isValidSig(s[i]); // res = Array.isArray(s[i]) && script.isValidSig(s[i]);
var res = Array.isArray(s[i]) && 9 <= s[i].length && s[i].length <= 73; res = Array.isArray(s[i]) && 9 <= s[i].length && s[i].length <= 73;
if (!res) if (!res)
return false; 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) if (r[r.length - 1] !== constants.opcodes.checkmultisig)
return false; return false;
if (redeem) if (redeem)
return utils.isEqual(redeem, r); return utils.isEqual(redeem, r);
var keys = script.decode(r).slice(1, -2); keys = script.decode(r).slice(1, -2);
return keys; return keys;
}; };
@ -1061,6 +1100,8 @@ script.isScripthashInput = function isScripthashInput(s, redeem) {
* This function is consensus-critical since BIP66. * This function is consensus-critical since BIP66.
*/ */
script.isValidSig = function(sig) { script.isValidSig = function(sig) {
var lenR, lenS;
// Empty signature. Not strictly DER encoded, but allowed to provide a // Empty signature. Not strictly DER encoded, but allowed to provide a
// compact way to provide an invalid signature for use with CHECK(MULTI)SIG // compact way to provide an invalid signature for use with CHECK(MULTI)SIG
if (sig.length === 0) if (sig.length === 0)
@ -1093,14 +1134,14 @@ script.isValidSig = function(sig) {
return false; return false;
// Extract the length of the R element. // 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. // Make sure the length of the S element is still inside the signature.
if (5 + lenR >= sig.length) if (5 + lenR >= sig.length)
return false; return false;
// Extract the length of the S element. // 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 // Verify that the length of the signature matches the sum of the length
// of the elements. // of the elements.
@ -1146,14 +1187,15 @@ script.isValidSig = function(sig) {
script.format = function(input, output) { script.format = function(input, output) {
var scripts = []; var scripts = [];
var prev, redeem;
if (input) { if (input) {
scripts.push(input.script); scripts.push(input.script);
if (input.out.tx && input.out.tx.outputs[input.out.index]) { 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); scripts.push(prev);
if (script.isScripthash(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); scripts.push(redeem);
} }
} }

View File

@ -22,27 +22,31 @@ function TXPool(wallet) {
// Load TXs from storage // Load TXs from storage
this._init(); this._init();
} }
inherits(TXPool, EventEmitter); inherits(TXPool, EventEmitter);
module.exports = TXPool;
TXPool.prototype._init = function init() { TXPool.prototype._init = function init() {
var self = this;
if (!this._storage) { if (!this._storage) {
this._loaded = true; this._loaded = true;
return; return;
} }
var self = this;
var s = this._storage.createReadStream({ var s = this._storage.createReadStream({
keys: false, keys: false,
start: this._prefix, start: this._prefix,
end: this._prefix + 'z' end: this._prefix + 'z'
}); });
s.on('data', function(data) { s.on('data', function(data) {
self.add(bcoin.tx.fromJSON(data), true); self.add(bcoin.tx.fromJSON(data), true);
}); });
s.on('error', function(err) { s.on('error', function(err) {
self.emit('error', err); self.emit('error', err);
}); });
s.on('end', function() { s.on('end', function() {
self._loaded = true; self._loaded = true;
self.emit('load', self._lastTs); self.emit('load', self._lastTs);
@ -51,6 +55,9 @@ TXPool.prototype._init = function init() {
TXPool.prototype.add = function add(tx, noWrite) { TXPool.prototype.add = function add(tx, noWrite) {
var hash = tx.hash('hex'); 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 // Ignore stale pending transactions
if (tx.ts === 0 && tx.ps + 2 * 24 * 3600 < +new Date() / 1000) { 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; this._all[hash] = tx;
var ownInput = this._wallet.ownInput(tx); ownInput = this._wallet.ownInput(tx);
var ownOutput = this._wallet.ownOutput(tx); ownOutput = this._wallet.ownOutput(tx);
var updated = false; updated = false;
// Consume unspent money or add orphans // Consume unspent money or add orphans
for (var i = 0; i < tx.inputs.length; i++) { for (i = 0; i < tx.inputs.length; i++) {
var input = tx.inputs[i]; input = tx.inputs[i];
var key = input.out.hash + '/' + input.out.index; key = input.out.hash + '/' + input.out.index;
var unspent = this._unspent[key]; unspent = this._unspent[key];
if (unspent) { if (unspent) {
// Add TX to inputs and spend money // 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 // Skip invalid transactions
if (!tx.verify(index)) if (!tx.verify(index))
@ -99,7 +106,7 @@ TXPool.prototype.add = function add(tx, noWrite) {
continue; continue;
// Add orphan, if no parent transaction is yet known // 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]) if (this._orphans[key])
this._orphans[key].push(orphan); this._orphans[key].push(orphan);
else else
@ -130,19 +137,19 @@ TXPool.prototype.add = function add(tx, noWrite) {
} }
// Add unspent outputs or fullfill orphans // Add unspent outputs or fullfill orphans
for (var i = 0; i < tx.outputs.length; i++) { for (i = 0; i < tx.outputs.length; i++) {
var out = tx.outputs[i]; out = tx.outputs[i];
// Do not add unspents for outputs that aren't ours. // Do not add unspents for outputs that aren't ours.
if (!~ownOutput.indexOf(out)) if (!~ownOutput.indexOf(out))
continue; continue;
var key = hash + '/' + i; key = hash + '/' + i;
var orphans = this._orphans[key]; orphans = this._orphans[key];
// Add input to orphan // Add input to orphan
if (orphans) { if (orphans) {
var some = orphans.some(checkOrphan, this); some = orphans.some(checkOrphan, this);
if (!some) if (!some)
orphans = null; orphans = null;
} }
@ -166,10 +173,11 @@ TXPool.prototype.add = function add(tx, noWrite) {
}; };
TXPool.prototype._storeTX = function _storeTX(hash, tx, noWrite) { TXPool.prototype._storeTX = function _storeTX(hash, tx, noWrite) {
var self = this;
if (!this._storage || noWrite) if (!this._storage || noWrite)
return; return;
var self = this;
this._storage.put(this._prefix + hash, tx.toJSON(), function(err) { this._storage.put(this._prefix + hash, tx.toJSON(), function(err) {
if (err) if (err)
self.emit('error', err); self.emit('error', err);
@ -177,13 +185,14 @@ TXPool.prototype._storeTX = function _storeTX(hash, tx, noWrite) {
}; };
TXPool.prototype._removeTX = function _removeTX(tx, noWrite) { TXPool.prototype._removeTX = function _removeTX(tx, noWrite) {
var self = this;
for (var i = 0; i < tx.outputs.length; i++) for (var i = 0; i < tx.outputs.length; i++)
delete this._unspent[tx.hash('hex') + '/' + i]; delete this._unspent[tx.hash('hex') + '/' + i];
if (!this._storage || noWrite) if (!this._storage || noWrite)
return; return;
var self = this;
this._storage.del(this._prefix + tx.hash('hex'), function(err) { this._storage.del(this._prefix + tx.hash('hex'), function(err) {
if (err) if (err)
self.emit('error', err); self.emit('error', err);
@ -194,8 +203,8 @@ TXPool.prototype.all = function all() {
return Object.keys(this._all).map(function(key) { return Object.keys(this._all).map(function(key) {
return this._all[key]; return this._all[key];
}, this).filter(function(tx) { }, this).filter(function(tx) {
return this._wallet.ownOutput(tx) || return this._wallet.ownOutput(tx)
this._wallet.ownInput(tx); || this._wallet.ownInput(tx);
}, this); }, this);
}; };
@ -208,9 +217,11 @@ TXPool.prototype.unspent = function unspent() {
}; };
TXPool.prototype.hasUnspent = function hasUnspent(hash, unspent) { TXPool.prototype.hasUnspent = function hasUnspent(hash, unspent) {
var has;
if (Array.isArray(hash) && hash.length && typeof hash[0] !== 'number') { if (Array.isArray(hash) && hash.length && typeof hash[0] !== 'number') {
unspent = this.unspent(); unspent = this.unspent();
var has = hash.map(function(hash) { has = hash.map(function(hash) {
var h = this.hasUnspent(hash, unspent); var h = this.hasUnspent(hash, unspent);
if (!h) if (!h)
return false; return false;
@ -232,7 +243,7 @@ TXPool.prototype.hasUnspent = function hasUnspent(hash, unspent) {
unspent = unspent || this.unspent(); unspent = unspent || this.unspent();
var has = unspent.filter(function(item) { has = unspent.filter(function(item) {
return item.tx.hash('hex') === hash; return item.tx.hash('hex') === hash;
}); });
@ -279,3 +290,5 @@ TXPool.prototype.fromJSON = function fromJSON(json) {
this.add(bcoin.tx.fromJSON(tx)); this.add(bcoin.tx.fromJSON(tx));
}, this); }, this);
}; };
module.exports = TXPool;

View File

@ -47,7 +47,6 @@ function TX(data, block) {
this.changeAddress = data.changeAddress || null; this.changeAddress = data.changeAddress || null;
} }
module.exports = TX;
TX.fee = 10000; TX.fee = 10000;
TX.dust = 5460; TX.dust = 5460;
@ -77,12 +76,13 @@ TX.prototype.input = function input(i, index) {
}; };
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) if (i instanceof TX)
i = { tx: i, index: index }; i = { tx: i, index: index };
else if (typeof i === 'string' || Array.isArray(i)) else if (typeof i === 'string' || Array.isArray(i))
i = { hash: i, index: index }; i = { hash: i, index: index };
var hash;
if (i.tx) if (i.tx)
hash = i.tx.hash('hex'); hash = i.tx.hash('hex');
else if (i.out) else if (i.out)
@ -93,7 +93,7 @@ TX.prototype._input = function _input(i, index) {
if (typeof hash !== 'string') if (typeof hash !== 'string')
hash = utils.toHex(hash); hash = utils.toHex(hash);
var input = { input = {
out: { out: {
tx: (i.out ? i.out.tx : i.tx) || null, tx: (i.out ? i.out.tx : i.tx) || null,
hash: utils.toHex(hash), hash: utils.toHex(hash),
@ -107,8 +107,8 @@ TX.prototype._input = function _input(i, index) {
utils.hidden(input.script, '_raw', i.script._raw); utils.hidden(input.script, '_raw', i.script._raw);
if (input.out.tx) { if (input.out.tx) {
var prev = input.out.tx.outputs[input.out.index].script; prev = input.out.tx.outputs[input.out.index].script;
var lock = bcoin.script.lockTime(prev); lock = bcoin.script.lockTime(prev);
if (lock) { if (lock) {
if (this._lock === 0) if (this._lock === 0)
this.lock = Math.max(lock.toNumber(), this.lock); this.lock = Math.max(lock.toNumber(), this.lock);
@ -123,9 +123,9 @@ TX.prototype._input = function _input(i, index) {
} }
// Try modifying existing input first // Try modifying existing input first
var index = this._inputIndex(hash, index); index = this._inputIndex(hash, index);
if (index !== -1) { if (index !== -1) {
var ex = this.inputs[index]; ex = this.inputs[index];
input.out.tx = input.out.tx || ex.out.tx; input.out.tx = input.out.tx || ex.out.tx;
input.seq = input.seq || ex.seq; input.seq = input.seq || ex.seq;
input.script = input.script.length ? input.script : ex.script; 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) { TX.prototype._inputIndex = function _inputIndex(hash, index) {
var i, ex;
if (hash instanceof TX) if (hash instanceof TX)
hash = hash.hash('hex'); 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) if (ex.out.hash === hash && ex.out.index === index)
return i; return i;
} }
@ -151,10 +154,12 @@ TX.prototype._inputIndex = function _inputIndex(hash, index) {
}; };
TX.prototype.prevOut = function prevOut(i, def) { TX.prototype.prevOut = function prevOut(i, def) {
var input;
if (typeof i === 'object') if (typeof i === 'object')
i = this.inputs.indexOf(i); i = this.inputs.indexOf(i);
var input = this.inputs[i]; input = this.inputs[i];
if (!input || !input.out.tx || input.out.index == null) if (!input || !input.out.tx || input.out.index == null)
return def; return def;
@ -163,6 +168,8 @@ TX.prototype.prevOut = function prevOut(i, def) {
}; };
TX.prototype.signatureHash = function signatureHash(i, type) { TX.prototype.signatureHash = function signatureHash(i, type) {
var input, s, hash;
if (typeof i === 'object') if (typeof i === 'object')
i = this.inputs.indexOf(i); i = this.inputs.indexOf(i);
@ -173,18 +180,20 @@ TX.prototype.signatureHash = function signatureHash(i, type) {
type = constants.hashType[type]; type = constants.hashType[type];
// Get the current input. // Get the current input.
var input = this.inputs[i]; input = this.inputs[i];
// Get the previous output's subscript // 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. // 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; return hash;
}; };
TX.prototype.signature = function signature(i, key, type) { TX.prototype.signature = function signature(i, key, type) {
var hash, signature;
if (typeof i === 'object') if (typeof i === 'object')
i = this.inputs.indexOf(i); i = this.inputs.indexOf(i);
@ -195,10 +204,10 @@ TX.prototype.signature = function signature(i, key, type) {
type = constants.hashType[type]; type = constants.hashType[type];
// Get the hash of the current tx, minus the other inputs, plus the sighash. // 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 // 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 // Add the sighash as a single byte to the signature
signature = signature.concat(type); signature = signature.concat(type);
@ -210,6 +219,7 @@ TX.prototype.signature = function signature(i, key, type) {
TX.prototype.scriptInput = function scriptInput(input, pub) { TX.prototype.scriptInput = function scriptInput(input, pub) {
// Get the previous output's subscript // Get the previous output's subscript
var s = input.out.tx.getSubscript(input.out.index); var s = input.out.tx.getSubscript(input.out.index);
var n, i, redeem;
// Already has a script template (at least) // Already has a script template (at least)
if (input.script.length) if (input.script.length)
@ -236,11 +246,11 @@ TX.prototype.scriptInput = function scriptInput(input, pub) {
// raw format: OP_FALSE [sig-1] [sig-2] ... // raw format: OP_FALSE [sig-1] [sig-2] ...
if (bcoin.script.isMultisig(s)) { if (bcoin.script.isMultisig(s)) {
input.script = [ [] ]; input.script = [ [] ];
var n = s[s.length - 2]; n = s[s.length - 2];
// If using pushdata instead of OP_1-16: // If using pushdata instead of OP_1-16:
if (Array.isArray(n)) if (Array.isArray(n))
n = n[0] || 0; n = n[0] || 0;
for (var i = 0; i < n; i++) for (i = 0; i < n; i++)
input.script[i + 1] = []; input.script[i + 1] = [];
this._recalculateFee(); this._recalculateFee();
return; return;
@ -250,12 +260,12 @@ TX.prototype.scriptInput = function scriptInput(input, pub) {
// p2sh format: OP_FALSE [sig-1] [sig-2] ... [redeem-script] // p2sh format: OP_FALSE [sig-1] [sig-2] ... [redeem-script]
if (bcoin.script.isScripthash(s)) { if (bcoin.script.isScripthash(s)) {
input.script = [ [] ]; input.script = [ [] ];
var redeem = bcoin.script.decode(pub); redeem = bcoin.script.decode(pub);
var n = redeem[redeem.length - 2]; n = redeem[redeem.length - 2];
// If using pushdata instead of OP_1-16: // If using pushdata instead of OP_1-16:
if (Array.isArray(n)) if (Array.isArray(n))
n = n[0] || 0; n = n[0] || 0;
for (var i = 0; i < n; i++) for (i = 0; i < n; i++)
input.script[i + 1] = []; input.script[i + 1] = [];
// P2SH requires the redeem script after signatures // P2SH requires the redeem script after signatures
input.script.push(pub); input.script.push(pub);
@ -268,6 +278,9 @@ TX.prototype.scriptInput = function scriptInput(input, pub) {
// Sign the now-built scriptSigs // Sign the now-built scriptSigs
TX.prototype.signInput = function signInput(input, key, type) { TX.prototype.signInput = function signInput(input, key, type) {
var s, hash, signature;
var len, redeem, m, keys, pub, pubn, ki, totalSigs, i;
if (!type) if (!type)
type = 'all'; type = 'all';
@ -275,13 +288,13 @@ TX.prototype.signInput = function signInput(input, key, type) {
type = constants.hashType[type]; type = constants.hashType[type];
// Get the previous output's subscript // 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. // 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 // 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 // Add the sighash as a single byte to the signature
signature = signature.concat(type); signature = signature.concat(type);
@ -302,8 +315,7 @@ TX.prototype.signInput = function signInput(input, key, type) {
// raw format: OP_FALSE [sig-1] [sig-2] ... // raw format: OP_FALSE [sig-1] [sig-2] ...
// p2sh format: OP_FALSE [sig-1] [sig-2] ... [redeem-script] // p2sh format: OP_FALSE [sig-1] [sig-2] ... [redeem-script]
if (bcoin.script.isMultisig(s) || bcoin.script.isScripthash(s)) { if (bcoin.script.isMultisig(s) || bcoin.script.isScripthash(s)) {
var len = input.script.length; len = input.script.length;
var redeem;
if (bcoin.script.isScripthash(s)) { if (bcoin.script.isScripthash(s)) {
len--; len--;
@ -312,18 +324,18 @@ TX.prototype.signInput = function signInput(input, key, type) {
redeem = s; redeem = s;
} }
var m = redeem[0]; m = redeem[0];
// If using pushdata instead of OP_1-16: // If using pushdata instead of OP_1-16:
if (Array.isArray(m)) if (Array.isArray(m))
m = m[0] || 0; m = m[0] || 0;
var keys = redeem.slice(1, -2); keys = redeem.slice(1, -2);
var pub = key.getPublic(true, 'array'); pub = key.getPublic(true, 'array');
var pubn = key.getPublic(false, 'array'); pubn = key.getPublic(false, 'array');
// Find the key index so we can place // Find the key index so we can place
// the signature in the same index. // 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])) if (utils.isEqual(pub, keys[ki]) || utils.isEqual(pubn, keys[ki]))
break; break;
} }
@ -336,8 +348,8 @@ TX.prototype.signInput = function signInput(input, key, type) {
// Add our signature to the correct slot // Add our signature to the correct slot
// and count the total number of signatures. // and count the total number of signatures.
var totalSigs = 0; totalSigs = 0;
for (var i = 1; i < len; i++) { for (i = 1; i < len; i++) {
if (Array.isArray(input.script[i]) && input.script[i].length) { if (Array.isArray(input.script[i]) && input.script[i].length) {
totalSigs++; totalSigs++;
continue; continue;
@ -353,7 +365,7 @@ TX.prototype.signInput = function signInput(input, key, type) {
// All signatures added. Finalize by removing empty slots. // All signatures added. Finalize by removing empty slots.
if (totalSigs >= m) { 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) if (Array.isArray(input.script[i]) && !input.script[i].length)
input.script.splice(i, 1); input.script.splice(i, 1);
} }
@ -414,6 +426,8 @@ TX.prototype.scriptOutput = function scriptOutput(output, options) {
options = options || output; options = options || output;
var script = output.script ? output.script.slice() : []; var script = output.script ? output.script.slice() : [];
var keys, m, n;
var hash, color;
if (output.script && output.script._raw) if (output.script && output.script._raw)
utils.hidden(script, '_raw', 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-0011.mediawiki
// https://github.com/bitcoin/bips/blob/master/bip-0019.mediawiki // https://github.com/bitcoin/bips/blob/master/bip-0019.mediawiki
// [required-sigs] [pubkey-hash1] [pubkey-hash2] ... [number-of-keys] checkmultisig // [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) { if (keys === options.address) {
keys = keys.map(function(address) { keys = keys.map(function(address) {
@ -440,8 +454,8 @@ TX.prototype.scriptOutput = function scriptOutput(output, options) {
// compat: // compat:
options.m = options.minSignatures || options.m; options.m = options.minSignatures || options.m;
var m = options.m || keys.length; m = options.m || keys.length;
var n = options.n || keys.length; n = options.n || keys.length;
assert(m >= 1 && m <= n); assert(m >= 1 && m <= n);
if (options.hash) if (options.hash)
@ -453,7 +467,7 @@ TX.prototype.scriptOutput = function scriptOutput(output, options) {
// make it p2sh // make it p2sh
if (options.scripthash) { if (options.scripthash) {
var hash = utils.ripesha(bcoin.script.encode(script)); hash = utils.ripesha(bcoin.script.encode(script));
script = [ script = [
'hash160', 'hash160',
hash, hash,
@ -480,7 +494,7 @@ TX.prototype.scriptOutput = function scriptOutput(output, options) {
'checksig' 'checksig'
]; ];
} else if (options.color) { } else if (options.color) {
var color = options.color; color = options.color;
if (typeof color === 'string') if (typeof color === 'string')
color = utils.ascii2array(color); color = utils.ascii2array(color);
assert(color.length <= 40); assert(color.length <= 40);
@ -494,15 +508,13 @@ TX.prototype.scriptOutput = function scriptOutput(output, options) {
}; };
TX.prototype.getSubscript = function getSubscript(index) { TX.prototype.getSubscript = function getSubscript(index) {
var output = this.outputs[index]; var script = this.outputs[index].script;
assert(output);
var script = output.script;
return bcoin.script.subscript(script); return bcoin.script.subscript(script);
}; };
TX.prototype.subscriptHash = function subscriptHash(index, s, type) { TX.prototype.subscriptHash = function subscriptHash(index, s, type) {
var copy = this.clone(); var copy = this.clone();
var verifyStr, hash;
if (typeof type === 'string') if (typeof type === 'string')
type = constants.hashType[type]; type = constants.hashType[type];
@ -550,11 +562,11 @@ TX.prototype.subscriptHash = function subscriptHash(index, s, type) {
copy.inputs[0].script = s; copy.inputs[0].script = s;
} }
var verifyStr = copy.render(); verifyStr = copy.render();
utils.writeU32(verifyStr, type, verifyStr.length); utils.writeU32(verifyStr, type, verifyStr.length);
var hash = utils.dsha256(verifyStr); hash = utils.dsha256(verifyStr);
return hash; return hash;
}; };
@ -568,6 +580,8 @@ TX.prototype.verify = function verify(index, force) {
return false; return false;
return this.inputs.every(function(input, i) { return this.inputs.every(function(input, i) {
var stack, prev, push, res, redeem;
if (index !== undefined && index !== i) if (index !== undefined && index !== i)
return true; return true;
@ -576,19 +590,19 @@ TX.prototype.verify = function verify(index, force) {
assert(input.out.tx.outputs.length > input.out.index); assert(input.out.tx.outputs.length > input.out.index);
var stack = []; stack = [];
var prev = input.out.tx.outputs[input.out.index].script; prev = input.out.tx.outputs[input.out.index].script;
if (bcoin.script.isScripthash(prev)) { if (bcoin.script.isScripthash(prev)) {
// p2sh transactions cannot have anything // p2sh transactions cannot have anything
// other than pushdata ops in the scriptSig // 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) if (!push)
return false; return false;
} }
bcoin.script.execute(input.script, stack, this, i); 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) if (!res)
return false; return false;
@ -598,7 +612,7 @@ TX.prototype.verify = function verify(index, force) {
return false; return false;
if (bcoin.script.isScripthash(prev)) { if (bcoin.script.isScripthash(prev)) {
var redeem = input.script[input.script.length - 1]; redeem = input.script[input.script.length - 1];
if (!Array.isArray(redeem)) if (!Array.isArray(redeem))
return false; return false;
redeem = bcoin.script.decode(redeem); redeem = bcoin.script.decode(redeem);
@ -618,19 +632,23 @@ TX.prototype.isCoinbase = function isCoinbase() {
TX.prototype.maxSize = function maxSize() { TX.prototype.maxSize = function maxSize() {
// Create copy with 0-script inputs // Create copy with 0-script inputs
var copy = this.clone(); var copy = this.clone();
var size;
copy.inputs.forEach(function(input) { copy.inputs.forEach(function(input) {
input.script = []; input.script = [];
}); });
var size = copy.render().length; size = copy.render().length;
// Add size for signatures and public keys // Add size for signatures and public keys
copy.inputs.forEach(function(input, i) { copy.inputs.forEach(function(input, i) {
var s, m, n, script, redeem;
// Get the previous output's script // 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 // 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)) { if (bcoin.script.isPubkey(s)) {
// Signature + len // Signature + len
@ -651,7 +669,7 @@ TX.prototype.maxSize = function maxSize() {
// Empty byte // Empty byte
size += 1; size += 1;
// Signature + len // Signature + len
var m = s[0]; m = s[0];
// If using pushdata instead of OP_1-16: // If using pushdata instead of OP_1-16:
if (Array.isArray(m)) if (Array.isArray(m))
m = m[0] || 0; m = m[0] || 0;
@ -661,8 +679,7 @@ TX.prototype.maxSize = function maxSize() {
} }
if (bcoin.script.isScripthash(s)) { if (bcoin.script.isScripthash(s)) {
var script = this.inputs[i].script; script = this.inputs[i].script;
var redeem, m, n;
if (script.length) { if (script.length) {
redeem = bcoin.script.decode(script[script.length - 1]); redeem = bcoin.script.decode(script[script.length - 1]);
m = redeem[0]; m = redeem[0];
@ -724,6 +741,9 @@ TX.prototype.utxos = function utxos(unspent) {
var utxos = []; var utxos = [];
var lastAdded = 0; var lastAdded = 0;
var byteSize, addFee, change;
function addInput(unspent, i) { function addInput(unspent, i) {
// Add new inputs until TX will have enough funds to cover both // Add new inputs until TX will have enough funds to cover both
// minimum post cost and fee // minimum post cost and fee
@ -744,9 +764,9 @@ TX.prototype.utxos = function utxos(unspent) {
// (10000 satoshi for every 1024 bytes) // (10000 satoshi for every 1024 bytes)
do { do {
// Calculate maximum possible size after signing // 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)); total.iadd(new bn(addFee * TX.fee));
fee += addFee; fee += addFee;
@ -764,7 +784,7 @@ TX.prototype.utxos = function utxos(unspent) {
} }
// How much money is left after sending outputs // 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. // Clear the tx of everything we added.
this.inputs = inputs; this.inputs = inputs;
@ -812,6 +832,7 @@ TX.prototype.fillUnspent = function fillUnspent(unspent, changeAddress) {
TX.prototype._recalculateFee = function recalculateFee() { TX.prototype._recalculateFee = function recalculateFee() {
var output = this.changeOutput; var output = this.changeOutput;
if (!output) { if (!output) {
this.output({ this.output({
address: this.changeAddress, address: this.changeAddress,
@ -864,26 +885,28 @@ TX.getInputData = function getInputData(input) {
if (!input || !input.script) return; if (!input || !input.script) return;
var script = input.script; var script = input.script;
var scriptSig, pub, hash, addr, redeem, data;
var output;
if (bcoin.script.isPubkeyhashInput(script)) { if (bcoin.script.isPubkeyhashInput(script)) {
var scriptSig = utils.toHex(script[0]); scriptSig = utils.toHex(script[0]);
var pubKey = script[1]; pub = script[1];
var hash = utils.ripesha(pubKey); hash = utils.ripesha(pub);
var addr = bcoin.wallet.hash2addr(hash); addr = bcoin.wallet.hash2addr(hash);
return { return {
sig: scriptSig, sig: scriptSig,
pub: pubKey, pub: pub,
hash: hash, hash: hash,
addr: addr addr: addr
}; };
} }
if (bcoin.script.isScripthashInput(script)) { if (bcoin.script.isScripthashInput(script)) {
var pub = script[script.length - 1]; pub = script[script.length - 1];
var hash = utils.ripesha(pub); hash = utils.ripesha(pub);
var addr = bcoin.wallet.hash2addr(hash, 'scripthash'); addr = bcoin.wallet.hash2addr(hash, 'scripthash');
var redeem = bcoin.script.decode(pub); redeem = bcoin.script.decode(pub);
var data = TX.getOutputData({ script: redeem }); data = TX.getOutputData({ script: redeem });
data.pub = pub; data.pub = pub;
data.hash = hash; data.hash = hash;
data.addr = addr; data.addr = addr;
@ -904,7 +927,7 @@ TX.getInputData = function getInputData(input) {
if (!input.out.tx) if (!input.out.tx)
return; return;
var output = input.out.tx.outputs[input.out.index]; output = input.out.tx.outputs[input.out.index];
return TX.getOutputData(output); return TX.getOutputData(output);
}; };
@ -913,22 +936,23 @@ TX.getOutputData = function getOutputData(output) {
if (!output || !output.script) return; if (!output || !output.script) return;
var script = output.script; var script = output.script;
var pub, hash, addr, pubs;
if (bcoin.script.isPubkey(script)) { if (bcoin.script.isPubkey(script)) {
var pubKey = script[0]; pub = script[0];
var hash = utils.ripesha(pubKey); hash = utils.ripesha(pub);
var addr = bcoin.wallet.hash2addr(hash); addr = bcoin.wallet.hash2addr(hash);
return { return {
sig: null, sig: null,
pub: pubKey, pub: pub,
hash: hash, hash: hash,
addr: addr addr: addr
}; };
} }
if (bcoin.script.isPubkeyhash(script)) { if (bcoin.script.isPubkeyhash(script)) {
var hash = script[2]; hash = script[2];
var addr = bcoin.wallet.hash2addr(hash); addr = bcoin.wallet.hash2addr(hash);
return { return {
sig: null, sig: null,
pub: 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) { if (pubs) {
var hash = utils.ripesha(pubs[0]); hash = utils.ripesha(pubs[0]);
var addr = bcoin.wallet.hash2addr(hash); addr = bcoin.wallet.hash2addr(hash);
return { return {
sig: null, sig: null,
pub: pubs[0], pub: pubs[0],
@ -963,8 +987,8 @@ TX.getOutputData = function getOutputData(output) {
} }
if (bcoin.script.isScripthash(script)) { if (bcoin.script.isScripthash(script)) {
var hash = utils.toHex(s[1]); hash = utils.toHex(s[1]);
var addr = bcoin.wallet.hash2addr(hash, 'scripthash'); addr = bcoin.wallet.hash2addr(hash, 'scripthash');
return { return {
sig: null, sig: null,
pub: null, pub: null,
@ -991,12 +1015,14 @@ TX.prototype.getFee = function getFee() {
}; };
TX.prototype.funds = function funds(side) { TX.prototype.funds = function funds(side) {
var acc = new bn(0);
var inputs;
if (side === 'in') { if (side === 'in') {
var inputs = this.inputs.filter(function(input) { inputs = this.inputs.filter(function(input) {
return input.out.tx; return input.out.tx;
}); });
var acc = new bn(0);
if (inputs.length === 0) if (inputs.length === 0)
return acc; return acc;
@ -1008,7 +1034,6 @@ TX.prototype.funds = function funds(side) {
} }
// Output // Output
var acc = new bn(0);
if (this.outputs.length === 0) if (this.outputs.length === 0)
return acc; return acc;
@ -1043,3 +1068,5 @@ TX.fromJSON = function fromJSON(json) {
return tx; return tx;
}; };
module.exports = TX;

View File

@ -7,15 +7,19 @@ var util = require('util');
function toArray(msg, enc) { function toArray(msg, enc) {
if (Array.isArray(msg)) if (Array.isArray(msg))
return msg.slice(); return msg.slice();
if (!msg) if (!msg)
return []; return [];
var res = []; var res = [];
var i, c, hi, lo, slice, num;
if (typeof msg === 'string') { if (typeof msg === 'string') {
if (!enc) { if (!enc) {
for (var i = 0; i < msg.length; i++) { for (i = 0; i < msg.length; i++) {
var c = msg.charCodeAt(i); c = msg.charCodeAt(i);
var hi = c >> 8; hi = c >> 8;
var lo = c & 0xff; lo = c & 0xff;
if (hi) if (hi)
res.push(hi, lo); res.push(hi, lo);
else else
@ -25,9 +29,10 @@ function toArray(msg, enc) {
msg = msg.replace(/[^a-z0-9]+/ig, ''); msg = msg.replace(/[^a-z0-9]+/ig, '');
if (msg.length % 2 !== 0) if (msg.length % 2 !== 0)
msg = '0' + msg; msg = '0' + msg;
for (var i = 0; i < msg.length; i += 8) {
var slice = msg.slice(i, i + 8); for (i = 0; i < msg.length; i += 8) {
var num = parseInt(slice, 16); slice = msg.slice(i, i + 8);
num = parseInt(slice, 16);
if (slice.length === 8) if (slice.length === 8)
res.push((num >>> 24) & 0xff); res.push((num >>> 24) & 0xff);
@ -39,15 +44,16 @@ function toArray(msg, enc) {
} }
} }
} else { } else {
for (var i = 0; i < msg.length; i++) for (i = 0; i < msg.length; i++)
res[i] = msg[i] | 0; res[i] = msg[i] | 0;
} }
return res; return res;
} }
utils.toArray = toArray; utils.toArray = toArray;
var base58 = '123456789ABCDEFGHJKLMNPQRSTUVWXYZ' + var base58 = '123456789ABCDEFGHJKLMNPQRSTUVWXYZ'
'abcdefghijkmnopqrstuvwxyz'; + 'abcdefghijkmnopqrstuvwxyz';
utils.toBase58 = function toBase58(arr) { utils.toBase58 = function toBase58(arr) {
var n = new bn(arr, 16); var n = new bn(arr, 16);
@ -56,17 +62,20 @@ utils.toBase58 = function toBase58(arr) {
var mod = new bn(0xacad10); var mod = new bn(0xacad10);
var res = ''; var res = '';
var r, end, i, c;
do { do {
var r = n.mod(mod); r = n.mod(mod);
n = n.div(mod); n = n.div(mod);
var end = n.cmpn(0) === 0; end = n.cmpn(0) === 0;
utils.assert.equal(r.length, 1); utils.assert.equal(r.length, 1);
r = r.words[0]; r = r.words[0];
for (var i = 0; i < 4; i++) { for (i = 0; i < 4; i++) {
var c = r % 58; c = r % 58;
r = (r - c) / 58; r = (r - c) / 58;
if (c === 0 && r === 0 && end) if (c === 0 && r === 0 && end)
@ -77,7 +86,7 @@ utils.toBase58 = function toBase58(arr) {
} while (!end); } while (!end);
// Add leading "zeroes" // Add leading "zeroes"
for (var i = 0; i < arr.length; i++) { for (i = 0; i < arr.length; i++) {
if (arr[i] !== 0) if (arr[i] !== 0)
break; break;
res = '1' + res; res = '1' + res;
@ -87,18 +96,20 @@ utils.toBase58 = function toBase58(arr) {
}; };
utils.fromBase58 = function fromBase58(str) { utils.fromBase58 = function fromBase58(str) {
var i, zeroes, q, w, res, c, z;
// Count leading "zeroes" // Count leading "zeroes"
for (var i = 0; i < str.length; i++) for (i = 0; i < str.length; i++)
if (str[i] !== '1') if (str[i] !== '1')
break; break;
var zeroes = i; zeroes = i;
// Read 4-char words and add them to bignum // Read 4-char words and add them to bignum
var q = 1; q = 1;
var w = 0; w = 0;
var res = new bn(0); res = new bn(0);
for (var i = zeroes; i < str.length; i++) { for (i = zeroes; i < str.length; i++) {
var c = base58.indexOf(str[i]); c = base58.indexOf(str[i]);
if (!(c >= 0 && c < 58)) if (!(c >= 0 && c < 58))
return []; return [];
@ -113,9 +124,10 @@ utils.fromBase58 = function fromBase58(str) {
} }
// Add leading "zeroes" // Add leading "zeroes"
var z = []; z = [];
for (var i = 0; i < zeroes; i++) for (i = 0; i < zeroes; i++)
z.push(0); z.push(0);
return z.concat(res.toArray()); return z.concat(res.toArray());
}; };
@ -152,12 +164,15 @@ utils.readU16 = function readU16(arr, off) {
utils.readU32 = function readU32(arr, off) { utils.readU32 = function readU32(arr, off) {
if (!off) if (!off)
off = 0; off = 0;
var r = arr[off] |
(arr[off + 1] << 8) | var r = arr[off]
(arr[off + 2] << 16) | | (arr[off + 1] << 8)
(arr[off + 3] << 24); | (arr[off + 2] << 16)
| (arr[off + 3] << 24);
if (r < 0) if (r < 0)
r += 0x100000000; r += 0x100000000;
return r; return r;
}; };
@ -186,6 +201,8 @@ utils.writeU32 = function writeU32(dst, num, off) {
}; };
utils.writeU64 = function writeU64(dst, num, off) { utils.writeU64 = function writeU64(dst, num, off) {
var i = 0;
if (!off) if (!off)
off = 0; off = 0;
@ -197,7 +214,8 @@ utils.writeU64 = function writeU64(dst, num, off) {
dst[off++] = ch; dst[off++] = ch;
}); });
var i = num.length; i = num.length;
while (i--) while (i--)
dst[off++] = num[i]; dst[off++] = num[i];
@ -223,6 +241,8 @@ utils.writeU32BE = function writeU32BE(dst, num, off) {
}; };
utils.writeU64BE = function writeU64BE(dst, num, off) { utils.writeU64BE = function writeU64BE(dst, num, off) {
var i = 0;
if (!off) if (!off)
off = 0; off = 0;
@ -230,7 +250,7 @@ utils.writeU64BE = function writeU64BE(dst, num, off) {
while (num.length < 8) while (num.length < 8)
num.unshift(0); num.unshift(0);
for (var i = 0; i < num.length; i++) for (; i < num.length; i++)
dst[off++] = num[i]; dst[off++] = num[i];
return 8; return 8;
@ -245,12 +265,15 @@ utils.readU16BE = function readU16BE(arr, off) {
utils.readU32BE = function readU32BE(arr, off) { utils.readU32BE = function readU32BE(arr, off) {
if (!off) if (!off)
off = 0; off = 0;
var r = (arr[off] << 24) |
(arr[off + 1] << 16) | var r = (arr[off] << 24)
(arr[off + 2] << 8) | | (arr[off + 1] << 16)
arr[off + 3]; | (arr[off + 2] << 8)
| arr[off + 3];
if (r < 0) if (r < 0)
r += 0x100000000; r += 0x100000000;
return r; return r;
}; };
@ -261,19 +284,27 @@ utils.readU64BE = function readU64BE(arr, off) {
}; };
utils.writeAscii = function writeAscii(dst, str, off) { utils.writeAscii = function writeAscii(dst, str, off) {
for (var i = 0; i < str.length; i++) { var i = 0;
var c = str.charCodeAt(i); var c;
for (; i < str.length; i++) {
c = str.charCodeAt(i);
dst[off + i] = c & 0xff; dst[off + i] = c & 0xff;
} }
return i; return i;
}; };
utils.readAscii = function readAscii(arr, off, len) { utils.readAscii = function readAscii(arr, off, len) {
var str = ''; var str = '';
for (var i = off; i < off + len; i++) { var i = off;
var c = String.fromCharCode(arr[i] & 0xff); var c;
for (i = off; i < off + len; i++) {
c = String.fromCharCode(arr[i] & 0xff);
str += c; str += c;
} }
return str; return str;
}; };
@ -290,17 +321,24 @@ utils.array2ascii = function array2ascii(arr) {
utils.copy = function copy(src, dst, off, force) { utils.copy = function copy(src, dst, off, force) {
var len = src.length; var len = src.length;
var i = 0;
if (!force) if (!force)
len = Math.min(dst.length - off, len); len = Math.min(dst.length - off, len);
for (var i = 0; i < len; i++)
for (; i < len; i++)
dst[i + off] = src[i]; dst[i + off] = src[i];
return i; return i;
}; };
utils.stringify = function stringify(arr) { utils.stringify = function stringify(arr) {
var res = ''; var res = '';
for (var i = 0; i < arr.length; i++) var i = 0;
for (; i < arr.length; i++)
res += String.fromCharCode(arr[i]); res += String.fromCharCode(arr[i]);
return res; return res;
}; };
@ -312,23 +350,28 @@ function zero2(word) {
} }
function toHex(msg) { function toHex(msg) {
var res = '';
var i = 0;
if (typeof msg === 'string') if (typeof msg === 'string')
return msg; return msg;
var res = ''; for (; i < msg.length; i++)
for (var i = 0; i < msg.length; i++)
res += zero2(msg[i].toString(16)); res += zero2(msg[i].toString(16));
return res; return res;
} }
utils.toHex = toHex; utils.toHex = toHex;
function binaryInsert(list, item, compare, search) { function binaryInsert(list, item, compare, search) {
var start = 0, var start = 0;
end = list.length; var end = list.length;
var pos, cmp;
while (start < end) { while (start < end) {
var pos = (start + end) >> 1; pos = (start + end) >> 1;
var cmp = compare(item, list[pos]); cmp = compare(item, list[pos]);
if (cmp === 0) { if (cmp === 0) {
start = pos; start = pos;
@ -343,8 +386,10 @@ function binaryInsert(list, item, compare, search) {
if (!search) if (!search)
list.splice(start, 0, item); list.splice(start, 0, item);
return start; return start;
} }
utils.binaryInsert = binaryInsert; utils.binaryInsert = binaryInsert;
function bitsToTarget(bits) { function bitsToTarget(bits) {
@ -352,26 +397,31 @@ function bitsToTarget(bits) {
var hi = (bits >>> 16) & 0xff; var hi = (bits >>> 16) & 0xff;
var mid = (bits >>> 8) & 0xff; var mid = (bits >>> 8) & 0xff;
var lo = bits & 0xff; var lo = bits & 0xff;
var res = new Array(len); 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] = 0;
res[i++] = lo; res[i++] = lo;
res[i++] = mid; res[i++] = mid;
res[i++] = hi; res[i++] = hi;
if (hi === 0) if (hi === 0)
res.pop(); res.pop();
if (hi === 0 && mid === 0) if (hi === 0 && mid === 0)
res.pop(); res.pop();
return res; return res;
} }
utils.bitsToTarget = bitsToTarget; utils.bitsToTarget = bitsToTarget;
function testTarget(target, hash) { function testTarget(target, hash) {
if (typeof target === 'number') if (typeof target === 'number')
target = bitsToTarget(target); target = bitsToTarget(target);
hash = utils.toArray(hash, 'hex'); hash = utils.toArray(hash, 'hex');
for (var i = hash.length - 1; i >= target.length; i--) for (var i = hash.length - 1; i >= target.length; i--)
@ -388,13 +438,16 @@ function testTarget(target, hash) {
return true; return true;
} }
utils.testTarget = testTarget; utils.testTarget = testTarget;
utils.isEqual = function isEqual(a, b) { utils.isEqual = function isEqual(a, b) {
var i = 0;
if (a.length !== b.length) if (a.length !== b.length)
return false; return false;
for (var i = 0; i < a.length; i++) for (; i < a.length; i++)
if (a[i] !== b[i]) if (a[i] !== b[i])
return false; return false;
@ -433,12 +486,14 @@ RequestCache.prototype.add = function add(id, cb) {
}; };
RequestCache.prototype.fullfill = function fullfill(id, err, data) { RequestCache.prototype.fullfill = function fullfill(id, err, data) {
var cbs = this.map[id];
if (!this.map[id]) if (!this.map[id])
return; return;
var cbs = this.map[id];
delete this.map[id]; delete this.map[id];
this.count--; this.count--;
cbs.forEach(function(cb) { cbs.forEach(function(cb) {
cb(err, data); cb(err, data);
}); });
@ -456,8 +511,11 @@ utils.asyncify = function asyncify(fn) {
utils.revHex = function revHex(s) { utils.revHex = function revHex(s) {
var r = ''; 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; r = s.slice(i, i + 2) + r;
return r; return r;
}; };
@ -472,6 +530,9 @@ utils.assert.equal = function assertEqual(l, r, msg) {
}; };
utils.toBTC = function toBTC(satoshi, strict) { 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)) { if (typeof satoshi === 'string' && /^\d+(?:\.\d+)?$/.test(satoshi)) {
satoshi = new bn(satoshi, 10); satoshi = new bn(satoshi, 10);
} else if (typeof satoshi === 'number') { } else if (typeof satoshi === 'number') {
@ -483,8 +544,6 @@ utils.toBTC = function toBTC(satoshi, strict) {
if (!(satoshi instanceof bn)) if (!(satoshi instanceof bn))
throw new Error('could not calculate btc'); 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) { if (lo.cmpn(0) !== 0) {
lo = lo.toString(10); lo = lo.toString(10);
while (lo.length < 8) while (lo.length < 8)
@ -498,10 +557,13 @@ utils.toBTC = function toBTC(satoshi, strict) {
}; };
utils.fromBTC = function(btc, strict) { utils.fromBTC = function(btc, strict) {
var m = new bn(10000000).mul(new bn(10));
var satoshi; var satoshi;
var parts;
if (typeof btc === 'string' && /^\d+(?:\.\d+)?$/.test(btc)) { if (typeof btc === 'string' && /^\d+(?:\.\d+)?$/.test(btc)) {
var parts = btc.split('.'); parts = btc.split('.');
parts[0] = parts[0] || '0'; parts[0] = parts[0] || '0';
parts[1] = parts[1] || '0'; parts[1] = parts[1] || '0';
while (parts[1].length < 8) while (parts[1].length < 8)
@ -517,8 +579,8 @@ utils.fromBTC = function(btc, strict) {
if (!(satoshi instanceof bn)) if (!(satoshi instanceof bn))
throw new Error('could not calculate satoshis'); throw new Error('could not calculate satoshis');
var m = new bn(10000000).mul(new bn(10));
satoshi.imuln(m); satoshi.imuln(m);
return satoshi; return satoshi;
}; };

View File

@ -82,18 +82,19 @@ function Wallet(options, passphrase) {
this._init(); this._init();
} }
inherits(Wallet, EventEmitter); inherits(Wallet, EventEmitter);
module.exports = Wallet;
Wallet.prototype._init = function init() { Wallet.prototype._init = function init() {
var self = this;
var prevBalance = null;
if (this.tx._loaded) { if (this.tx._loaded) {
this.loaded = true; this.loaded = true;
return; return;
} }
// Notify owners about new accepted transactions // Notify owners about new accepted transactions
var self = this;
var prevBalance = null;
this.tx.on('update', function(lastTs, tx) { this.tx.on('update', function(lastTs, tx) {
var b = this.balance(); var b = this.balance();
if (prevBalance && prevBalance.cmp(b) !== 0) if (prevBalance && prevBalance.cmp(b) !== 0)
@ -203,35 +204,43 @@ Wallet.prototype.derive = function derive() {
Wallet.prototype.getPrivateKey = function getPrivateKey(enc) { Wallet.prototype.getPrivateKey = function getPrivateKey(enc) {
var priv = this.key.getPrivate(); var priv = this.key.getPrivate();
var arr, chk;
if (priv) if (priv)
priv = priv.toArray(); priv = priv.toArray();
else else
return; return;
if (!enc) if (!enc)
return priv; return priv;
if (enc === 'base58') { if (enc === 'base58') {
// We'll be using ncompressed public key as an address // We'll be using ncompressed public key as an address
var arr = [ network.prefixes.privkey ]; arr = [ network.prefixes.privkey ];
// 0-pad key // 0-pad key
while (arr.length + priv.length < 33) while (arr.length + priv.length < 33)
arr.push(0); arr.push(0);
arr = arr.concat(priv); arr = arr.concat(priv);
if (this.compressed) if (this.compressed)
arr.push(1); arr.push(1);
var chk = utils.checksum(arr);
chk = utils.checksum(arr);
return utils.toBase58(arr.concat(chk)); return utils.toBase58(arr.concat(chk));
} else {
return priv;
} }
return priv;
}; };
Wallet.prototype.getFullPublicKey = function getFullPublicKey(enc) { Wallet.prototype.getFullPublicKey = function getFullPublicKey(enc) {
var pub = this.getOwnPublicKey(); var pub = this.getOwnPublicKey();
var keys;
if (this.type === 'scripthash') { if (this.type === 'scripthash') {
var keys = this.getPublicKeys(); keys = this.getPublicKeys();
pub = bcoin.script.encode(bcoin.script.multisig(keys, this.m, this.n)); 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) { Wallet.hash2addr = function hash2addr(hash, prefix) {
var addr;
hash = utils.toArray(hash, 'hex'); hash = utils.toArray(hash, 'hex');
prefix = network.prefixes[prefix || 'pubkeyhash']; prefix = network.prefixes[prefix || 'pubkeyhash'];
hash = [ prefix ].concat(hash); hash = [ prefix ].concat(hash);
var addr = hash.concat(utils.checksum(hash)); addr = hash.concat(utils.checksum(hash));
return utils.toBase58(addr); return utils.toBase58(addr);
}; };
@ -317,6 +328,8 @@ Wallet.__defineGetter__('prefixes', function() {
}); });
Wallet.addr2hash = function addr2hash(addr, prefix) { Wallet.addr2hash = function addr2hash(addr, prefix) {
var chk;
if (prefix == null && typeof addr === 'string') if (prefix == null && typeof addr === 'string')
prefix = Wallet.prefixes[addr[0]]; prefix = Wallet.prefixes[addr[0]];
@ -330,7 +343,7 @@ Wallet.addr2hash = function addr2hash(addr, prefix) {
if (addr[0] !== prefix) if (addr[0] !== prefix)
return []; 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)) if (utils.readU32(chk, 0) !== utils.readU32(addr, 21))
return []; return [];
@ -352,11 +365,11 @@ Wallet.prototype.ownOutput = function ownOutput(tx, index) {
var keys = this.getPublicKeys(); var keys = this.getPublicKeys();
var outputs = tx.outputs.filter(function(output, i) { var outputs = tx.outputs.filter(function(output, i) {
var s = output.script;
if (index !== undefined && index !== i) if (index !== undefined && index !== i)
return false; return false;
var s = output.script;
if (bcoin.script.isPubkey(s, key)) if (bcoin.script.isPubkey(s, key))
return true; return true;
@ -386,6 +399,8 @@ Wallet.prototype.ownInput = function ownInput(tx, index) {
var keys = this.getPublicKeys(); var keys = this.getPublicKeys();
var inputs = tx.inputs.filter(function(input, i) { var inputs = tx.inputs.filter(function(input, i) {
var s;
if (index !== undefined && index !== i) if (index !== undefined && index !== i)
return false; return false;
@ -404,7 +419,7 @@ Wallet.prototype.ownInput = function ownInput(tx, index) {
if (!input.out.tx) if (!input.out.tx)
return false; 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)) if (bcoin.script.isPubkey(s, key))
return true; return true;
@ -468,11 +483,11 @@ Wallet.prototype.scriptInputs = function scriptInputs(tx, inputs) {
}; };
Wallet.prototype.signInputs = function(tx, type, inputs) { Wallet.prototype.signInputs = function(tx, type, inputs) {
var key = this.key;
if (!type) if (!type)
type = 'all'; type = 'all';
var key = this.key;
inputs = inputs || tx.inputs; inputs = inputs || tx.inputs;
inputs = inputs.filter(function(input) { inputs = inputs.filter(function(input) {
@ -531,16 +546,22 @@ Wallet.prototype.balance = function balance() {
}; };
Wallet.prototype.fill = function fill(tx, cb) { Wallet.prototype.fill = function fill(tx, cb) {
cb = utils.asyncify(cb);
var result = tx.fillUnspent(this.unspent(), this.getAddress()); var result = tx.fillUnspent(this.unspent(), this.getAddress());
var err;
cb = utils.asyncify(cb);
if (!result) { if (!result) {
var err = new Error('Not enough funds'); err = new Error('Not enough funds');
err.minBalance = tx.total; err.minBalance = tx.total;
cb(err); cb(err);
return null; return null;
} }
this.sign(tx); this.sign(tx);
cb(null, tx); cb(null, tx);
return tx; return tx;
}; };
@ -590,12 +611,10 @@ Wallet.fromJSON = function fromJSON(json, decrypt) {
if (decrypt) if (decrypt)
json.priv = decrypt(json.priv); json.priv = decrypt(json.priv);
var priv; var priv, pub, compressed, key, w;
var pub;
var compressed;
if (json.priv) { 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(utils.isEqual(key.slice(-4), utils.checksum(key.slice(0, -4))));
assert.equal(key[0], network.prefixes.privkey); assert.equal(key[0], network.prefixes.privkey);
@ -621,7 +640,7 @@ Wallet.fromJSON = function fromJSON(json, decrypt) {
priv = new hd.priv(json.hd); priv = new hd.priv(json.hd);
} }
var w = new Wallet({ w = new Wallet({
label: json.label, label: json.label,
priv: priv, priv: priv,
pub: pub, pub: pub,
@ -633,3 +652,5 @@ Wallet.fromJSON = function fromJSON(json, decrypt) {
return w; return w;
}; };
module.exports = Wallet;