add fullnode chain. improve pool.

This commit is contained in:
Christopher Jeffrey 2015-12-29 18:42:38 -08:00
parent 65dd1727be
commit 146e50dc68
6 changed files with 848 additions and 279 deletions

View File

@ -21,6 +21,8 @@ bcoin.tx = require('./bcoin/tx');
bcoin.txPool = require('./bcoin/tx-pool');
bcoin.block = require('./bcoin/block');
bcoin.chain = require('./bcoin/chain');
bcoin.spvChain = require('./bcoin/chain');
bcoin.fullChain = require('./bcoin/fullchain');
bcoin.wallet = require('./bcoin/wallet');
bcoin.peer = require('./bcoin/peer');
bcoin.pool = require('./bcoin/pool');

View File

@ -267,7 +267,7 @@ Block.prototype.getNextBlock = function getNextBlock(chain) {
chain = chain || bcoin.chain.global;
if (!chain)
return utils.toHex(constants.protocol.zeroHash);
return utils.toHex(constants.zeroHash);
next = chain.getNextBlock(this.hash('hex'));

View File

@ -53,24 +53,12 @@ function Chain(options) {
this.request = new utils.RequestCache();
// Start from the genesis block
// if we're a full node.
if (this.options.fullNode) {
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
this.index.lastTs = this.index.ts[this.index.ts.length - 1];
Chain.global = this;
bcoin.chain.global = this;
this.loading = false;
this._init();
@ -338,7 +326,7 @@ Chain.prototype.add = function add(block) {
// At least one block was added
res = true;
this.block.list.push(block);
this._bloomBlock(block);
this.block.bloom.add(hash, 'hex');
// Fullfill request
this.request.fullfill(hash, block);
@ -371,11 +359,7 @@ Chain.prototype._compress = function compress() {
this.block.bloom.reset();
for (i = 0; i < this.block.list.length; i++)
this._bloomBlock(this.block.list[i]);
};
Chain.prototype._bloomBlock = function _bloomBlock(block) {
this.block.bloom.add(block.hash(), 'hex');
this.block.bloom.add(this.block.list[i].hash('hex'), 'hex');
};
Chain.prototype.has = function has(hash, noProbe, cb) {
@ -413,6 +397,45 @@ Chain.prototype.has = function has(hash, noProbe, cb) {
return cb(!!this.orphan.map[hash]);
};
Chain.prototype.byHeight = function byHeight(height) {
if (this.loading)
return null;
var index = this.index.heights.indexOf(height);
if (index === -1)
return -1;
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 (this.loading)
return null;
if (Array.isArray(hash))
hash = utils.toHex(hash);
else if (hash.hash)
hash = hash.hash('hex');
var index = this.index.hashes.indexOf(hash);
if (index === -1)
return -1;
return {
index: index,
hash: this.index.hashes[index],
ts: this.index.ts[index],
height: this.index.heights[index]
};
};
Chain.prototype.hasBlock = function hasBlock(hash) {
if (this.loading)
return false;
@ -533,97 +556,49 @@ Chain.prototype.getLast = function getLast(cb) {
};
Chain.prototype.getStartHeight = function getStartHeight() {
if (!this.options.fullNode) {
if (this.options.startHeight != null) {
return this.options.startHeight;
}
return 0;
}
return this.index.heights[this.index.heights.length - 1];
if (this.options.startHeight != null)
return this.options.startHeight;
return 0;
};
Chain.prototype.locatorHashes = function locatorHashes(start) {
var chain = this.index.hashes;
var hashes = [];
var top = chain.length - 1;
var step = 1;
var i;
assert(start == null);
if (typeof start === 'string') {
// Hash
for (i = top; i >= 0; i--) {
if (chain[i] === start) {
top = i;
break;
}
}
} else if (typeof start === 'number') {
// Height
start = this.index.heights.indexOf(start);
if (start !== -1)
top = start;
}
if (this.index.hashes.length === 1)
return [this.index.hashes[0]];
i = top;
for (;;) {
hashes.push(chain[i]);
i = i - step;
if (i <= 0) {
hashes.push(chain[0]);
break;
}
if (hashes.length >= 10)
step *= 2;
}
return hashes;
return [
this.index.hashes[this.index.hashes.length - 1],
this.index.hashes[0]
];
};
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');
while (this.orphan.bmap[hash]) {
root = hash;
hash = this.orphan.bmap[hash].prevBlock;
}
return root;
assert(false);
};
Chain.prototype.getHeight = function getHeight(hash) {
var i, height;
var entry = this.byHash(hash);
if (Array.isArray(hash))
hash = utils.toHex(hash);
else if (hash.hash)
hash = hash.hash('hex');
i = this.index.hashes.indexOf(hash);
height = this.index.heights[i];
if (height == null)
if (!entry)
return -1;
return height;
return entry.height;
};
Chain.prototype.getNextBlock = function getNextBlock(hash) {
var height, nextHeight, i;
var entry = this.byHeight(hash);
var nextHeight;
height = this.getHeight(hash);
nextHeight = this.index.heights[i + 1];
i = this.index.heights.indexOf(nextHeight);
if (nextHeight !== height + 1)
if (!entry)
return null;
return this.index.hashes[i] || null;
nextHeight = this.index.heights[entry.index + 1];
if (nextHeight == null || nextHeight !== entry.height + 1)
return null;
return this.index.hashes[entry.index + 1] || null;
};
Chain.prototype.toJSON = function toJSON() {
@ -708,4 +683,8 @@ Chain.prototype.fromJSON = function fromJSON(json) {
this.index.bloom.add(this.index.hashes[i], 'hex');
};
/**
* Expose
*/
module.exports = Chain;

557
lib/bcoin/fullchain.js Normal file
View File

@ -0,0 +1,557 @@
/**
* fullchain.js - fullnode blockchain management for bcoin
* Copyright (c) 2014-2015, Fedor Indutny (MIT License)
* https://github.com/indutny/bcoin
*/
var inherits = require('inherits');
var EventEmitter = require('events').EventEmitter;
var bcoin = require('../bcoin');
var bn = require('bn.js');
var constants = bcoin.protocol.constants;
var network = bcoin.protocol.network;
var utils = bcoin.utils;
var assert = utils.assert;
/**
* Chain
*/
function Chain(options) {
if (!(this instanceof Chain))
return new Chain(options);
EventEmitter.call(this);
this.options = options || {};
this.prefix = 'bt/chain/';
this.storage = this.options.storage;
this.strict = this.options.strict || false;
this.cacheLimit = this.options.cacheLimit || 2000;
this.tip = null;
this.orphan = {
map: {},
bmap: {},
count: 0
};
this.index = {
entries: [],
// Get hash by height
hashes: [],
// Get height by hash
heights: {},
lastTs: 0
};
this.fromJSON({
v: 1,
type: 'chain',
network: network.type,
entries: [
{
hash: utils.toHex(network.genesis._hash),
version: network.genesis.version,
// prevBlock: utils.toHex(network.genesis.prevBlock),
ts: network.genesis.ts,
bits: network.genesis.bits,
height: 0
}
]
});
// Last TS after preload, needed for fill percent
this.index.lastTs = this.index.entries[this.index.entries.length - 1].ts;
bcoin.chain.global = this;
this.loading = false;
this._init();
}
inherits(Chain, EventEmitter);
Chain.prototype._init = function _init() {
var self = this;
var s;
if (!this.storage)
return;
utils.nextTick(function() {
self.emit('debug', 'Chain is loading.');
});
this.loading = true;
s = this.storage.createReadStream({
start: this.prefix,
end: this.prefix + 'z'
});
s.on('data', function(data) {
data.value.hash = data.key.slice(self.prefix.length);
self._addIndex(ChainBlock.fromJSON(self, data.value));
});
s.on('error', function(err) {
self.emit('error', err);
});
s.on('end', function() {
self.loading = false;
self.emit('load');
self.emit('debug', 'Chain successfully loaded.');
});
};
Chain.prototype._addIndex = function _addIndex(entry) {
var self = this;
if (this.index.entries[entry.hash])
return new Error('Already added.');
if (this.index.hashes[entry.height] === entry.hash)
return new Error('Duplicate height.');
checkpoint = network.checkpoints[entry.height];
if (checkpoint) {
this.emit('checkpoint', entry.height, entry.hash, checkpoint);
if (hash !== checkpoint) {
this.resetLastCheckpoint(entry.height);
this.emit('fork', entry.height, entry.hash, checkpoint);
return new Error('Forked chain at checkpoint.');
}
}
this.index.entries[entry.height] = entry;
this.index.hashes[entry.height] = entry.hash;
this.index.heights[entry.hash] = entry.height;
this._save(entry);
};
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 self = this;
var forked = this.index.entries.slice(height + 1);
this.orphan.map = {};
this.orphan.bmap = {};
this.orphan.count = 0;
this.index.entries.length = height + 1;
this.index.heights = this.index.entries.reduce(function(out, entry) {
out[entry.hash] = entry.height;
return out;
}, {});
this.index.hashes.length = height + 1;
this.index.lastTs = this.index.entries[this.index.entries.length - 1].ts;
if (!this.storage)
return;
forked.forEach(function(entry) {
self._del(entry);
});
};
Chain.prototype.add = function add(block) {
if (this.loading) {
this.once('load', function() {
this.add(block);
});
return;
}
var res = false;
var err = null;
var initial = block;
var hash, prev, prevProbe, range, i, entry;
for (;;) {
// No need to revalidate orphans
if (!res && !block.verify()) {
err = new Error('Block verification failed.');
break;
}
hash = block.hash('hex');
prev = block.prevBlock;
// If the block is already known to be an orphan
if (this.orphan.map[prev]) {
err = new Error('Block is a known orphan.');
break;
}
i = this.index.heights[prev];
// If previous block wasn't ever seen - add current to orphans
if (i == null) {
this.orphan.count++;
this.orphan.map[prev] = block;
this.orphan.bmap[hash] = block;
break;
}
entry = new ChainBlock(this, {
hash: hash,
version: block.version,
// prevBlock: prev,
ts: block.ts,
bits: block.bits,
height: i + 1
});
// Already have block
if (this.index.hashes[entry.height] === hash)
break;
assert(this.index.heights[entry.hash] == null);
// Remove forked nodes from storage, if shorter chain is detected
if (this.index.hashes[entry.height]) {
if (!this.tip) {
this.tip = entry;
} else if (this.tip.chainwork.cmp(entry.chainwork) < 0) {
// Found fork
this.resetHeight(entry.height - 1);
this.tip = entry;
}
}
// Validated known block at this point - add it to index
this._addIndex(entry);
// At least one block was added
res = true;
if (!this.orphan.map[hash])
break;
// We have orphan child for this block - add it to chain
block = this.orphan.map[hash];
delete this.orphan.bmap[block.hash('hex')];
delete this.orphan.map[hash];
this.orphan.count--;
}
return err;
};
Chain.prototype.has = function has(hash, noProbe, cb) {
var res;
if (typeof noProbe === 'function') {
cb = noProbe;
noProbe = false;
}
if (this.loading) {
this.once('load', function() {
this.has(hash, noProbe, cb);
});
return;
}
res = this.hasBlock(hash) || this.hasOrphan(hash);
if (!cb)
return res;
cb = utils.asyncify(cb);
return cb(res);
};
Chain.prototype.byHeight = function byHeight(height) {
if (this.loading)
return null;
return this.index.entries[height];
};
Chain.prototype.byHash = function byHash(hash) {
if (this.loading)
return null;
if (Array.isArray(hash))
hash = utils.toHex(hash);
else if (hash.hash)
hash = hash.hash('hex');
return this.byHeight(this.index.heights[hash]);
};
Chain.prototype.hasBlock = function hasBlock(hash) {
if (this.loading)
return false;
return !!this.byHash(hash);
};
Chain.prototype.hasOrphan = function hasOrphan(hash) {
if (this.loading)
return false;
if (Array.isArray(hash))
hash = utils.toHex(hash);
else if (hash.hash)
hash = hash.hash('hex');
return !!this.orphan.bmap[hash];
};
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;
return delta < 40 * 60;
};
Chain.prototype.fillPercent = function fillPercent() {
var total = (+new Date() / 1000 - 40 * 60) - this.index.lastTs;
var current = this.index.entries[this.index.entries.length - 1].ts - this.index.lastTs;
return Math.max(0, Math.min(current / total, 1));
};
Chain.prototype.hashesInRange = function hashesInRange(start, end, cb) {
assert(false);
};
Chain.prototype.getLast = function getLast(cb) {
var res;
if (this.loading) {
this.once('load', function() {
this.getLast(cb);
});
return;
}
res = this.index.hashes[this.index.hashes.length - 1];
if (!cb)
return res;
cb = utils.asyncify(cb);
return cb(res);
};
Chain.prototype.getStartHeight = function getStartHeight() {
if (this.loading)
return 0;
return this.index.entries[this.index.entries.length - 1].height;
};
Chain.prototype.locatorHashes = function locatorHashes(start) {
var chain = this.index.hashes;
var hashes = [];
var top = chain.length - 1;
var step = 1;
var i;
if (start) {
if (Array.isArray(start))
start = utils.toHex(start);
else if (start.hash)
start = start.hash('hex');
}
if (typeof start === 'string') {
// Hash
if (this.index.heights[start])
top = this.index.heights[start];
} else if (typeof start === 'number') {
// Height
top = start;
}
i = top;
for (;;) {
hashes.push(chain[i]);
i = i - step;
if (i <= 0) {
hashes.push(chain[0]);
break;
}
if (hashes.length >= 10)
step *= 2;
}
return hashes;
};
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');
while (this.orphan.bmap[hash]) {
root = hash;
hash = this.orphan.bmap[hash].prevBlock;
}
return root;
};
Chain.prototype.getHeight = function getHeight(hash) {
var entry = this.byHash(hash);
if (!entry)
return -1;
return entry.height;
};
Chain.prototype.getNextBlock = function getNextBlock(hash) {
var entry = this.byHash(hash);
if (!entry || !entry.next)
return null;
return entry.next.hash;
};
Chain.prototype._save = function(entry) {
var self = this;
if (!this.storage)
return;
this.storage.put(this.prefix + entry.hash, entry.toJSON(), function(err) {
if (err)
self.emit('error', err);
});
};
Chain.prototype._del = function(entry) {
var self = this;
if (!this.storage)
return;
this.storage.del(this.prefix + entry.hash, function(err) {
if (err)
self.emit('error', err);
});
};
Chain.prototype.toJSON = function toJSON() {
var entries = this.index.entries;
return {
v: 1,
type: 'chain',
network: network.type,
entries: entries.map(function(entry) {
return entry.toJSON();
})
};
};
Chain.prototype.fromJSON = function fromJSON(json) {
assert.equal(json.v, 1);
assert.equal(json.type, 'chain');
assert.equal(json.network, network.type);
json.entries.forEach(function(entry) {
this._addIndex(ChainBlock.fromJSON(this, entry));
}, this);
if (this.index.entries.length === 0)
this.add(new bcoin.block(network.genesis, 'block'));
};
/**
* ChainBlock
*/
function ChainBlock(chain, data) {
this.chain = chain;
this.hash = data.hash;
this.version = data.version;
// this.prevBlock = data.prevBlock;
this.ts = data.ts;
this.bits = data.bits;
this.height = data.height;
this.chainwork = this.getChainwork();
}
ChainBlock.prototype.__defineGetter__('prev', function() {
return this.chain.index.entries[this.height - 1];
});
ChainBlock.prototype.__defineGetter__('next', function() {
return this.chain.index.entries[this.height + 1];
});
ChainBlock.prototype.__defineGetter__('proof', function() {
var target = utils.fromCompact(this.bits);
if (target.isNeg() || target.cmpn(0) === 0)
return new bn(0);
// May be faster:
// return new bn(1).shln(256).div(target.addn(1));
return new bn(2).pow(new bn(256)).div(target.addn(1));
});
ChainBlock.prototype.getChainwork = function() {
if (!this.prev)
return new bn(0);
return (this.prev ? this.prev.chainwork : new bn(0)).add(this.proof);
};
ChainBlock.prototype.toJSON = function() {
// return [
// this.hash,
// this.version,
// // this.prevBlock,
// this.ts,
// this.bits,
// this.height
// };
return {
hash: this.hash,
version: this.version,
// prevBlock: this.prevBlock,
ts: this.ts,
bits: this.bits,
height: this.height
};
};
ChainBlock.fromJSON = function(chain, json) {
// return new ChainBlock(chain, {
// hash: json[0],
// version: json[1],
// ts: json[2],
// bits: json[3],
// height: json[4]
// });
return new ChainBlock(chain, json);
};
/**
* Expose
*/
module.exports = Chain;

View File

@ -19,6 +19,7 @@ var network = bcoin.protocol.network;
function Pool(options) {
var self = this;
var Chain;
if (!(this instanceof Pool))
return new Pool(options);
@ -57,14 +58,22 @@ function Pool(options) {
hiReached: false
};
if (this.options.fullNode) {
if (!options.loadTimeout)
this.load.timeout = 30000;
if (!options.loadInterval)
this.load.interval = 5000;
}
this.maxRetries = options.maxRetries || 42;
this.requestTimeout = options.requestTimeout || 10000;
this.chain = new bcoin.chain({
Chain = this.options.fullNode
? bcoin.fullChain
: bcoin.spvChain;
this.chain = new Chain({
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.fullNode ? 100 : null,
fullNode: this.options.fullNode,
startHeight: this.options.startHeight
});
@ -77,11 +86,6 @@ function Pool(options) {
(Math.random() * 0xffffffff) | 0
);
this.bestHeight = 0;
this.bestBlock = null;
this.needSync = true;
this.syncPeer = null;
this.peers = {
// Peers that are loading blocks themselves
block: [],
@ -92,7 +96,9 @@ function Pool(options) {
};
this.block = {
lastHash: null
lastHash: null,
bestHeight: 0,
bestHash: null
};
this.request = {
@ -153,19 +159,23 @@ Pool.prototype._init = function _init() {
this._load();
this.chain.on('missing', function(hash, preload, parent) {
if (self.options.fullNode) return;
self._request('block', hash, { force: true });
self._scheduleRequests();
self._loadRange(preload);
if (!self.options.fullNode) {
self._request('filtered', hash, { force: true });
self._scheduleRequests();
self._loadRange(preload);
}
});
this.chain.on('fork', function(height, hash, checkpoint) {
var peer = self.syncPeer;
var peer = self.peers.load;
if (!self.options.fullNode)
return;
if (!peer)
return;
delete self.syncPeer;
peer.destroy();
self.startSync();
});
this.options.wallets.forEach(function(w) {
@ -180,7 +190,7 @@ Pool.prototype._addLoader = function _addLoader() {
if (this.destroyed)
return;
if (this.peers.load !== null)
if (this.peers.load != null)
return;
peer = new bcoin.peer(this, this.createSocket, {
@ -188,6 +198,7 @@ Pool.prototype._addLoader = function _addLoader() {
startHeight: this.options.startHeight,
relay: this.options.relay
});
this.peers.load = peer;
peer.on('error', function(err) {
@ -204,18 +215,23 @@ Pool.prototype._addLoader = function _addLoader() {
self._addLoader();
}
interval = setInterval(function() {
self._load();
}, this.load.interval);
peer.once('ack', function() {
peer.updateWatch();
if (!self._load())
clearTimeout(timer);
});
if (this.options.fullNode)
return;
peer.on('version', function(version) {
if (version.height > self.block.bestHeight)
self.block.bestHeight = version.height;
self.emit('version', version, peer);
});
function load() {
self._load();
}
interval = setInterval(load, this.load.interval);
function destroy() {
// Chain is full and up-to-date
@ -231,44 +247,119 @@ Pool.prototype._addLoader = function _addLoader() {
timer = setTimeout(destroy, this.load.timeout);
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.emit('blocks', hashes);
if (hashes.length === 0) {
// Reset global load
self.block.lastHash = null;
return;
}
// Request each block
hashes.forEach(function(hash) {
self._request('block', hash);
});
if (!self.options.fullNode) {
// Request each block
hashes.forEach(function(hash) {
self._request('filtered', hash);
});
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];
if (self.chain.hasOrphan(hash)) {
peer.loadBlocks(self.chain.locatorHashes(), self.chain.getOrphanRoot(hash));
continue;
}
if (i === hashes.length - 1)
peer.loadBlocks(self.chain.locatorHashes(), 0);
if (!self.chain.hasBlock(hash))
self._request('block', hash);
}
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;
}
self.block.lastHash = hashes[hashes.length - 1];
// Store last hash to continue global load
self.block.lastHash = hashes[hashes.length - 1];
clearInterval(interval);
interval = setInterval(load, self.load.interval);
clearTimeout(timer);
// Reinstantiate timeout
if (self._load())
timer = setTimeout(destroy, self.load.timeout);
});
clearTimeout(timer);
timer = setTimeout(destroy, self.load.timeout);
});
};
Pool.prototype._handleMerkle = function _handleMerkle(block, peer) {
this._response(block);
this.chain.add(block);
this.emit('chain-progress', this.chain.fillPercent(), peer);
this.emit('block', block, peer);
};
Pool.prototype._handleBlock = function _handleBlock(block, peer) {
var chainIndex = this.chain.index;
var hash, len, orphan, err;
backoff = 0;
this._response(block);
hash = block.hash('hex');
len = chainIndex.hashes.length;
orphan = this.chain.hasOrphan(block);
err = this.chain.add(block);
if (err)
this.emit('chain-error', err, peer);
this.emit('_block', block, peer);
if (this.chain.hasOrphan(block)) {
peer.loadBlocks(this.chain.locatorHashes(), this.chain.getOrphanRoot(block));
if (!orphan)
this.emit('orphan', block, peer);
return;
}
if (chainIndex.hashes.length === len)
return;
this.emit('chain-progress', this.chain.fillPercent(), peer);
this.emit('block', block, peer);
};
Pool.prototype.isFull = function isFull() {
return this.chain.isFull();
};
@ -304,9 +395,7 @@ Pool.prototype._loadRange = function _loadRange(hashes, force) {
Pool.prototype._load = function _load() {
var self = this;
if (this.options.fullNode)
return;
var next;
if (this.request.queue.length >= this.load.hwm) {
this.load.hiReached = true;
@ -315,19 +404,32 @@ Pool.prototype._load = function _load() {
this.load.hiReached = false;
// Load more blocks, starting from last hash
if (this.block.lastHash)
next(this.block.lastHash);
else
this.chain.getLast(next);
console.log(
'Requesting inv packet from %s with getblocks',
this.peers.load.address);
function next(hash) {
if (!self.peers.load)
self._addLoader();
if (!this.options.fullNode) {
next = function(hash) {
if (!self.peers.load)
self._addLoader();
else
self.peers.load.loadBlocks([ hash ]);
};
// Load more blocks, starting from last hash
if (this.block.lastHash)
next(this.block.lastHash);
else
self.peers.load.loadBlocks([ hash ]);
this.chain.getLast(next);
return true;
}
if (!this.peers.load)
this._addLoader();
else
this.peers.load.loadBlocks(this.chain.locatorHashes(), 0);
return true;
};
@ -392,48 +494,13 @@ Pool.prototype._addPeer = function _addPeer(backoff) {
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);
self._handleMerkle(block, peer);
});
} else {
peer.on('block', function(block) {
var hashes = self.chain.index.hashes;
var hash, len, orphan, err;
if (self.syncPeer !== peer)
return;
// Reset backoff, peer seems to be responsive
backoff = 0;
self._response(block);
hash = block.hash('hex');
len = hashes.length;
orphan = self.chain.hasOrphan(block);
err = self.chain.add(block);
if (err)
self.emit('chain-error', err, peer);
self.emit('_block', block, peer);
if (self.chain.hasOrphan(block)) {
peer.loadBlocks(self.chain.locatorHashes(), self.chain.getOrphanRoot(block));
if (!orphan)
self.emit('orphan', block, peer);
return;
}
if (hashes.length === len)
return;
self.needSync = hashes[hashes.length - 1] !== self.bestBlock;
self.emit('chain-progress', self.chain.fillPercent(), peer);
self.emit('block', block, peer);
self._handleBlock(block, peer);
});
}
@ -461,7 +528,7 @@ Pool.prototype._addPeer = function _addPeer(backoff) {
peer.on('blocks', function(blocks) {
if (blocks.length === 1)
self.bestBlock = peer.bestBlock;
self.block.bestHash = peer.bestBlock;
self.emit('blocks', blocks, peer);
});
@ -470,80 +537,26 @@ Pool.prototype._addPeer = function _addPeer(backoff) {
});
peer.on('version', function(version) {
if (version.height > self.bestHeight)
self.bestHeight = version.height;
if (version.height > self.block.bestHeight)
self.block.bestHeight = version.height;
self.emit('version', version, peer);
});
peer.on('ack', function() {
if (self.options.fullNode) {
if (self.peers.block.length >= Math.min(5, self.size))
self.startSync();
}
});
utils.nextTick(function() {
self.emit('peer', peer);
});
};
Pool.prototype.bestPeer = function bestPeer() {
var best = null;
this.peers.block.forEach(function(peer) {
return this.peers.block.reduce(function(best, peer) {
if (!peer.version || !peer.socket)
return;
if (!best || peer.version.height > best.version.height)
best = peer;
});
return peer;
if (best)
this.emit('debug', 'Best peer: %s', best.address);
return best;
};
Pool.prototype.startSync = function startSync(peer) {
if (!this.options.fullNode)
return;
if (this.syncPeer)
return;
peer = peer || this.bestPeer();
if (!peer)
return;
this.syncPeer = peer;
peer.on('blocks', function(hashes) {
var req = [];
var i, hash;
for (i = 0; i < hashes.length; i++) {
hash = hashes[i];
if (peer.chain.hasOrphan(hash)) {
peer.loadBlocks(peer.chain.locatorHashes(), peer.chain.getOrphanRoot(hash));
continue;
}
if (!peer.chain.hasBlock(hash)) {
req.push({ type: 'block', hash: hash });
continue;
}
if (i === hashes.length - 1) {
peer.loadBlocks(peer.chain.locatorHashes(), 0);
continue;
}
}
if (req.length)
peer.getData(req);
});
this.emit('debug', 'version (%s): sending locator hashes', peer.address);
peer.loadBlocks(this.chain.locatorHashes(), 0);
return best;
}, null);
};
Pool.prototype._removePeer = function _removePeer(peer) {
@ -557,16 +570,14 @@ Pool.prototype._removePeer = function _removePeer(peer) {
if (this.peers.load === peer)
this.peers.load = null;
if (this.syncPeer === peer) {
delete this.syncPeer;
this.startSync();
}
};
Pool.prototype.watch = function watch(id) {
var hid, i;
if (this.options.fullNode)
return;
if (id instanceof bcoin.wallet) {
this.watchWallet(id);
return;
@ -595,6 +606,9 @@ Pool.prototype.watch = function watch(id) {
Pool.prototype.unwatch = function unwatch(id) {
var i;
if (this.options.fullNode)
return;
id = utils.toHex(id);
if (!this.bloom.test(id, 'hex'))
@ -648,6 +662,9 @@ Pool.prototype.addWallet = function addWallet(w, defaultTs) {
if (!ts)
ts = defaultTs || ((+new Date / 1000) - 7 * 24 * 3600);
if (self.options.fullNode)
return;
self.search(false, ts, e);
}
@ -663,12 +680,16 @@ Pool.prototype.removeWallet = function removeWallet(w) {
};
Pool.prototype.watchWallet = function watchWallet(w) {
if (this.options.fullNode)
return;
if (w.type === 'scripthash') {
// 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:
@ -676,12 +697,16 @@ Pool.prototype.watchWallet = function watchWallet(w) {
};
Pool.prototype.unwatchWallet = function unwatchWallet(w) {
if (this.options.fullNode)
return;
if (w.type === 'scripthash') {
// 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());
// For the pubkey in p2pkh inputs:
@ -691,6 +716,9 @@ Pool.prototype.unwatchWallet = function unwatchWallet(w) {
Pool.prototype.search = function search(id, range, e) {
var self = this;
if (this.options.fullNode)
return;
e = e || new EventEmitter();
// Optional id argument
@ -726,6 +754,8 @@ Pool.prototype.search = function search(id, range, e) {
self.watch(id);
self._loadRange(hashes, true);
// XXX
// return;
hashes = hashes.slice().reverse();
hashes.forEach(function(hash, i) {
// Get the block that is in index
@ -802,7 +832,7 @@ Pool.prototype._request = function _request(type, hash, options, cb) {
}
// Block should be not in chain, or be requested
if (!options.force && type === 'block')
if (!options.force && (type === 'block' || type === 'filtered'))
return this.chain.has(hash, true, next);
return next(false);
@ -846,6 +876,7 @@ Pool.prototype._scheduleRequests = function _scheduleRequests() {
Pool.prototype._doRequests = function _doRequests() {
var queue, above, items, below;
var red, count, split, i, off, req, j;
var mapReq;
if (this.request.active >= this.parallel)
return;
@ -864,19 +895,29 @@ Pool.prototype._doRequests = function _doRequests() {
if (above && below && this.load.hiReached)
this._load();
function mapReq(item) {
return item.start(this.peers.block[i]);
if (!this.options.fullNode) {
mapReq = function(item) {
return item.start(this.peers.block[i]);
};
// 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);
}
return;
}
// 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);
}
req = items.map(function(item) {
return item.start(this.peers.load);
}, this);
this.peers.load.getData(req);
};
Pool.prototype.getTX = function getTX(hash, range, cb) {
@ -1056,13 +1097,8 @@ LoadRequest.prototype.start = function start(peer) {
this.peer = peer;
this.peer.once('close', this.onclose);
if (this.type === 'block')
reqType = 'filtered';
else if (this.type === 'tx')
reqType = 'tx';
return {
type: reqType,
type: this.type,
hash: this.hash
};
};

View File

@ -5,14 +5,7 @@ var net = require('net');
var path = require('path');
var bcoin = require('../');
var addrs = [
'seed.bitcoin.sipa.be',
'dnsseed.bluematt.me',
'dnsseed.bitcoin.dashjr.org',
'seed.bitcoinstats.com',
'seed.bitnodes.io',
'bitseed.xf2.org'
];
var addrs = bcoin.protocol.network.seeds.slice();
var pool = bcoin.pool({
size: 32,
@ -25,6 +18,8 @@ var pool = bcoin.pool({
}
});
pool.on('error', function() {});
console.log('Updating bcoin preloaded chain...');
pool.on('block', function(block) {