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