get blockchain sync working. fix pushdata ops.

This commit is contained in:
Christopher Jeffrey 2015-12-16 20:32:01 -08:00
parent 5e5507f668
commit 88ddb3620c
9 changed files with 233 additions and 61 deletions

View File

@ -69,6 +69,10 @@ var pool = new bcoin.pool({
}) })
}); });
pool.on('error', function(err) {
console.log('Error: %s', err.message);
});
// Receive the address of another peer. // Receive the address of another peer.
pool.on('addr', function(data, peer) { pool.on('addr', function(data, peer) {
var host = data.ipv4 + ':' + data.port; var host = data.ipv4 + ':' + data.port;

View File

@ -1,5 +1,6 @@
var bcoin = require('../bcoin'); var bcoin = require('../bcoin');
var utils = bcoin.utils; var utils = bcoin.utils;
var constants = bcoin.protocol.constants;
function Block(data, subtype) { function Block(data, subtype) {
if (!(this instanceof Block)) if (!(this instanceof Block))
@ -36,7 +37,7 @@ function Block(data, subtype) {
return tx.hash('hex'); return tx.hash('hex');
}); });
this.invalid = !this._checkBlock(); this.invalid = !this._checkBlock();
this.hashes = this.merkleTree; this.hashes = [];
} }
this._hash = null; this._hash = null;
@ -127,21 +128,31 @@ Block.prototype._verifyMerkle = function verifyMerkle() {
} }
}; };
Block.prototype._buildMerkle = function buildMerkle() { Block.prototype.getMerkleRoot = function getMerkleRoot() {
var merkleTree = []; var merkleTree = [];
for (var i = 0; i < this.txs.length; i++) { for (var i = 0; i < this.txs.length; i++) {
merkleTree.push(this.txs[i].hash('hex')); merkleTree.push(this.txs[i].hash('hex'));
} }
var j = 0; var j = 0;
for (var size = this.txs.length; size > 1; size = ((size + 1) / 2) | 0) { for (var size = this.txs.length; size > 1; size = ((size + 1) / 2) | 0) {
for (var i = 0; i < size; i += 2) { for (var i = 0; i < size; i += 2) {
var i2 = Math.min(i + 1, size - 1); var i2 = Math.min(i + 1, size - 1);
var hash = utils.dsha256(merkleTree[j+i] + merkleTree[j+i2], 'hex'); 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');
merkleTree.push(utils.toHex(hash)); merkleTree.push(utils.toHex(hash));
} }
j += size; j += size;
} }
return merkleTree;
if (!merkleTree.length)
return utils.toHex(constants.zeroHash);
return merkleTree[merkleTree.length - 1];
}; };
// This mimics the behavior of CheckBlockHeader() // This mimics the behavior of CheckBlockHeader()
@ -172,9 +183,6 @@ Block.prototype._checkBlock = function checkBlock() {
return false; return false;
} }
// Build MerkleTree
this.merkleTree = this._buildMerkle();
// Check for duplicate tx ids // Check for duplicate tx ids
var unique = {}; var unique = {};
for (var i = 0; i < this.txs.length; i++) { for (var i = 0; i < this.txs.length; i++) {
@ -184,8 +192,14 @@ Block.prototype._checkBlock = function checkBlock() {
unique[hash] = true; unique[hash] = true;
} }
// Build MerkleTree
var merkleRoot = this.getMerkleRoot();
// Check merkle root // Check merkle root
return this.merkleTree[this.merkleTree.length - 1] === this.merkleRoot; if (merkleRoot !== this.merkleRoot)
return false;
return true;
}; };
Block.prototype.toJSON = function toJSON() { Block.prototype.toJSON = function toJSON() {
@ -212,7 +226,7 @@ Block.fromJSON = function fromJSON(json) {
parser.parseMerkleBlock(raw) : parser.parseMerkleBlock(raw) :
parser.parseBlock(raw); parser.parseBlock(raw);
var block = new bcoin.block(data, json.subtype); var block = new Block(data, json.subtype);
block._hash = json.hash; block._hash = json.hash;

View File

@ -153,6 +153,14 @@ Chain.prototype._addIndex = function _addIndex(hash, ts, height) {
return; return;
} }
// var checkpoint = network.checkpoints[height];
// if (checkpoint) {
// this.emit('checkpoint', height, hash, checkpoint);
// if (hash !== checkpoint) {
// this.resetLastCheckpoint(height);
// }
// }
this.index.ts.splice(pos, 0, ts); this.index.ts.splice(pos, 0, ts);
this.index.hashes.splice(pos, 0, hash); this.index.hashes.splice(pos, 0, hash);
this.index.heights.splice(pos, 0, height); this.index.heights.splice(pos, 0, height);
@ -169,6 +177,33 @@ Chain.prototype._addIndex = function _addIndex(hash, ts, height) {
}); });
}; };
Chain.prototype.resetLastCheckpoint = function resetLastCheckpoint(height) {
var lastHeight = Object.keys(network.checkpoints).sort().indexOf(height) - 1;
if (lastHeight < 0)
i = 0;
this.resetHeight(lastHeight);
};
Chain.prototype.resetHeight = function resetHeight(height) {
var index = this.index.heights.indexOf(height);
if (index < 0)
throw new Error('Cannot reset to height of ' + height);
this.block.list = [];
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.bloom.reset();
this.index.lastTs = this.index.ts[this.index.ts.length - 1];
};
Chain.prototype._killFork = function _killFork(probe) { Chain.prototype._killFork = function _killFork(probe) {
var delta = 2 * 3600; var delta = 2 * 3600;
var upper = probe.ts + delta; var upper = probe.ts + delta;
@ -224,30 +259,37 @@ Chain.prototype.add = function add(block) {
} }
var res = false; var res = false;
var err = null;
var initial = block; var initial = block;
do { do {
// No need to revalidate orphans // No need to revalidate orphans
if (!res && !block.verify()) if (!res && !block.verify()) {
err = new Error('Block verification failed.');
break; break;
}
var hash = block.hash('hex'); var hash = block.hash('hex');
var prev = block.prevBlock; var prev = block.prevBlock;
// If the block is already known to be an orphan // If the block is already known to be an orphan
if (this.orphan.map[prev]) if (this.orphan.map[prev]) {
err = new Error('Block is a known orphan.');
break; break;
}
var prevProbe = this._probeIndex(prev, block.ts); var prevProbe = this._probeIndex(prev, block.ts);
// Remove forked nodes from storage, if shorter chain is detected // Remove forked nodes from storage, if shorter chain is detected
if (this._killFork(prevProbe)) if (this._killFork(prevProbe)) {
err = new Error('Fork found.');
break; break;
}
// If previous block wasn't ever seen - add current to orphans // If previous block wasn't ever seen - add current to orphans
if (!this._probeIndex(hash, block.ts) && !prevProbe) { if (!this._probeIndex(hash, block.ts) && !prevProbe) {
this.orphan.count++; this.orphan.count++;
this.orphan.map[prev] = block; this.orphan.map[prev] = block;
this.orphan.bmap[block.hash('hex')] = block; this.orphan.bmap[hash] = block;
var range = this._getRange(hash, block.ts, true); var range = this._getRange(hash, block.ts, true);
var hashes = this.index.hashes.slice(range.start, range.end + 1); var hashes = this.index.hashes.slice(range.start, range.end + 1);
@ -272,15 +314,15 @@ Chain.prototype.add = function add(block) {
// We have orphan child for this block - add it to chain // We have orphan child for this block - add it to chain
block = this.orphan.map[hash]; block = this.orphan.map[hash];
delete this.orphan.map[hash];
delete this.orphan.bmap[block.hash('hex')]; delete this.orphan.bmap[block.hash('hex')];
delete this.orphan.map[hash];
this.orphan.count--; this.orphan.count--;
} while (true); } while (true);
// Compress old blocks // Compress old blocks
this._compress(); this._compress();
return res; return err;
}; };
Chain.prototype._compress = function compress() { Chain.prototype._compress = function compress() {
@ -331,6 +373,13 @@ Chain.prototype.has = function has(hash, noProbe, cb) {
return cb(!!this.orphan.map[hash]); return cb(!!this.orphan.map[hash]);
}; };
Chain.prototype.hasBlock = function hasBlock(hash, noProbe, strict) {
if (this.loading)
return false;
return this.index.bloom.test(hash, 'hex');
};
Chain.prototype.get = function get(hash, force, cb) { Chain.prototype.get = function get(hash, force, cb) {
if (typeof force === 'function') { if (typeof force === 'function') {
cb = force; cb = force;

View File

@ -96,7 +96,7 @@ Peer.prototype._init = function init() {
'Sent version (%s): height=%s', 'Sent version (%s): height=%s',
ip, this.pool.chain.getStartHeight()); ip, this.pool.chain.getStartHeight());
self.pool.emit('debug', 'version (%s): sending locator hashes', ip); self.pool.emit('debug', 'version (%s): sending locator hashes', ip);
self.loadHeaders(self.chain.locatorHashes(), 0); self.loadBlocks(self.chain.locatorHashes(), 0);
}); });
} }
@ -436,14 +436,37 @@ Peer.prototype._handleInv = function handleInv(items) {
}, this).map(function(item) { }, this).map(function(item) {
return item.hash; return item.hash;
}); });
if (blocks.length === 1)
this.bestBlock = utils.toHex(blocks[0]);
this.emit('blocks', blocks); this.emit('blocks', blocks);
if (this.pool.options.fullNode) {
for (var i = 0; i < blocks.length; i++) {
var hash = utils.toHex(blocks[i]);
if (this.chain.orphan.bmap[hash]) {
this.loadBlocks(this.chain.locatorHashes(), this.chain.getOrphanRoot(hash));
continue;
}
if (!this.chain.hasBlock(hash)) {
this.getData([{ type: 'block', hash: hash }]);
continue;
}
if (i === blocks.length - 1) {
this.loadBlocks(this.chain.locatorHashes(), 0);
continue;
}
}
}
if (txs.length === 0) if (txs.length === 0)
return; return;
this.emit('txs', txs.map(function(tx) { this.emit('txs', txs.map(function(tx) {
return tx.hash; return tx.hash;
})); }));
this.getData(txs); this.getData(txs);
}; };
@ -457,24 +480,9 @@ Peer.prototype._handleHeaders = function handleHeaders(headers) {
return header; return header;
}); });
if (this.pool.options.fullNode) {
for (var i = 0; i < headers.length; i++) {
var header = headers[i];
var hash = header.hash;
// if (this.chain.orphan.bmap[hash]) {
if (this.chain.orphan.map[header.prevBlock]) {
this.loadHeaders(this.chain.locatorHashes(), this.chain.getOrphanRoot(hash));
continue;
}
if (!this.chain.index.bloom.test(hash, 'hex') || i === headers.length - 1)
this.getData([{ type: 'block', hash: hash }]);
}
}
this.emit('headers', headers); this.emit('headers', headers);
}; };
Peer.prototype.loadHeaders = function loadHeaders(hashes, stop) { Peer.prototype.loadHeaders = function loadHeaders(hashes, stop) {
this._write(this.framer.getHeaders(hashes, stop)); this._write(this.framer.getHeaders(hashes, stop));
}; };

View File

@ -5,6 +5,7 @@ var EventEmitter = require('events').EventEmitter;
var bcoin = require('../bcoin'); var bcoin = require('../bcoin');
var utils = bcoin.utils; var utils = bcoin.utils;
var assert = utils.assert; var assert = utils.assert;
var network = bcoin.protocol.network;
function Pool(options) { function Pool(options) {
var self = this; var self = this;
@ -89,8 +90,8 @@ function Pool(options) {
// Added and watched wallets // Added and watched wallets
this.wallets = []; this.wallets = [];
this.createConnection = options.createConnection; this.createSocket = options.createConnection || options.createSocket;
assert(this.createConnection); assert(this.createSocket);
this.chain.on('debug', function() { this.chain.on('debug', function() {
var args = Array.prototype.slice.call(arguments); var args = Array.prototype.slice.call(arguments);
@ -130,7 +131,7 @@ Pool.prototype._addLoader = function _addLoader() {
if (this.peers.load !== null) if (this.peers.load !== null)
return; return;
var peer = new bcoin.peer(this, this.createConnection, { var peer = new bcoin.peer(this, this.createSocket, {
backoff: 750 * Math.random(), backoff: 750 * Math.random(),
startHeight: this.options.startHeight, startHeight: this.options.startHeight,
relay: this.options.relay relay: this.options.relay
@ -275,7 +276,7 @@ Pool.prototype._addPeer = function _addPeer(backoff) {
if (this.peers.block.length + this.peers.pending.length >= this.size) if (this.peers.block.length + this.peers.pending.length >= this.size)
return; return;
var peer = new bcoin.peer(this, this.createConnection, { var peer = new bcoin.peer(this, this.createSocket, {
backoff: backoff, backoff: backoff,
startHeight: this.options.startHeight, startHeight: this.options.startHeight,
relay: this.options.relay relay: this.options.relay
@ -335,21 +336,30 @@ Pool.prototype._addPeer = function _addPeer(backoff) {
peer.on('block', function(block) { peer.on('block', function(block) {
backoff = 0; backoff = 0;
var height = self.chain.index.hashes.length; var len = self.chain.index.hashes.length;
var hash = block.hash('hex'); var hash = block.hash('hex');
if (!self.chain.index.bloom.test(block.prevBlock, 'hex')) { if (!self.chain.hasBlock(block.prevBlock))
peer.loadHeaders(self.chain.locatorHashes(), self.chain.getOrphanRoot(block)); peer.loadBlocks(self.chain.locatorHashes(), self.chain.getOrphanRoot(block));
}
self._response(block); self._response(block);
self.chain.add(block); var err = self.chain.add(block);
if (err)
self.emit('chain-error', err, peer);
// if (!self.chain.index.bloom.test(block.prevBlock, 'hex')) { self.emit('_block', block, peer);
// peer.loadHeaders(self.chain.locatorHashes(), self.chain.getOrphanRoot(block));
// } else if (self.chain.index.hashes.length === height) { if (self.chain.index.hashes.length === len)
// return; return;
// }
var height = self.chain.index.heights.length[self.chain.index.heights.length - 1];
var checkpoint = network.checkpoints[height];
if (checkpoint) {
self.emit('checkpoint', block, height, checkpoint, peer);
// if (checkpoint !== hash)
// self.chain.resetLastCheckpoint(height);
}
self.emit('chain-progress', self.chain.fillPercent(), peer); self.emit('chain-progress', self.chain.fillPercent(), peer);
self.emit('block', block, peer); self.emit('block', block, peer);
@ -379,6 +389,8 @@ Pool.prototype._addPeer = function _addPeer(backoff) {
}); });
peer.on('blocks', function(blocks) { peer.on('blocks', function(blocks) {
if (blocks.length === 1)
self.bestBlock = peer.bestBlock;
self.emit('blocks', blocks, peer); self.emit('blocks', blocks, peer);
}); });
@ -410,11 +422,7 @@ Pool.prototype._removePeer = function _removePeer(peer) {
Pool.prototype.watch = function watch(id) { Pool.prototype.watch = function watch(id) {
if (id instanceof bcoin.wallet) { if (id instanceof bcoin.wallet) {
// this.watch(id.getAddress()); this.watchWallet(id);
this.watch(id.getFullHash());
this.watch(id.getFullPublicKey());
this.watch(id.getOwnHash());
this.watch(id.getOwnPublicKey());
return; return;
} }
@ -463,10 +471,7 @@ Pool.prototype.unwatch = function unwatch(id) {
Pool.prototype.addWallet = function addWallet(w, defaultTs) { Pool.prototype.addWallet = function addWallet(w, defaultTs) {
if (this.wallets.indexOf(w) !== -1) if (this.wallets.indexOf(w) !== -1)
return false; return false;
this.watch(w.getFullHash()); this.watchWallet(w);
this.watch(w.getFullPublicKey());
this.watch(w.getOwnHash());
this.watch(w.getOwnPublicKey());
var self = this; var self = this;
var e = new EventEmitter(); var e = new EventEmitter();
@ -490,18 +495,41 @@ Pool.prototype.addWallet = function addWallet(w, defaultTs) {
} }
return e; return e;
} };
Pool.prototype.removeWallet = function removeWallet(w) { Pool.prototype.removeWallet = function removeWallet(w) {
var i = this.wallets.indexOf(w); var i = this.wallets.indexOf(w);
if (i == -1) if (i == -1)
return; return;
this.wallets.splice(i, 1); this.wallets.splice(i, 1);
this.unwatch(w.getFullHash()); this.unwatchWallet(w);
this.unwatch(w.getFullPublicKey()); };
Pool.prototype.watchWallet = function watchWallet(w) {
if (w.type === 'script') {
// For the redeem script hash in outputs:
this.watch(w.getFullHash());
// For the redeem script in inputs:
this.watch(w.getFullPublicKey());
}
// For the pubkey hash in outputs:
this.watch(w.getOwnHash());
// For the pubkey in inputs:
this.watch(w.getOwnPublicKey());
};
Pool.prototype.unwatchWallet = function unwatchWallet(w) {
if (w.type === 'script') {
// For the redeem script hash in p2sh outputs:
this.unwatch(w.getFullHash());
// For the redeem script in p2sh inputs:
this.unwatch(w.getFullPublicKey());
}
// For the pubkey hash in p2pk/multisig outputs:
this.unwatch(w.getOwnHash()); this.unwatch(w.getOwnHash());
// For the pubkey in p2pkh inputs:
this.unwatch(w.getOwnPublicKey()); this.unwatch(w.getOwnPublicKey());
} };
Pool.prototype.search = function search(id, range, e) { Pool.prototype.search = function search(id, range, e) {
e = e || new EventEmitter(); e = e || new EventEmitter();

View File

@ -155,3 +155,8 @@ exports.oneHash = utils.toArray(
'0000000000000000000000000000000000000000000000000000000000000001', '0000000000000000000000000000000000000000000000000000000000000001',
'hex' 'hex'
); );
exports.zeroHash = utils.toArray(
'0000000000000000000000000000000000000000000000000000000000000000',
'hex'
);

View File

@ -62,6 +62,11 @@ main.checkpoints = [
{ height: 295000, hash: '00000000000000004d9b4ef50f0f9d686fd69db2e03af35a100370c64632a983' } { height: 295000, hash: '00000000000000004d9b4ef50f0f9d686fd69db2e03af35a100370c64632a983' }
]; ];
main.checkpoints = main.checkpoints.reduce(function(out, block) {
out[block.height] = utils.revHex(block.hash);
return block.height;
}, {});
main.checkpoints.tsLastCheckpoint = 1397080064; main.checkpoints.tsLastCheckpoint = 1397080064;
main.checkpoints.txsLastCheckpoint = 36544669; main.checkpoints.txsLastCheckpoint = 36544669;
main.checkpoints.txsPerDay = 60000.0; main.checkpoints.txsPerDay = 60000.0;
@ -128,6 +133,11 @@ testnet.checkpoints = [
{ height: 546, hash: '000000002a936ca763904c3c35fce2f3556c559c0214345d31b1bcebf76acb70' } { height: 546, hash: '000000002a936ca763904c3c35fce2f3556c559c0214345d31b1bcebf76acb70' }
]; ];
testnet.checkpoints = testnet.checkpoints.reduce(function(out, block) {
out[block.height] = utils.revHex(block.hash);
return block.height;
}, {});
testnet.checkpoints.tsLastCheckpoint = 1338180505; testnet.checkpoints.tsLastCheckpoint = 1338180505;
testnet.checkpoints.txsLastCheckpoint = 16341; testnet.checkpoints.txsLastCheckpoint = 16341;
testnet.checkpoints.txsPerDay = 300; testnet.checkpoints.txsPerDay = 300;

View File

@ -16,6 +16,10 @@ script.decode = function decode(s) {
if (b >= 0x01 && b <= 0x4b) { if (b >= 0x01 && b <= 0x4b) {
opcodes.push(s.slice(i, i + b)); opcodes.push(s.slice(i, i + b));
i += b; i += b;
utils.hidden(opcodes[opcodes.length - 1], 'pushdata', {
opcode: null,
len: b
});
continue; continue;
} }
@ -33,29 +37,51 @@ script.decode = function decode(s) {
var opcode = constants.opcodesByVal[b]; var opcode = constants.opcodesByVal[b];
if (opcode === 'pushdata1') { if (opcode === 'pushdata1') {
var len = s[i++]; // NOTE: pushdata1 could be substituted with 0x01-0x4b
// later if less than or equal to 0x4b.
// bad when passed back into encode().
var len = s[i];
i += 1;
opcodes.push(s.slice(i, i + len)); opcodes.push(s.slice(i, i + len));
i += 2 + len; i += len;
utils.hidden(opcodes[opcodes.length - 1], 'pushdata', {
opcode: opcode,
len: len
});
} else if (opcode === 'pushdata2') { } else if (opcode === 'pushdata2') {
// NOTE: len could theoretically be less than 0xffff
// here. bad when passed back into encode().
var len = utils.readU16(s, i); var len = utils.readU16(s, i);
i += 2; i += 2;
opcodes.push(s.slice(i, i + len)); opcodes.push(s.slice(i, i + len));
i += len; i += len;
utils.hidden(opcodes[opcodes.length - 1], 'pushdata', {
opcode: opcode,
len: len
});
} else if (opcode === 'pushdata4') { } else if (opcode === 'pushdata4') {
// NOTE: len could theoretically be less than 0xffffffff
// here. bad when passed back into encode().
var len = utils.readU32(s, i); var len = utils.readU32(s, i);
i += 4; i += 4;
opcodes.push(s.slice(i, i + len)); opcodes.push(s.slice(i, i + len));
i += len; i += len;
utils.hidden(opcodes[opcodes.length - 1], 'pushdata', {
opcode: opcode,
len: len
});
} else { } else {
opcodes.push(opcode || b); opcodes.push(opcode || b);
} }
} }
return opcodes; return opcodes;
}; };
script.encode = function encode(s) { script.encode = function encode(s) {
if (!s) if (!s)
return []; return [];
var opcodes = constants.opcodes; var opcodes = constants.opcodes;
var res = []; var res = [];
for (var i = 0; i < s.length; i++) { for (var i = 0; i < s.length; i++) {
@ -63,6 +89,24 @@ script.encode = function encode(s) {
// Push value to stack // Push value to stack
if (Array.isArray(instr)) { if (Array.isArray(instr)) {
// Check for nonstandard pushdatas that
// may have been decoded from before.
if (instr.pushdata) {
if (instr.pushdata.opcode === null) {
res = res.concat(instr.pushdata.len, instr);
} else if (instr.pushdata.opcode === 'pushdata1') {
res = res.concat(opcodes.pushdata1, instr.pushdata.len, instr);
} else if (instr.pushdata.opcode === 'pushdata2') {
res.push(opcodes.pushdata2);
utils.writeU16(res, instr.pushdata.len, res.length);
res = res.concat(instr);
} else if (instr.pushdata.opcode === 'pushdata4') {
res.push(opcodes.pushdata4);
utils.writeU32(res, instr.pushdata.len, res.length);
res = res.concat(instr);
}
continue;
}
if (instr.length === 0) { if (instr.length === 0) {
// OP_FALSE // OP_FALSE
res.push(0); res.push(0);

View File

@ -447,7 +447,7 @@ RequestCache.prototype.fullfill = function fullfill(id, err, data) {
utils.asyncify = function asyncify(fn) { utils.asyncify = function asyncify(fn) {
return function _asynicifedFn(err, data1, data2) { return function _asynicifedFn(err, data1, data2) {
if (!fn) if (!fn)
return; return err || data1;
utils.nextTick(function() { utils.nextTick(function() {
fn(err, data1, data2); fn(err, data1, data2);
}); });
@ -576,3 +576,13 @@ utils.merge = function(target) {
}); });
return target; return target;
}; };
utils.hidden = function(obj, prop, value) {
Object.defineProperty(obj, prop, {
value: value,
enumerable: false,
configurable: true,
writable: true
});
return obj;
};