add fullnode chain. improve pool.
This commit is contained in:
parent
65dd1727be
commit
146e50dc68
@ -21,6 +21,8 @@ bcoin.tx = require('./bcoin/tx');
|
|||||||
bcoin.txPool = require('./bcoin/tx-pool');
|
bcoin.txPool = require('./bcoin/tx-pool');
|
||||||
bcoin.block = require('./bcoin/block');
|
bcoin.block = require('./bcoin/block');
|
||||||
bcoin.chain = require('./bcoin/chain');
|
bcoin.chain = require('./bcoin/chain');
|
||||||
|
bcoin.spvChain = require('./bcoin/chain');
|
||||||
|
bcoin.fullChain = require('./bcoin/fullchain');
|
||||||
bcoin.wallet = require('./bcoin/wallet');
|
bcoin.wallet = require('./bcoin/wallet');
|
||||||
bcoin.peer = require('./bcoin/peer');
|
bcoin.peer = require('./bcoin/peer');
|
||||||
bcoin.pool = require('./bcoin/pool');
|
bcoin.pool = require('./bcoin/pool');
|
||||||
|
|||||||
@ -267,7 +267,7 @@ Block.prototype.getNextBlock = function getNextBlock(chain) {
|
|||||||
chain = chain || bcoin.chain.global;
|
chain = chain || bcoin.chain.global;
|
||||||
|
|
||||||
if (!chain)
|
if (!chain)
|
||||||
return utils.toHex(constants.protocol.zeroHash);
|
return utils.toHex(constants.zeroHash);
|
||||||
|
|
||||||
next = chain.getNextBlock(this.hash('hex'));
|
next = chain.getNextBlock(this.hash('hex'));
|
||||||
|
|
||||||
|
|||||||
@ -53,24 +53,12 @@ function Chain(options) {
|
|||||||
|
|
||||||
this.request = new utils.RequestCache();
|
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);
|
this.fromJSON(preload);
|
||||||
|
|
||||||
// Last TS after preload, needed for fill percent
|
// Last TS after preload, needed for fill percent
|
||||||
this.index.lastTs = this.index.ts[this.index.ts.length - 1];
|
this.index.lastTs = this.index.ts[this.index.ts.length - 1];
|
||||||
|
|
||||||
Chain.global = this;
|
bcoin.chain.global = this;
|
||||||
|
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
this._init();
|
this._init();
|
||||||
@ -338,7 +326,7 @@ Chain.prototype.add = function add(block) {
|
|||||||
// At least one block was added
|
// At least one block was added
|
||||||
res = true;
|
res = true;
|
||||||
this.block.list.push(block);
|
this.block.list.push(block);
|
||||||
this._bloomBlock(block);
|
this.block.bloom.add(hash, 'hex');
|
||||||
|
|
||||||
// Fullfill request
|
// Fullfill request
|
||||||
this.request.fullfill(hash, block);
|
this.request.fullfill(hash, block);
|
||||||
@ -371,11 +359,7 @@ Chain.prototype._compress = function compress() {
|
|||||||
this.block.bloom.reset();
|
this.block.bloom.reset();
|
||||||
|
|
||||||
for (i = 0; i < this.block.list.length; i++)
|
for (i = 0; i < this.block.list.length; i++)
|
||||||
this._bloomBlock(this.block.list[i]);
|
this.block.bloom.add(this.block.list[i].hash('hex'), 'hex');
|
||||||
};
|
|
||||||
|
|
||||||
Chain.prototype._bloomBlock = function _bloomBlock(block) {
|
|
||||||
this.block.bloom.add(block.hash(), 'hex');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Chain.prototype.has = function has(hash, noProbe, cb) {
|
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]);
|
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) {
|
Chain.prototype.hasBlock = function hasBlock(hash) {
|
||||||
if (this.loading)
|
if (this.loading)
|
||||||
return false;
|
return false;
|
||||||
@ -533,97 +556,49 @@ Chain.prototype.getLast = function getLast(cb) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Chain.prototype.getStartHeight = function getStartHeight() {
|
Chain.prototype.getStartHeight = function getStartHeight() {
|
||||||
if (!this.options.fullNode) {
|
if (this.options.startHeight != null)
|
||||||
if (this.options.startHeight != null) {
|
return this.options.startHeight;
|
||||||
return this.options.startHeight;
|
return 0;
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return this.index.heights[this.index.heights.length - 1];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Chain.prototype.locatorHashes = function locatorHashes(start) {
|
Chain.prototype.locatorHashes = function locatorHashes(start) {
|
||||||
var chain = this.index.hashes;
|
assert(start == null);
|
||||||
var hashes = [];
|
|
||||||
var top = chain.length - 1;
|
|
||||||
var step = 1;
|
|
||||||
var i;
|
|
||||||
|
|
||||||
if (typeof start === 'string') {
|
if (this.index.hashes.length === 1)
|
||||||
// Hash
|
return [this.index.hashes[0]];
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
i = top;
|
return [
|
||||||
for (;;) {
|
this.index.hashes[this.index.hashes.length - 1],
|
||||||
hashes.push(chain[i]);
|
this.index.hashes[0]
|
||||||
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) {
|
Chain.prototype.getOrphanRoot = function getOrphanRoot(hash) {
|
||||||
var self = this;
|
assert(false);
|
||||||
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) {
|
Chain.prototype.getHeight = function getHeight(hash) {
|
||||||
var i, height;
|
var entry = this.byHash(hash);
|
||||||
|
|
||||||
if (Array.isArray(hash))
|
if (!entry)
|
||||||
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)
|
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
return height;
|
return entry.height;
|
||||||
};
|
};
|
||||||
|
|
||||||
Chain.prototype.getNextBlock = function getNextBlock(hash) {
|
Chain.prototype.getNextBlock = function getNextBlock(hash) {
|
||||||
var height, nextHeight, i;
|
var entry = this.byHeight(hash);
|
||||||
|
var nextHeight;
|
||||||
|
|
||||||
height = this.getHeight(hash);
|
if (!entry)
|
||||||
nextHeight = this.index.heights[i + 1];
|
|
||||||
i = this.index.heights.indexOf(nextHeight);
|
|
||||||
|
|
||||||
if (nextHeight !== height + 1)
|
|
||||||
return null;
|
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() {
|
Chain.prototype.toJSON = function toJSON() {
|
||||||
@ -708,4 +683,8 @@ Chain.prototype.fromJSON = function fromJSON(json) {
|
|||||||
this.index.bloom.add(this.index.hashes[i], 'hex');
|
this.index.bloom.add(this.index.hashes[i], 'hex');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expose
|
||||||
|
*/
|
||||||
|
|
||||||
module.exports = Chain;
|
module.exports = Chain;
|
||||||
|
|||||||
557
lib/bcoin/fullchain.js
Normal file
557
lib/bcoin/fullchain.js
Normal 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;
|
||||||
@ -19,6 +19,7 @@ var network = bcoin.protocol.network;
|
|||||||
|
|
||||||
function Pool(options) {
|
function Pool(options) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
var Chain;
|
||||||
|
|
||||||
if (!(this instanceof Pool))
|
if (!(this instanceof Pool))
|
||||||
return new Pool(options);
|
return new Pool(options);
|
||||||
@ -57,14 +58,22 @@ function Pool(options) {
|
|||||||
hiReached: false
|
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.maxRetries = options.maxRetries || 42;
|
||||||
this.requestTimeout = options.requestTimeout || 10000;
|
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,
|
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,
|
fullNode: this.options.fullNode,
|
||||||
startHeight: this.options.startHeight
|
startHeight: this.options.startHeight
|
||||||
});
|
});
|
||||||
@ -77,11 +86,6 @@ function Pool(options) {
|
|||||||
(Math.random() * 0xffffffff) | 0
|
(Math.random() * 0xffffffff) | 0
|
||||||
);
|
);
|
||||||
|
|
||||||
this.bestHeight = 0;
|
|
||||||
this.bestBlock = null;
|
|
||||||
this.needSync = true;
|
|
||||||
this.syncPeer = null;
|
|
||||||
|
|
||||||
this.peers = {
|
this.peers = {
|
||||||
// Peers that are loading blocks themselves
|
// Peers that are loading blocks themselves
|
||||||
block: [],
|
block: [],
|
||||||
@ -92,7 +96,9 @@ function Pool(options) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.block = {
|
this.block = {
|
||||||
lastHash: null
|
lastHash: null,
|
||||||
|
bestHeight: 0,
|
||||||
|
bestHash: null
|
||||||
};
|
};
|
||||||
|
|
||||||
this.request = {
|
this.request = {
|
||||||
@ -153,19 +159,23 @@ Pool.prototype._init = function _init() {
|
|||||||
this._load();
|
this._load();
|
||||||
|
|
||||||
this.chain.on('missing', function(hash, preload, parent) {
|
this.chain.on('missing', function(hash, preload, parent) {
|
||||||
if (self.options.fullNode) return;
|
if (!self.options.fullNode) {
|
||||||
self._request('block', hash, { force: true });
|
self._request('filtered', hash, { force: true });
|
||||||
self._scheduleRequests();
|
self._scheduleRequests();
|
||||||
self._loadRange(preload);
|
self._loadRange(preload);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.chain.on('fork', function(height, hash, checkpoint) {
|
this.chain.on('fork', function(height, hash, checkpoint) {
|
||||||
var peer = self.syncPeer;
|
var peer = self.peers.load;
|
||||||
|
|
||||||
|
if (!self.options.fullNode)
|
||||||
|
return;
|
||||||
|
|
||||||
if (!peer)
|
if (!peer)
|
||||||
return;
|
return;
|
||||||
delete self.syncPeer;
|
|
||||||
peer.destroy();
|
peer.destroy();
|
||||||
self.startSync();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.options.wallets.forEach(function(w) {
|
this.options.wallets.forEach(function(w) {
|
||||||
@ -180,7 +190,7 @@ Pool.prototype._addLoader = function _addLoader() {
|
|||||||
if (this.destroyed)
|
if (this.destroyed)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (this.peers.load !== null)
|
if (this.peers.load != null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
peer = new bcoin.peer(this, this.createSocket, {
|
peer = new bcoin.peer(this, this.createSocket, {
|
||||||
@ -188,6 +198,7 @@ Pool.prototype._addLoader = function _addLoader() {
|
|||||||
startHeight: this.options.startHeight,
|
startHeight: this.options.startHeight,
|
||||||
relay: this.options.relay
|
relay: this.options.relay
|
||||||
});
|
});
|
||||||
|
|
||||||
this.peers.load = peer;
|
this.peers.load = peer;
|
||||||
|
|
||||||
peer.on('error', function(err) {
|
peer.on('error', function(err) {
|
||||||
@ -204,18 +215,23 @@ Pool.prototype._addLoader = function _addLoader() {
|
|||||||
self._addLoader();
|
self._addLoader();
|
||||||
}
|
}
|
||||||
|
|
||||||
interval = setInterval(function() {
|
|
||||||
self._load();
|
|
||||||
}, this.load.interval);
|
|
||||||
|
|
||||||
peer.once('ack', function() {
|
peer.once('ack', function() {
|
||||||
peer.updateWatch();
|
peer.updateWatch();
|
||||||
if (!self._load())
|
if (!self._load())
|
||||||
clearTimeout(timer);
|
clearTimeout(timer);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.options.fullNode)
|
peer.on('version', function(version) {
|
||||||
return;
|
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() {
|
function destroy() {
|
||||||
// Chain is full and up-to-date
|
// Chain is full and up-to-date
|
||||||
@ -231,44 +247,119 @@ Pool.prototype._addLoader = function _addLoader() {
|
|||||||
|
|
||||||
timer = setTimeout(destroy, this.load.timeout);
|
timer = setTimeout(destroy, this.load.timeout);
|
||||||
|
|
||||||
|
peer.on('block', function(block) {
|
||||||
|
self._handleBlock(block, peer);
|
||||||
|
});
|
||||||
|
|
||||||
// Split blocks and request them using multiple peers
|
// Split blocks and request them using multiple peers
|
||||||
peer.on('blocks', function(hashes) {
|
peer.on('blocks', function(hashes) {
|
||||||
|
var i, hash;
|
||||||
|
|
||||||
|
self.emit('blocks', hashes);
|
||||||
|
|
||||||
if (hashes.length === 0) {
|
if (hashes.length === 0) {
|
||||||
// Reset global load
|
// Reset global load
|
||||||
self.block.lastHash = null;
|
self.block.lastHash = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Request each block
|
if (!self.options.fullNode) {
|
||||||
hashes.forEach(function(hash) {
|
// Request each block
|
||||||
self._request('block', hash);
|
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();
|
self._scheduleRequests();
|
||||||
|
|
||||||
// The part of the response is in chain, no need to escalate requests
|
self.block.lastHash = hashes[hashes.length - 1];
|
||||||
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
|
clearInterval(interval);
|
||||||
self.block.lastHash = hashes[hashes.length - 1];
|
interval = setInterval(load, self.load.interval);
|
||||||
|
|
||||||
clearTimeout(timer);
|
clearTimeout(timer);
|
||||||
|
timer = setTimeout(destroy, self.load.timeout);
|
||||||
// Reinstantiate timeout
|
|
||||||
if (self._load())
|
|
||||||
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() {
|
Pool.prototype.isFull = function isFull() {
|
||||||
return this.chain.isFull();
|
return this.chain.isFull();
|
||||||
};
|
};
|
||||||
@ -304,9 +395,7 @@ Pool.prototype._loadRange = function _loadRange(hashes, force) {
|
|||||||
|
|
||||||
Pool.prototype._load = function _load() {
|
Pool.prototype._load = function _load() {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
var next;
|
||||||
if (this.options.fullNode)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (this.request.queue.length >= this.load.hwm) {
|
if (this.request.queue.length >= this.load.hwm) {
|
||||||
this.load.hiReached = true;
|
this.load.hiReached = true;
|
||||||
@ -315,19 +404,32 @@ Pool.prototype._load = function _load() {
|
|||||||
|
|
||||||
this.load.hiReached = false;
|
this.load.hiReached = false;
|
||||||
|
|
||||||
// Load more blocks, starting from last hash
|
console.log(
|
||||||
if (this.block.lastHash)
|
'Requesting inv packet from %s with getblocks',
|
||||||
next(this.block.lastHash);
|
this.peers.load.address);
|
||||||
else
|
|
||||||
this.chain.getLast(next);
|
|
||||||
|
|
||||||
function next(hash) {
|
if (!this.options.fullNode) {
|
||||||
if (!self.peers.load)
|
next = function(hash) {
|
||||||
self._addLoader();
|
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
|
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;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -392,48 +494,13 @@ Pool.prototype._addPeer = function _addPeer(backoff) {
|
|||||||
peer.on('merkleblock', function(block) {
|
peer.on('merkleblock', function(block) {
|
||||||
// Reset backoff, peer seems to be responsive
|
// Reset backoff, peer seems to be responsive
|
||||||
backoff = 0;
|
backoff = 0;
|
||||||
|
self._handleMerkle(block, peer);
|
||||||
self._response(block);
|
|
||||||
self.chain.add(block);
|
|
||||||
self.emit('chain-progress', self.chain.fillPercent(), peer);
|
|
||||||
self.emit('block', block, peer);
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
peer.on('block', function(block) {
|
peer.on('block', function(block) {
|
||||||
var hashes = self.chain.index.hashes;
|
// Reset backoff, peer seems to be responsive
|
||||||
var hash, len, orphan, err;
|
|
||||||
|
|
||||||
if (self.syncPeer !== peer)
|
|
||||||
return;
|
|
||||||
|
|
||||||
backoff = 0;
|
backoff = 0;
|
||||||
|
self._handleBlock(block, peer);
|
||||||
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);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -461,7 +528,7 @@ Pool.prototype._addPeer = function _addPeer(backoff) {
|
|||||||
|
|
||||||
peer.on('blocks', function(blocks) {
|
peer.on('blocks', function(blocks) {
|
||||||
if (blocks.length === 1)
|
if (blocks.length === 1)
|
||||||
self.bestBlock = peer.bestBlock;
|
self.block.bestHash = peer.bestBlock;
|
||||||
self.emit('blocks', blocks, peer);
|
self.emit('blocks', blocks, peer);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -470,80 +537,26 @@ Pool.prototype._addPeer = function _addPeer(backoff) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
peer.on('version', function(version) {
|
peer.on('version', function(version) {
|
||||||
if (version.height > self.bestHeight)
|
if (version.height > self.block.bestHeight)
|
||||||
self.bestHeight = version.height;
|
self.block.bestHeight = version.height;
|
||||||
self.emit('version', version, peer);
|
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() {
|
utils.nextTick(function() {
|
||||||
self.emit('peer', peer);
|
self.emit('peer', peer);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
Pool.prototype.bestPeer = function bestPeer() {
|
Pool.prototype.bestPeer = function bestPeer() {
|
||||||
var best = null;
|
return this.peers.block.reduce(function(best, peer) {
|
||||||
|
|
||||||
this.peers.block.forEach(function(peer) {
|
|
||||||
if (!peer.version || !peer.socket)
|
if (!peer.version || !peer.socket)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!best || peer.version.height > best.version.height)
|
if (!best || peer.version.height > best.version.height)
|
||||||
best = peer;
|
return peer;
|
||||||
});
|
|
||||||
|
|
||||||
if (best)
|
return best;
|
||||||
this.emit('debug', 'Best peer: %s', best.address);
|
}, null);
|
||||||
|
|
||||||
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);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Pool.prototype._removePeer = function _removePeer(peer) {
|
Pool.prototype._removePeer = function _removePeer(peer) {
|
||||||
@ -557,16 +570,14 @@ Pool.prototype._removePeer = function _removePeer(peer) {
|
|||||||
|
|
||||||
if (this.peers.load === peer)
|
if (this.peers.load === peer)
|
||||||
this.peers.load = null;
|
this.peers.load = null;
|
||||||
|
|
||||||
if (this.syncPeer === peer) {
|
|
||||||
delete this.syncPeer;
|
|
||||||
this.startSync();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Pool.prototype.watch = function watch(id) {
|
Pool.prototype.watch = function watch(id) {
|
||||||
var hid, i;
|
var hid, i;
|
||||||
|
|
||||||
|
if (this.options.fullNode)
|
||||||
|
return;
|
||||||
|
|
||||||
if (id instanceof bcoin.wallet) {
|
if (id instanceof bcoin.wallet) {
|
||||||
this.watchWallet(id);
|
this.watchWallet(id);
|
||||||
return;
|
return;
|
||||||
@ -595,6 +606,9 @@ Pool.prototype.watch = function watch(id) {
|
|||||||
Pool.prototype.unwatch = function unwatch(id) {
|
Pool.prototype.unwatch = function unwatch(id) {
|
||||||
var i;
|
var i;
|
||||||
|
|
||||||
|
if (this.options.fullNode)
|
||||||
|
return;
|
||||||
|
|
||||||
id = utils.toHex(id);
|
id = utils.toHex(id);
|
||||||
|
|
||||||
if (!this.bloom.test(id, 'hex'))
|
if (!this.bloom.test(id, 'hex'))
|
||||||
@ -648,6 +662,9 @@ Pool.prototype.addWallet = function addWallet(w, defaultTs) {
|
|||||||
if (!ts)
|
if (!ts)
|
||||||
ts = defaultTs || ((+new Date / 1000) - 7 * 24 * 3600);
|
ts = defaultTs || ((+new Date / 1000) - 7 * 24 * 3600);
|
||||||
|
|
||||||
|
if (self.options.fullNode)
|
||||||
|
return;
|
||||||
|
|
||||||
self.search(false, ts, e);
|
self.search(false, ts, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -663,12 +680,16 @@ Pool.prototype.removeWallet = function removeWallet(w) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Pool.prototype.watchWallet = function watchWallet(w) {
|
Pool.prototype.watchWallet = function watchWallet(w) {
|
||||||
|
if (this.options.fullNode)
|
||||||
|
return;
|
||||||
|
|
||||||
if (w.type === 'scripthash') {
|
if (w.type === 'scripthash') {
|
||||||
// For the redeem script hash in outputs:
|
// For the redeem script hash in outputs:
|
||||||
this.watch(w.getFullHash());
|
this.watch(w.getFullHash());
|
||||||
// For the redeem script in inputs:
|
// For the redeem script in inputs:
|
||||||
this.watch(w.getFullPublicKey());
|
this.watch(w.getFullPublicKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
// For the pubkey hash in outputs:
|
// For the pubkey hash in outputs:
|
||||||
this.watch(w.getOwnHash());
|
this.watch(w.getOwnHash());
|
||||||
// For the pubkey in inputs:
|
// For the pubkey in inputs:
|
||||||
@ -676,12 +697,16 @@ Pool.prototype.watchWallet = function watchWallet(w) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Pool.prototype.unwatchWallet = function unwatchWallet(w) {
|
Pool.prototype.unwatchWallet = function unwatchWallet(w) {
|
||||||
|
if (this.options.fullNode)
|
||||||
|
return;
|
||||||
|
|
||||||
if (w.type === 'scripthash') {
|
if (w.type === 'scripthash') {
|
||||||
// For the redeem script hash in p2sh outputs:
|
// For the redeem script hash in p2sh outputs:
|
||||||
this.unwatch(w.getFullHash());
|
this.unwatch(w.getFullHash());
|
||||||
// For the redeem script in p2sh inputs:
|
// For the redeem script in p2sh inputs:
|
||||||
this.unwatch(w.getFullPublicKey());
|
this.unwatch(w.getFullPublicKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
// For the pubkey hash in p2pk/multisig outputs:
|
// For the pubkey hash in p2pk/multisig outputs:
|
||||||
this.unwatch(w.getOwnHash());
|
this.unwatch(w.getOwnHash());
|
||||||
// For the pubkey in p2pkh inputs:
|
// For the pubkey in p2pkh inputs:
|
||||||
@ -691,6 +716,9 @@ Pool.prototype.unwatchWallet = function unwatchWallet(w) {
|
|||||||
Pool.prototype.search = function search(id, range, e) {
|
Pool.prototype.search = function search(id, range, e) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
|
if (this.options.fullNode)
|
||||||
|
return;
|
||||||
|
|
||||||
e = e || new EventEmitter();
|
e = e || new EventEmitter();
|
||||||
|
|
||||||
// Optional id argument
|
// Optional id argument
|
||||||
@ -726,6 +754,8 @@ Pool.prototype.search = function search(id, range, e) {
|
|||||||
self.watch(id);
|
self.watch(id);
|
||||||
|
|
||||||
self._loadRange(hashes, true);
|
self._loadRange(hashes, true);
|
||||||
|
// XXX
|
||||||
|
// return;
|
||||||
hashes = hashes.slice().reverse();
|
hashes = hashes.slice().reverse();
|
||||||
hashes.forEach(function(hash, i) {
|
hashes.forEach(function(hash, i) {
|
||||||
// Get the block that is in index
|
// 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
|
// 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 this.chain.has(hash, true, next);
|
||||||
|
|
||||||
return next(false);
|
return next(false);
|
||||||
@ -846,6 +876,7 @@ Pool.prototype._scheduleRequests = function _scheduleRequests() {
|
|||||||
Pool.prototype._doRequests = function _doRequests() {
|
Pool.prototype._doRequests = function _doRequests() {
|
||||||
var queue, above, items, below;
|
var queue, above, items, below;
|
||||||
var red, count, split, i, off, req, j;
|
var red, count, split, i, off, req, j;
|
||||||
|
var mapReq;
|
||||||
|
|
||||||
if (this.request.active >= this.parallel)
|
if (this.request.active >= this.parallel)
|
||||||
return;
|
return;
|
||||||
@ -864,19 +895,29 @@ Pool.prototype._doRequests = function _doRequests() {
|
|||||||
if (above && below && this.load.hiReached)
|
if (above && below && this.load.hiReached)
|
||||||
this._load();
|
this._load();
|
||||||
|
|
||||||
function mapReq(item) {
|
if (!this.options.fullNode) {
|
||||||
return item.start(this.peers.block[i]);
|
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
|
req = items.map(function(item) {
|
||||||
red = this.redundancy;
|
return item.start(this.peers.load);
|
||||||
count = this.peers.block.length;
|
}, this);
|
||||||
split = Math.ceil(items.length * red / count);
|
|
||||||
for (i = 0, off = 0; i < count; i += red, off += split) {
|
this.peers.load.getData(req);
|
||||||
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) {
|
Pool.prototype.getTX = function getTX(hash, range, cb) {
|
||||||
@ -1056,13 +1097,8 @@ LoadRequest.prototype.start = function start(peer) {
|
|||||||
this.peer = peer;
|
this.peer = peer;
|
||||||
this.peer.once('close', this.onclose);
|
this.peer.once('close', this.onclose);
|
||||||
|
|
||||||
if (this.type === 'block')
|
|
||||||
reqType = 'filtered';
|
|
||||||
else if (this.type === 'tx')
|
|
||||||
reqType = 'tx';
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: reqType,
|
type: this.type,
|
||||||
hash: this.hash
|
hash: this.hash
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -5,14 +5,7 @@ var net = require('net');
|
|||||||
var path = require('path');
|
var path = require('path');
|
||||||
var bcoin = require('../');
|
var bcoin = require('../');
|
||||||
|
|
||||||
var addrs = [
|
var addrs = bcoin.protocol.network.seeds.slice();
|
||||||
'seed.bitcoin.sipa.be',
|
|
||||||
'dnsseed.bluematt.me',
|
|
||||||
'dnsseed.bitcoin.dashjr.org',
|
|
||||||
'seed.bitcoinstats.com',
|
|
||||||
'seed.bitnodes.io',
|
|
||||||
'bitseed.xf2.org'
|
|
||||||
];
|
|
||||||
|
|
||||||
var pool = bcoin.pool({
|
var pool = bcoin.pool({
|
||||||
size: 32,
|
size: 32,
|
||||||
@ -25,6 +18,8 @@ var pool = bcoin.pool({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
pool.on('error', function() {});
|
||||||
|
|
||||||
console.log('Updating bcoin preloaded chain...');
|
console.log('Updating bcoin preloaded chain...');
|
||||||
|
|
||||||
pool.on('block', function(block) {
|
pool.on('block', function(block) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user