diff --git a/lib/bcoin/block.js b/lib/bcoin/block.js index 4ef5d30a..272bf4af 100644 --- a/lib/bcoin/block.js +++ b/lib/bcoin/block.js @@ -42,14 +42,22 @@ function Block(data, subtype) { this.network = data.network || false; this.relayedBy = data.relayedBy || '0.0.0.0'; + this.valid = null; + this._hash = null; + // List of matched TXs this.tx = []; - this.invalid = false; - if (this.subtype === 'merkleblock') { - // Verify partial merkle tree and fill `ts` array - this.invalid = !this._verifyMerkle(); - } else if (this.subtype === 'block') { + if (!this.subtype) { + if (this.hashes.length) + this.subtype = 'merkleblock'; + else if (this.txs.length) + this.subtype = 'block'; + else + this.subtype = 'header'; + } + + if (this.subtype === 'block') { this.txs = this.txs.map(function(tx) { tx.network = self.network; tx.relayedBy = self.relayedBy; @@ -67,11 +75,9 @@ function Block(data, subtype) { // this.height = height; // } // } - - this.invalid = !this._checkBlock(); } - this._hash = null; + this.verify(); } Block.prototype.hash = function hash(enc) { @@ -97,7 +103,14 @@ Block.prototype.abbr = function abbr() { }; Block.prototype.verify = function verify() { - return !this.invalid && utils.testTarget(this.bits, this.hash()); + if (this.valid == null) + this.valid = this._verify(); + return this.valid; +}; + +Block.verify = function verify(data, subtype) { + var block = new Block(data, subtype); + return block.verify(); }; Block.prototype.render = function render() { @@ -112,7 +125,7 @@ Block.prototype.hasTX = function hasTX(hash) { return this.tx.indexOf(hash) !== -1; }; -Block.prototype._verifyMerkle = function verifyMerkle() { +Block.prototype._verifyPartial = function _verifyPartial() { var height = 0; var tx = []; var i = 0; @@ -121,7 +134,7 @@ Block.prototype._verifyMerkle = function verifyMerkle() { var flags = this.flags; var i, root; - if (this.subtype === 'block') + if (this.subtype !== 'merkleblock') return; // Count leaves @@ -203,7 +216,7 @@ Block.prototype.getMerkleRoot = function getMerkleRoot() { // This mimics the behavior of CheckBlockHeader() // and CheckBlock() in bitcoin/src/main.cpp. -Block.prototype._checkBlock = function checkBlock() { +Block.prototype._verify = function _verify() { var i, unique, hash, merkleRoot; // Check proof of work matches claimed amount @@ -214,6 +227,14 @@ Block.prototype._checkBlock = function checkBlock() { if (this.ts > (Date.now() / 1000) + 2 * 60 * 60) return false; + if (this.subtype === 'merkleblock') { + if (!this._verifyPartial()) + return false; + } + + if (this.subtype !== 'block') + return true; + // Size can't be bigger than MAX_BLOCK_SIZE if (this.txs.length > constants.block.maxSize || this.size() > constants.block.maxSize) { @@ -389,9 +410,10 @@ Block.fromJSON = function fromJSON(json) { parser = new bcoin.protocol.parser(); - data = json.subtype === 'merkleblock' - ? parser.parseMerkleBlock(raw) - : parser.parseBlock(raw); + if (json.subtype === 'merkleblock') + data = parser.parseMerkleBlock(raw); + else if (json.subtype === 'block' || json.subtype === 'header') + data = parser.parseBlock(raw); data.network = json.network; data.relayedBy = json.relayedBy; diff --git a/lib/bcoin/chain.js b/lib/bcoin/chain.js index 61dc77f7..52bbc9f1 100644 --- a/lib/bcoin/chain.js +++ b/lib/bcoin/chain.js @@ -48,6 +48,7 @@ function Chain(options) { hashes: [], ts: [], heights: [], + lookup: {}, lastTs: 0 }; @@ -56,7 +57,7 @@ function Chain(options) { this.fromJSON(preload); // Last TS after preload, needed for fill percent - this.index.lastTs = this.index.ts[0]; + this.index.lastTs = this.index.ts[this.index.ts.length - 1]; bcoin.chain.global = this; @@ -177,6 +178,8 @@ Chain.prototype._addIndex = function _addIndex(hash, ts, height) { this.index.hashes.splice(pos, 0, hash); this.index.heights.splice(pos, 0, height); this.index.bloom.add(hash, 'hex'); + // this.index.lookup[hash] = pos; + // this.index.lookup[height] = pos; this._save(hash, { ts: ts, @@ -196,6 +199,7 @@ Chain.prototype.resetLastCheckpoint = function resetLastCheckpoint(height) { Chain.prototype.resetHeight = function resetHeight(height) { var self = this; var index = this.index.heights.indexOf(height); + var ahead = this.index.hashes.slice(index + 1); if (index < 0) throw new Error('Cannot reset to height of ' + height); @@ -212,21 +216,22 @@ Chain.prototype.resetHeight = function resetHeight(height) { this.index.hashes.forEach(function(hash) { self.index.bloom.add(hash, 'hex'); }); -}; -Chain.prototype._heightByTime = function _heightByTime(ts) { - for (var i = this.index.ts.length - 1; i >= 0; i--) { - if (ts >= this.index.ts[i]) - return this.index.heights[i]; - } - return -1; + this.index.lastTs = Math.min( + this.index.lastTs, + this.index.ts[this.index.ts.length - 1] + ); + + ahead.forEach(function(hash) { + self._delete(hash); + }); }; Chain.prototype.resetTime = function resetTime(ts) { - var height = this._heightByTime(ts); - if (height === -1) + var entry = this.byTime(ts); + if (!entry) return; - return this.resetHeight(height); + return this.resetHeight(entry.height); }; Chain.prototype._killFork = function _killFork(probe) { @@ -407,20 +412,6 @@ Chain.prototype.has = function has(hash, noIndex, cb) { return cb(false); }; -Chain.prototype.byHeight = function byHeight(height) { - var index = this.index.heights.indexOf(height); - - if (index === -1) - return null; - - return { - index: index, - hash: this.index.hashes[index], - ts: this.index.ts[index], - height: this.index.heights[index] - }; -}; - Chain.prototype.byHash = function byHash(hash) { if (Array.isArray(hash)) hash = utils.toHex(hash); @@ -440,6 +431,28 @@ Chain.prototype.byHash = function byHash(hash) { }; }; +Chain.prototype.byHeight = function byHeight(height) { + var index = this.index.heights.indexOf(height); + + if (index === -1) + return null; + + return { + index: index, + hash: this.index.hashes[index], + ts: this.index.ts[index], + height: this.index.heights[index] + }; +}; + +Chain.prototype.byTime = function byTime(ts) { + for (var i = this.index.ts.length - 1; i >= 0; i--) { + if (ts >= this.index.ts[i]) + return this.byHeight(this.index.heights[i]); + } + return null; +}; + Chain.prototype.hasBlock = function hasBlock(hash) { if (Array.isArray(hash)) hash = utils.toHex(hash); @@ -508,33 +521,6 @@ Chain.prototype.getTip = function() { }; }; -Chain.prototype.get = function get(hash, force, cb) { - var i, block; - - if (typeof force === 'function') { - cb = force; - force = false; - } - - // Cached block found - if (!force) { - if (this.block.bloom.test(hash, 'hex')) { - block = this.getCache(hash); - if (block) { - bcoin.utils.nextTick(cb.bind(null, block)); - return block; - } - // False positive: - // assert(false); - } - if (this.hasOrphan(hash)) - return cb(this.getOrphan(hash)); - } - - if (this.request.add(hash, cb)) - this.emit('missing', hash, null, null); -}; - Chain.prototype.isFull = function isFull() { // < 40m since last block if (this.request.count) @@ -546,11 +532,25 @@ Chain.prototype.isFull = function isFull() { }; Chain.prototype.fillPercent = function fillPercent() { - var total = (+new Date() / 1000 - 40 * 60) - this.index.lastTs; - var current = this.index.ts[this.index.ts.length - 1] - this.index.lastTs; + var total = (+new Date() / 1000 - 40 * 60) - this.index.ts[0]; + var current = this.index.ts[this.index.ts.length - 1] - this.index.ts[0]; return Math.max(0, Math.min(current / total, 1)); }; +Chain.prototype.hashRange = function hashRange(start, end) { + var hashes; + + start = this.chain.byTime(start); + end = this.chain.byTime(end); + + if (!start || !end) + return []; + + hashes = this.chain.index.hashes.slice(start.index, end.index + 1); + + return hashes; +}; + Chain.prototype.hashesInRange = function hashesInRange(start, end, cb) { var ts, hashes, heights, zip, i, count; @@ -591,31 +591,22 @@ Chain.prototype.hashesInRange = function hashesInRange(start, end, cb) { return cb(hashes, count); }; -Chain.prototype.getLast = function getLast(cb) { - if (this.loading) { - this.once('load', function() { - this.getLast(cb); - }); - return; - } - cb = utils.asyncify(cb); - return cb(this.index.hashes[this.index.hashes.length - 1]); -}; - Chain.prototype.getStartHeight = function getStartHeight() { return 0; }; Chain.prototype.locatorHashes = function locatorHashes(start) { - assert(start == null); + if (start != null) { + if (typeof start === 'number') + start = this.byHeight(start); + else + start = this.byHash(start) || start; - if (this.index.hashes.length === 1) - return [this.index.hashes[0]]; + if (start && start.index != null) + start = start.index; + } - return [ - this.index.hashes[this.index.hashes.length - 1], - this.index.hashes[0] - ]; + return bcoin.fullChain.prototype.locatorHashes.call(this, start); }; Chain.prototype.getOrphanRoot = function getOrphanRoot(hash) { diff --git a/lib/bcoin/fullchain.js b/lib/bcoin/fullchain.js index d7d43df9..f2d39cf7 100644 --- a/lib/bcoin/fullchain.js +++ b/lib/bcoin/fullchain.js @@ -64,7 +64,7 @@ function Chain(options) { }); // Last TS after preload, needed for fill percent - this.index.lastTs = this.index.entries[0].ts; + this.index.lastTs = this.index.entries[this.index.entries.length - 1].ts; bcoin.chain.global = this; @@ -145,7 +145,10 @@ Chain.prototype.resetLastCheckpoint = function resetLastCheckpoint(height) { Chain.prototype.resetHeight = function resetHeight(height) { var self = this; - var forked = this.index.entries.slice(height + 1); + var ahead = this.index.entries.slice(height + 1); + + if (height >= this.index.entries - 1) + throw new Error('Cannot reset to height of ' + height); this.orphan.map = {}; this.orphan.bmap = {}; @@ -157,26 +160,21 @@ Chain.prototype.resetHeight = function resetHeight(height) { }, {}); this.index.hashes.length = height + 1; - this.index.lastTs = this.index.entries[0].ts; + this.index.lastTs = Math.min( + this.index.lastTs, + this.index.entries[this.index.entries.length - 1].ts + ); - forked.forEach(function(entry) { - self._delete(entry); + ahead.forEach(function(entry) { + self._delete(entry.hash); }); }; -Chain.prototype._heightByTime = function _heightByTime(ts) { - for (var i = this.index.entries.length - 1; i >= 0; i--) { - if (ts >= this.index.entries[i].ts) - return this.index.entries[i].height; - } - return -1; -}; - Chain.prototype.resetTime = function resetTime(ts) { - var height = this._heightByTime(ts); - if (height === -1) + var entry = this.byTime(ts); + if (!entry) return; - return this.resetHeight(height); + return this.resetHeight(entry.height); }; Chain.prototype.add = function add(block) { @@ -301,6 +299,14 @@ Chain.prototype.byHash = function byHash(hash) { return this.byHeight(this.index.heights[hash]); }; +Chain.prototype.byTime = function byTime(ts) { + for (var i = this.index.entries.length - 1; i >= 0; i--) { + if (ts >= this.index.entries[i].ts) + return this.index.entries[i]; + } + return null; +}; + Chain.prototype.hasBlock = function hasBlock(hash) { return !!this.byHash(hash); }; @@ -336,10 +342,6 @@ Chain.prototype.getTip = function getTip() { return this.index.entries[this.index.entries.length - 1]; }; -Chain.prototype.get = function get(hash, force, cb) { - assert(false); -}; - Chain.prototype.isFull = function isFull() { var last = this.index.entries[this.index.entries.length - 1].ts; var delta = (+new Date() / 1000) - last; @@ -352,19 +354,22 @@ Chain.prototype.fillPercent = function fillPercent() { return Math.max(0, Math.min(current / total, 1)); }; -Chain.prototype.hashesInRange = function hashesInRange(start, end, cb) { - assert(false); +Chain.prototype.hashRange = function hashRange(start, end) { + var hashes; + + start = this.chain.byTime(start); + end = this.chain.byTime(end); + + if (!start || !end) + return []; + + hashes = this.chain.index.hashes.slice(start.height, end.height + 1); + + return hashes; }; -Chain.prototype.getLast = function getLast(cb) { - if (this.loading) { - this.once('load', function() { - this.getLast(cb); - }); - return; - } - cb = utils.asyncify(cb); - return cb(this.index.hashes[this.index.hashes.length - 1]); +Chain.prototype.hashesInRange = function hashesInRange(start, end, cb) { + assert(false); }; Chain.prototype.getStartHeight = function getStartHeight() { @@ -387,8 +392,10 @@ Chain.prototype.locatorHashes = function locatorHashes(start) { if (typeof start === 'string') { // Hash - if (this.index.heights[start]) + if (this.index.heights[start] != null) top = this.index.heights[start]; + else + hashes.push(start); } else if (typeof start === 'number') { // Height top = start; @@ -396,7 +403,8 @@ Chain.prototype.locatorHashes = function locatorHashes(start) { i = top; for (;;) { - hashes.push(chain[i]); + if (chain[i]) + hashes.push(chain[i]); i = i - step; if (i <= 0) { hashes.push(chain[0]); diff --git a/lib/bcoin/peer.js b/lib/bcoin/peer.js index 8e8aae2e..ef6c7878 100644 --- a/lib/bcoin/peer.js +++ b/lib/bcoin/peer.js @@ -502,7 +502,7 @@ Peer.prototype._handleInv = function handleInv(items) { }); if (blocks.length === 1) - this.bestBlock = utils.toHex(blocks[0]); + this.bestHash = utils.toHex(blocks[0]); this.emit('blocks', blocks); diff --git a/lib/bcoin/pool.js b/lib/bcoin/pool.js index 120bf678..33ead1d1 100644 --- a/lib/bcoin/pool.js +++ b/lib/bcoin/pool.js @@ -94,9 +94,9 @@ function Pool(options) { }; this.block = { - lastHash: null, bestHeight: 0, - bestHash: null + bestHash: null, + type: this.options.fullNode ? 'block' : 'filtered' }; this.request = { @@ -154,13 +154,11 @@ Pool.prototype._init = function _init() { for (i = 0; i < this.size; i++) this._addPeer(0); - this._load(); - this.chain.on('missing', function(hash, preload, parent) { if (!self.options.fullNode) { - self._request('filtered', hash, { force: true }); + self._request(self.block.type, hash, { force: true }); self._scheduleRequests(); - self._loadRange(preload); + // self._loadRange(preload); } }); @@ -226,7 +224,7 @@ Pool.prototype._addLoader = function _addLoader() { }); function load() { - self._load(); + // self._load(); } interval = setInterval(load, this.load.interval); @@ -236,7 +234,6 @@ Pool.prototype._addLoader = function _addLoader() { if (self.chain.isFull()) { clearTimeout(timer); self.emit('full'); - self.block.lastHash = null; return; } @@ -245,89 +242,68 @@ Pool.prototype._addLoader = function _addLoader() { timer = setTimeout(destroy, this.load.timeout); + peer.on('merkleblock', function(block) { + self._handleBlock(block, peer); + }); + peer.on('block', function(block) { self._handleBlock(block, peer); }); - // Split blocks and request them using multiple peers peer.on('blocks', function(hashes) { - var i, hash; + self._handleInv(hashes, peer); + }); - self.emit('blocks', hashes); + // Split blocks and request them using multiple peers + peer.on('headers', function(headers) { + var i, header, last, block; - if (hashes.length === 0) { - // Reset global load - self.block.lastHash = null; + if (headers.length === 0) return; - } - - if (!self.options.fullNode) { - // Request each block - for (i = 0; i < hashes.length; i++) { - self._request('filtered', hashes[i]); - } - - // Push our getblocks packet - self._scheduleRequests(); - - // The part of the response is in chain, no need to escalate requests - async.every(hashes, function(hash, cb) { - self.chain.has(utils.toHex(hash), function(res) { - cb(!res); - }); - }, function(allNew) { - if (!allNew) { - self.block.lastHash = null; - return; - } - - // Store last hash to continue global load - self.block.lastHash = hashes[hashes.length - 1]; - - clearTimeout(timer); - - // Reinstantiate timeout - if (self._load()) - timer = setTimeout(destroy, self.load.timeout); - }); - - return; - } - - for (i = 0; i < hashes.length; i++) { - hash = hashes[i]; - - // Resolve orphan chain - if (self.chain.hasOrphan(hash)) { - peer.loadBlocks( - self.chain.locatorHashes(), - self.chain.getOrphanRoot(hash) - ); - continue; - } - - // Restart the entire getblocks process - if (i === hashes.length - 1) - peer.loadBlocks(self.chain.locatorHashes(), 0); - - // Request block if we don't have it - if (!self.chain.hasBlock(hash) && !self.chain.hasOrphan(hash)) - self._request('block', hash); - } self.emit('debug', - 'Requesting %s block packets from %s with getdata', - self.request.active, - peer.address - ); + 'Recieved %s headers from %s', + headers.length, + peer.address); - // Push our getblocks packet + self.emit('headers', headers); + + for (i = 0; i < headers.length; i++) { + block = bcoin.block(headers[i], 'header'); + + if (last && block.prevBlock !== last.hash('hex')) + break; + + if (!block.verify()) + break; + + if (!self.chain.hasBlock(block) && !self.chain.hasOrphan(block)) + self._request(self.block.type, block.hash('hex')); + + // We could do headers-first: + // self._addIndex(block, peer); + + // Resolve orphan chain + // if (self.chain.hasOrphan(block)) { + // peer.loadHeaders( + // self.chain.locatorHashes(), + // self.chain.getOrphanRoot(block) + // ); + // continue; + // } + + last = block; + } + + // Restart the getheaders process + if (last && headers.length >= 1999) + peer.loadHeaders(self.chain.locatorHashes(last), 0); + // peer.loadHeaders([last.hash('hex')], 0); + + // Schedule our requests self._scheduleRequests(); - // Store last hash because we can - self.block.lastHash = hashes[hashes.length - 1]; - - // Reset interval to avoid calling getblocks unnecessarily + // Reset interval to avoid calling getheaders unnecessarily clearInterval(interval); interval = setInterval(load, self.load.interval); @@ -337,34 +313,44 @@ Pool.prototype._addLoader = function _addLoader() { }); }; -Pool.prototype._handleMerkle = function _handleMerkle(block, peer) { - if (this.options.fullNode) - return; +Pool.prototype._handleInv = function _handleInv(hashes, peer) { + var i, hash; - this._response(block); - this.chain.add(block); - this.emit('chain-progress', this.chain.fillPercent(), peer); - this.emit('block', block, peer); + for (i = 0; i < hashes.length; i++) { + hash = utils.toHex(hashes[i]); + if (!this.chain.hasBlock(hash) && !this.chain.hasOrphan(hash)) + this.peers.load.loadHeaders(this.chain.locatorHashes(), hash); + } }; Pool.prototype._handleBlock = function _handleBlock(block, peer) { var self = this; - var hash, size, orphan, err; - if (!this.options.fullNode) + var requested = this._response(block); + + // Someone is sending us blocks without us requesting them. + if (!requested) return; // Emulate bip37 - emit all the "watched" txs - if (this.listeners('watched').length > 0) { - utils.nextTick(function() { - block.txs.forEach(function(tx) { - if (self.isWatched(tx)) - self.emit('watched', tx, peer); - }); + if (this.options.fullNode + && this.listeners('watched').length > 0 + && block.verify()) { + block.txs.forEach(function(tx) { + if (self.isWatched(tx)) + self.emit('watched', tx, peer); }); } - this._response(block); + if (!this._addIndex(block, peer)) + return; + + this.emit('block', block, peer); +}; + +Pool.prototype._addIndex = function _addIndex(block, peer) { + var self = this; + var hash, size, orphan, err; hash = block.hash('hex'); size = this.chain.size(); @@ -374,26 +360,25 @@ Pool.prototype._handleBlock = function _handleBlock(block, peer) { if (err) this.emit('chain-error', err, peer); - this.emit('_block', block, peer); - if (this.chain.hasOrphan(block)) { // Resolve orphan chain - peer.loadBlocks( - this.chain.locatorHashes(), - this.chain.getOrphanRoot(block) - ); + // peer.loadHeaders( + // this.chain.locatorHashes(), + // this.chain.getOrphanRoot(block) + // ); // Emit our orphan if it is new if (!orphan) this.emit('orphan', block, peer); - return; + return false; } // Do not emit if nothing was added to the chain if (this.chain.size() === size) - return; + return false; this.emit('chain-progress', this.chain.fillPercent(), peer); - this.emit('block', block, peer); + + return true; }; Pool.prototype.isFull = function isFull() { @@ -442,31 +427,16 @@ Pool.prototype._load = function _load() { if (this.peers.load) { this.emit('debug', - 'Requesting inv packet from %s with getblocks', + 'Requesting headers packet from %s with getheaders', this.peers.load.address); } - if (!this.options.fullNode) { - next = function(hash) { - if (!self.peers.load) - self._addLoader(); - else - self.peers.load.loadBlocks([hash], 0); - }; - - // Load more blocks, starting from last hash - if (this.block.lastHash) - next(this.block.lastHash); - else - this.chain.getLast(next); - + if (!this.peers.load) { + this._addLoader(); return true; } - if (!this.peers.load) - this._addLoader(); - else - this.peers.load.loadBlocks(this.chain.locatorHashes(), 0); + this.peers.load.loadHeaders(this.chain.locatorHashes(), 0); return true; }; @@ -531,7 +501,7 @@ Pool.prototype._addPeer = function _addPeer(backoff) { peer.on('merkleblock', function(block) { // Reset backoff, peer seems to be responsive backoff = 0; - self._handleMerkle(block, peer); + self._handleBlock(block, peer); }); peer.on('block', function(block) { @@ -565,10 +535,8 @@ Pool.prototype._addPeer = function _addPeer(backoff) { self.emit('addr', addr, peer); }); - peer.on('blocks', function(blocks) { - if (blocks.length === 1) - self.block.bestHash = peer.bestBlock; - self.emit('blocks', blocks, peer); + peer.on('blocks', function(hashes) { + self._handleInv(hashes, peer); }); peer.on('txs', function(txs) { @@ -834,6 +802,7 @@ Pool.prototype.searchWallet = function(w) { Pool.prototype.search = function search(id, range, e) { var self = this; + var hashes, pending, listener, timeout, done, total; if (this.options.fullNode) return; @@ -862,72 +831,70 @@ Pool.prototype.search = function search(id, range, e) { // Last 5 days by default, this covers 1000 blocks that we have in the // chain by default if (!range.end) - range.end = +new Date() / 1000; + range.end = +new Date() / 1000 | 0; if (!range.start) - range.start = +new Date() / 1000 - 432000; - - this.chain.hashesInRange(range.start, range.end, function(hashes, count) { - var waiting = count; - - self.emit('debug', - 'Search for %s (%s) hashes between %s and %s', - hashes.length, - count, - new Date(range.start * 1000).toISOString(), - new Date(range.end * 1000).toISOString() - ); + range.start = (+new Date() / 1000 | 0) - 432000; + if (range.start < this.chain.lastTs) { if (id) - self.watch(id); + this.watch(id); - self._loadRange(hashes, true); + done = function(res) { + e.emit('end', res); + clearInterval(timeout); + self.removeListener('block', listener); + if (id) + self.unwatch(id); + }; - hashes = hashes.slice().reverse(); - hashes.forEach(function(hash, i) { - // Get the block that is in index - self.chain.request.add(hash, function(block) { - loadBlock(block, hashes[i + 1]); - }); + this.on('block', listener = function(block) { + if (block.ts >= range.end) + done(); }); - function loadBlock(block, stop) { - // Stop block reached - if (block.hash('hex') === stop) - return; + total = (range.end - range.start) / network.powTargetSpacing | 0; + if (total === 0) + total = 1; - // Get block's prev and request it and all of it's parents up to - // the next known block hash - self.chain.request.add(block.prevBlock, function(prev) { - done(); + timeout = setTimeout(done.bind(null, true), total * 10 * 1000); - // First hash loaded - if (!stop) - return; + this.chain.resetTime(range.start); - // Continue loading blocks - loadBlock(prev, stop); - }); + if (this.peers.load) + this.peers.load.destroy(); + + this._load(); + + return e; + } + + hashes = this.chain.hashRange(range.start, range.end); + pending = hashes.length; + + if (id) + this.watch(id); + + done = function() { + pending--; + assert(pending >= 0); + e.emit('progress', count - pending, count); + if (pending === 0) { + if (id) + self.unwatch(id); + e.emit('end'); } + }; - function done() { - waiting--; - assert(waiting >= 0); - e.emit('progress', count - waiting, count); - if (waiting === 0) { - if (id) - self.unwatch(id); - e.emit('end'); - } - } - - // Empty search - if (hashes.length === 0) { - bcoin.utils.nextTick(function() { - e.emit('end', true); - }); - } + hashes.forEach(function(hash) { + self._request('filtered', hash, { force: true }, done); }); + if (hashes.length === 0) { + bcoin.utils.nextTick(function() { + e.emit('end', true); + }); + } + return e; }; @@ -958,8 +925,8 @@ Pool.prototype._request = function _request(type, hash, options, cb) { } // Block should be not in chain, or be requested - if (!options.force && (type === 'block' || type === 'filtered')) - return this.chain.has(hash, true, next); + // if (!options.force && (type === 'block' || type === 'filtered')) + // return this.chain.has(hash, true, next); return next(false); }; @@ -1021,38 +988,36 @@ Pool.prototype._doRequests = function _doRequests() { if (above && below && this.load.hiReached) this._load(); - if (!this.options.fullNode) { - mapReq = function(item) { - return item.start(this.peers.block[i]); - }; + if (this.options.fullNode) { + req = items.map(function(item) { + return item.start(this.peers.load); + }, this); - // Split list between peers - 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); - } + this.peers.load.getData(req); return; } - req = items.map(function(item) { - return item.start(this.peers.load); - }, this); + mapReq = function(item) { + return item.start(this.peers.block[i]); + }; - this.peers.load.getData(req); + // Split list between peers + 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.getBlock = function getBlock(hash, cb) { - var type = this.options.fullNode ? 'block' : 'filtered'; - - if (this.chain.request.add(hash, cb)) { - this._request(type, hash, { force: true }); - this._scheduleRequests(); - } + this._request('block', hash, { force: true }, function(block) { + cb(null, block); + }); + this._scheduleRequests(); }; Pool.prototype.sendBlock = function sendBlock(block) { @@ -1188,15 +1153,26 @@ Pool.prototype.toJSON = function toJSON() { return { v: 1, type: 'pool', - chain: this.chain.toJSON() + chain: this.chain.toJSON(), + requests: this.request.queue.map(function(item) { + return { + type: item.type, + hash: item.hash + }; + }) }; }; Pool.prototype.fromJSON = function fromJSON(json) { assert.equal(json.v, 1); assert.equal(json.type, 'pool'); + this.chain.fromJSON(json.chain); + json.requests.forEach(function(item) { + this._request(item.type, item.hash); + }, this); + return this; };