From feda74523f5c081c68f70eeeeffb36981ebe4898 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 9 Jun 2014 16:23:49 -0500 Subject: [PATCH] peer/pool: add origin satoshi protocol. --- lib/bcoin/chain.js | 51 ++++++++++++++++++++++ lib/bcoin/peer.js | 74 +++++++++++++++++++++++++------- lib/bcoin/pool.js | 104 +++++++++++++++++++++++++++++++++++++++------ 3 files changed, 201 insertions(+), 28 deletions(-) diff --git a/lib/bcoin/chain.js b/lib/bcoin/chain.js index 8c7d9abc..26cd3776 100644 --- a/lib/bcoin/chain.js +++ b/lib/bcoin/chain.js @@ -37,6 +37,18 @@ function Chain(options) { }; this.request = new utils.RequestCache(); + // Start from the genesis block + // if we're using the original protocol. + if (!this.options.relay) { + preload = { + v: preload.v, + type: preload.type, + hashes: preload.hashes.slice(0, 1), + ts: preload.ts.slice(0, 1), + heights: preload.heights.slice(0, 1) + }; + } + this.fromJSON(preload); // Last TS after preload, needed for fill percent @@ -198,6 +210,31 @@ Chain.prototype.add = function add(block) { return; } + if (!this.options.relay) { + var hash = block.hash('hex'); + var prev = block.prevBlock; + var prevProbe = this._probeIndex(prev, block.ts); + if (prevProbe) { + this._addIndex(hash, block.ts, prevProbe.height + 1); + if (this.orphan.map[hash]) { + delete this.orphan.map[hash]; + this.orphan.count--; + } + this.block.list.push(block); + this._bloomBlock(block); + this.request.fullfill(hash, block); + this._compress(); + return true; + } else { + if (!this.orphan.map[prev]) { + this.orphan.map[prev] = block; + this.orphan.count++; + } + this._compress(); + return false; + } + } + var res = false; var initial = block; do { @@ -388,6 +425,20 @@ Chain.prototype.getLast = function getLast(cb) { return cb(this.index.hashes[this.index.hashes.length - 1]); }; +Chain.prototype.getStartHeight = function getLast(cb) { + if (this.options.relay) { + if (this.options.startHeight != null) { + return this.options.startHeight; + } + return 0; + } + for (var i = 0; i < this.index.heights.length; i++) { + if (this.index.heights[i + 1] !== this.index.heights[i] + 1) { + return this.index.heights[i]; + } + } +}; + Chain.prototype.toJSON = function toJSON() { var keep = 1000; diff --git a/lib/bcoin/peer.js b/lib/bcoin/peer.js index b5b80f5b..1af4efeb 100644 --- a/lib/bcoin/peer.js +++ b/lib/bcoin/peer.js @@ -89,6 +89,14 @@ Peer.prototype._init = function init() { self._error(err); }); + if (!this.pool.options.relay) { + this.once('version', function() { + var ip = self.socket && self.socket.remoteAddress || '0.0.0.0'; + self.pool.emit('debug', 'version (%s): loading locator hashes for getblocks', ip); + self.loadBlocks(self.pool.locatorHashes(), 0); + }); + } + this._ping.timer = setInterval(function() { self._write(self.framer.ping([ 0xde, 0xad, 0xbe, 0xef, @@ -98,9 +106,7 @@ Peer.prototype._init = function init() { // Send hello this._write(this.framer.version({ - height: this.options.startHeight != null - ? this.options.startHeight - : 0, + height: this.pool.chain.getStartHeight(), relay: this.options.relay })); @@ -167,18 +173,8 @@ Peer.prototype.broadcast = function broadcast(items) { }; Peer.prototype.updateWatch = function updateWatch() { - if (!this.pool.options.relay) { - if (this.ack) { - var self = this; - if (this.pool.block.lastHash) - this.loadBlocks([ self.pool.block.lastHash ], 0); - else - this.pool.chain.getLast(function(hash) { - self.loadBlocks([ hash ], 0); - }); - } + if (!this.pool.options.relay) return; - } if (this.ack) this._write(this.framer.filterLoad(this.bloom, 'none')); @@ -438,11 +434,57 @@ Peer.prototype._handleInv = function handleInv(items) { this.emit('blocks', blocks); if (!this.pool.options.relay) { - if (txs.length) + var self = this; + + if (txs.length) { this.emit('txs', txs.map(function(tx) { return tx.hash; })); - this.getData(items); + // this.getData(txs); + } + + var hashes = blocks.map(utils.toHex); + + var orphans = Object.keys(this.chain.orphan.map).reduce(function(out, prev) { + var orphan = self.chain.orphan.map[prev]; + out[orphan.hash('hex')] = true; + return out; + }, {}); + + hashes = hashes.filter(function(hash) { + return !orphans[hash]; + }); + + // hashes = hashes.filter(function(hash) { + // return !self.chain.index.bloom.test(hash, 'hex') + // && !self.chain.block.bloom.test(hash, 'hex'); + // }); + + if (blocks.length === 1) { + var ip = this.socket && this.socket.remoteAddress || '0.0.0.0'; + this.pool.emit('debug', 'inv (%s): %s', ip, utils.revHex(utils.toHex(blocks[0]))); + } + + if (!hashes.length) { + var ip = this.socket && this.socket.remoteAddress || '0.0.0.0'; + //if (blocks.length === 1 && this._latestBlock) { + if (blocks.length === 1) { + // Already have the latest orphan, another getblocks: + this.pool.emit('debug', 'inv (%s): loading locator hashes for getblocks', ip); + this.loadBlocks(this.pool.locatorHashes(), 0); + } else { + this.pool.emit('debug', 'inv (%s): no hashes to getdata', ip); + } + return; + } + + this.getData(hashes.map(function(hash) { + return { + type: 'block', + hash: hash + }; + })); + return; } diff --git a/lib/bcoin/pool.js b/lib/bcoin/pool.js index 52525c78..c6f8422a 100644 --- a/lib/bcoin/pool.js +++ b/lib/bcoin/pool.js @@ -40,7 +40,9 @@ function Pool(options) { storage: this.storage, // Since regular blocks contain transactions and full merkle // trees, it's risky to cache 2000 blocks. Let's do 100. - cacheLimit: !this.options.relay ? 100 : null + cacheLimit: !this.options.relay ? 100 : null, + relay: this.options.relay, + startHeight: this.options.startHeight }); this.watchMap = {}; this.bloom = new bcoin.bloom(8 * 1024, @@ -98,10 +100,41 @@ Pool.prototype._init = function _init() { var self = this; this.chain.on('missing', function(hash, preload, parent) { + if (!self.options.relay) return; self._request('block', hash, { force: true }); self._scheduleRequests(); self._loadRange(preload); }); + + if (!this.options.relay) { + // Non-standard stall recovery, + // this shouldn't be necessary. + this._lastLocator = null; + setInterval(function() { + if (!self._lastLocator) return; + if (Date.now() - 60 * 1000 > self._lastLocator) { + self.emit('debug', 'invoking stall recovery!'); + [].concat( + self.peers.pending, + self.peers.block, + self.peers.load + ).reduce(function(out, peer) { + if (peer && !~out.indexOf(peer)) { + out.push(peer); + } + return out; + }, []).forEach(function(peer) { + var ip = peer.socket && peer.socket.remoteAddress || '0.0.0.0'; + self.emit('debug', 'invoking stall recovery on peer (%s)!', ip); + peer.loadBlocks(self.locatorHashes(), 0); + }); + } else { + self.emit('debug', 'stall recovery diff: %d/%d', + self._lastLocator / 1000 | 0, + Date.now() / 1000 | 0); + } + }, 10 * 1000); + } }; Pool.prototype._addLoader = function _addLoader() { @@ -157,6 +190,8 @@ Pool.prototype._addLoader = function _addLoader() { // Split blocks and request them using multiple peers peer.on('blocks', function(hashes) { + if (!self.options.relay) return; + if (hashes.length === 0) { // Reset global load self.block.lastHash = null; @@ -198,6 +233,8 @@ Pool.prototype.isFull = function isFull() { }; Pool.prototype._loadRange = function _loadRange(hashes, force) { + if (!this.options.relay) return; + if (!hashes) return; @@ -220,6 +257,8 @@ Pool.prototype._loadRange = function _loadRange(hashes, force) { }; Pool.prototype._load = function _load() { + if (!this.options.relay) return; + if (this.request.queue.length >= this.load.hwm) { this.load.hiReached = true; return false; @@ -295,19 +334,34 @@ Pool.prototype._addPeer = function _addPeer(backoff) { self._scheduleRequests(); }); - peer.on('merkleblock', function(block) { - // Reset backoff, peer seems to be responsive - backoff = 0; + if (this.options.relay) { + peer.on('merkleblock', function(block) { + // Reset backoff, peer seems to be responsive + backoff = 0; - self._response(block); - self.chain.add(block); - self.emit('chain-progress', self.chain.fillPercent(), peer); - self.emit('block', block, peer); - }); - - if (!this.options.relay) { + self._response(block); + self.chain.add(block); + self.emit('chain-progress', self.chain.fillPercent(), peer); + self.emit('block', block, peer); + }); + } else { peer.on('block', function(block) { - peer.emit('merkleblock', block); + var index = self.chain.index; + backoff = 0; + self._response(block); + var ret = self.chain.add(block); + self.emit('chain-progress', self.chain.fillPercent(), peer); + self.emit('block', block, peer); + if (!ret) { + var ip = peer.socket && peer.socket.remoteAddress || '0.0.0.0'; + self.emit('debug', 'block (%s): %s', ip, utils.revHex(block.hash('hex'))); + self.emit('debug', 'block (%s): loading locator hashes for getblocks', ip); + peer._latestBlock = { hash: block.hash('hex'), ts: block.ts }; + if (!self._latestBlock || peer._latestBlock.ts > self._latestBlock.ts) { + self._latestBlock = peer._latestBlock; + } + peer.loadBlocks(self.locatorHashes(), 0); + } }); } @@ -350,6 +404,32 @@ Pool.prototype._addPeer = function _addPeer(backoff) { }); }; +Pool.prototype.locatorHashes = function() { + var self = this; + var hashes = this.chain.index.hashes; + var indicies = []; + var top = hashes.length - 1; + var step = 1, start = 0; + + for (var i = top; i > 0; i -= step, ++start) { + if (start >= 10) step *= 2; + indicies.push(hashes[i]); + } + + indicies.push(hashes[0]); + + indicies = indicies.reduce(function(out, hash) { + if (!~out.indexOf(hash)) { + out.push(hash); + } + return out; + }, []); + + this._lastLocator = Date.now(); + + return indicies; +}; + Pool.prototype._removePeer = function _removePeer(peer) { var i = this.peers.pending.indexOf(peer); if (i !== -1)