pool: save progress
This commit is contained in:
parent
b3c6eb06bc
commit
0eecb94b3f
@ -14,13 +14,17 @@ function Chain(options) {
|
|||||||
|
|
||||||
this.options = options || {};
|
this.options = options || {};
|
||||||
this.blocks = [];
|
this.blocks = [];
|
||||||
|
this.fifo = [];
|
||||||
this.hashes = preload.hashes.slice();
|
this.hashes = preload.hashes.slice();
|
||||||
this.ts = preload.ts.slice();
|
this.ts = preload.ts.slice();
|
||||||
this.orphan = {
|
this.orphan = {
|
||||||
map: {},
|
map: {},
|
||||||
count: 0
|
count: 0
|
||||||
};
|
};
|
||||||
this.requests = {};
|
this.requests = {
|
||||||
|
map: {},
|
||||||
|
count: 0
|
||||||
|
};
|
||||||
this.bloom = new bcoin.bloom(28 * 1024 * 1024, 33, 0xdeadbeef);
|
this.bloom = new bcoin.bloom(28 * 1024 * 1024, 33, 0xdeadbeef);
|
||||||
|
|
||||||
if (this.hashes.length === 0) {
|
if (this.hashes.length === 0) {
|
||||||
@ -36,21 +40,33 @@ module.exports = Chain;
|
|||||||
|
|
||||||
Chain.prototype.add = function add(block) {
|
Chain.prototype.add = function add(block) {
|
||||||
var res = false;
|
var res = false;
|
||||||
|
var initial = block;
|
||||||
do {
|
do {
|
||||||
// No need to revalidate orphans
|
// No need to revalidate orphans
|
||||||
if (!res && !block.verify())
|
if (!res && !block.verify())
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
var hash = block.hash('hex');
|
||||||
var rhash = block.hash();
|
var rhash = block.hash();
|
||||||
var prev = block.prevBlock;
|
var prev = block.prevBlock;
|
||||||
var pos = utils.binaryInsert(this.ts, block.ts, function(a, b) {
|
|
||||||
|
// Validity period is two hours
|
||||||
|
var pos = utils.binaryInsert(this.ts, block.ts - 2 * 3600, function(a, b) {
|
||||||
return a - b;
|
return a - b;
|
||||||
}, true);
|
}, true);
|
||||||
var last = (this.hashes.length && pos > 0) ? this.hashes[pos - 1] : null;
|
var test = this.bloom.test(rhash) &&
|
||||||
|
prev !== initial &&
|
||||||
|
!this.orphan.map[prev];
|
||||||
|
|
||||||
// Add orphan
|
var match = this.hashes.length === 0;
|
||||||
if (last && prev !== last && !this.bloom.test(rhash)) {
|
for (pos--; !match && 0 <= 0 && pos < this.hashes.length; pos++)
|
||||||
if (!this.bloom.test(rhash) && !this.orphan.map[prev]) {
|
match = this.hashes[pos] === prev;
|
||||||
|
|
||||||
|
// If last hash at ts matches prev hash or we already know this block -
|
||||||
|
// add it to either list or FIFO
|
||||||
|
if (!match && !test) {
|
||||||
|
// Add orphan
|
||||||
|
if (!this.orphan.map[prev]) {
|
||||||
this.orphan.count++;
|
this.orphan.count++;
|
||||||
this.orphan.map[prev] = block;
|
this.orphan.map[prev] = block;
|
||||||
this.bloom.add(rhash);
|
this.bloom.add(rhash);
|
||||||
@ -59,8 +75,6 @@ Chain.prototype.add = function add(block) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
var hash = block.hash('hex');
|
|
||||||
|
|
||||||
// It may be a re-requested block
|
// It may be a re-requested block
|
||||||
if (!this.bloom.test(rhash)) {
|
if (!this.bloom.test(rhash)) {
|
||||||
// Sorted insert
|
// Sorted insert
|
||||||
@ -73,13 +87,24 @@ Chain.prototype.add = function add(block) {
|
|||||||
this.bloom.add(rhash);
|
this.bloom.add(rhash);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Blocks is a FIFO queue, acting like a cache for the requests
|
// Some old block for caching purposes, should be a FIFO
|
||||||
this.blocks.push(block);
|
if (!this.covers(block.ts)) {
|
||||||
|
this.fifo.push(block);
|
||||||
|
|
||||||
|
// A new block
|
||||||
|
} else {
|
||||||
|
// Insert block into a cache set if it isn't already there
|
||||||
|
var pos = utils.binaryInsert(this.blocks, block, function(a, b) {
|
||||||
|
return a.ts - b.ts;
|
||||||
|
}, true);
|
||||||
|
this.blocks.splice(pos, 0, block);
|
||||||
|
}
|
||||||
|
|
||||||
// Fullfill requests
|
// Fullfill requests
|
||||||
if (this.requests[hash]) {
|
if (this.requests.map[hash]) {
|
||||||
var req = this.requests[hash];
|
var req = this.requests.map[hash];
|
||||||
delete this.requests[hash];
|
delete this.requests.map[hash];
|
||||||
|
this.requests.count--;
|
||||||
req.forEach(function(cb) {
|
req.forEach(function(cb) {
|
||||||
cb(block);
|
cb(block);
|
||||||
});
|
});
|
||||||
@ -105,11 +130,11 @@ Chain.prototype.add = function add(block) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Chain.prototype._compress = function compress() {
|
Chain.prototype._compress = function compress() {
|
||||||
// Store only last 1000 blocks, others will be requested if needed
|
// Store only last 1000 blocks and 1000 FIFO
|
||||||
if (this.blocks.length < 1000)
|
if (this.blocks.length > 1000)
|
||||||
return;
|
this.blocks = this.blocks.slice(-1000);
|
||||||
|
if (this.fifo.length > 1000)
|
||||||
this.blocks = this.blocks.slice(this.blocks.length - 1000);
|
this.fifo = this.fifo.slice(-1000);
|
||||||
};
|
};
|
||||||
|
|
||||||
Chain.prototype.getLast = function getLast() {
|
Chain.prototype.getLast = function getLast() {
|
||||||
@ -119,10 +144,20 @@ Chain.prototype.getLast = function getLast() {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
Chain.prototype.has = function has(hash) {
|
Chain.prototype.has = function has(hash, cacheonly) {
|
||||||
if (!Array.isArray(hash))
|
if (!Array.isArray(hash))
|
||||||
hash = utils.toArray(hash, 'hex');
|
hash = utils.toArray(hash, 'hex');
|
||||||
return this.bloom.test(hash) && !this.requests[utils.toHex(hash)];
|
if (!cacheonly)
|
||||||
|
return this.bloom.test(hash) && !this.requests.map[utils.toHex(hash)];
|
||||||
|
|
||||||
|
for (var i = 0; i < this.blocks.length; i++)
|
||||||
|
if (this.blocks[i].hash('hex') === hash)
|
||||||
|
return true;
|
||||||
|
for (var i = 0; i < this.fifo.length; i++)
|
||||||
|
if (this.fifo[i].hash('hex') === hash)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
Chain.prototype.get = function get(hash, cb) {
|
Chain.prototype.get = function get(hash, cb) {
|
||||||
@ -132,15 +167,32 @@ Chain.prototype.get = function get(hash, cb) {
|
|||||||
for (var i = 0; i < this.blocks.length; i++)
|
for (var i = 0; i < this.blocks.length; i++)
|
||||||
if (this.blocks[i].hash('hex') === hash)
|
if (this.blocks[i].hash('hex') === hash)
|
||||||
return cb(this.blocks[i]);
|
return cb(this.blocks[i]);
|
||||||
|
for (var i = 0; i < this.fifo.length; i++)
|
||||||
|
if (this.fifo[i].hash('hex') === hash)
|
||||||
|
return cb(this.fifo[i]);
|
||||||
|
|
||||||
if (this.requests[hash])
|
if (this.requests.map[hash]) {
|
||||||
this.requests[hash].push(cb);
|
this.requests.map[hash].push(cb);
|
||||||
else
|
this.requests.count++;
|
||||||
this.requests[hash] = [ cb ];
|
} else {
|
||||||
|
this.requests.map[hash] = [ cb ];
|
||||||
|
}
|
||||||
this.emit('missing', hash);
|
this.emit('missing', hash);
|
||||||
};
|
};
|
||||||
|
|
||||||
Chain.prototype.isFull = function isFull() {
|
Chain.prototype.isFull = function isFull() {
|
||||||
// < 10m since last block
|
// < 10m since last block
|
||||||
return (+new Date / 1000) - this.ts[this.ts.length - 1] < 10 * 60;
|
return !this.requests.count &&
|
||||||
|
(+new Date / 1000) - this.ts[this.ts.length - 1] < 10 * 60;
|
||||||
|
};
|
||||||
|
|
||||||
|
Chain.prototype.covers = function covers(ts) {
|
||||||
|
return this.ts.length && this.ts[0] <= ts;
|
||||||
|
};
|
||||||
|
|
||||||
|
Chain.prototype.hashesFrom = function hashesFrom(ts) {
|
||||||
|
var pos = utils.binaryInsert(this.ts, ts, function(a, b) {
|
||||||
|
return a - b;
|
||||||
|
}, true);
|
||||||
|
return this.hashes.slice(pos);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -12,7 +12,7 @@ function Pool(options) {
|
|||||||
EventEmitter.call(this);
|
EventEmitter.call(this);
|
||||||
|
|
||||||
this.options = options || {};
|
this.options = options || {};
|
||||||
this.size = options.size || 2;
|
this.size = options.size || 4;
|
||||||
this.parallel = options.parallel || 1000;
|
this.parallel = options.parallel || 1000;
|
||||||
this.load = {
|
this.load = {
|
||||||
timeout: options.loadTimeout || 10000,
|
timeout: options.loadTimeout || 10000,
|
||||||
@ -37,13 +37,12 @@ function Pool(options) {
|
|||||||
load: null
|
load: null
|
||||||
};
|
};
|
||||||
this.block = {
|
this.block = {
|
||||||
lastSeen: null,
|
nextLoad: [],
|
||||||
lastTs: null,
|
loadTs: Infinity,
|
||||||
queue: [],
|
queue: [],
|
||||||
active: 0,
|
active: 0,
|
||||||
requests: {}
|
requests: {}
|
||||||
};
|
};
|
||||||
this.searches = [];
|
|
||||||
|
|
||||||
this.createConnection = options.createConnection;
|
this.createConnection = options.createConnection;
|
||||||
assert(this.createConnection);
|
assert(this.createConnection);
|
||||||
@ -62,17 +61,25 @@ Pool.prototype._init = function _init() {
|
|||||||
this.chain.on('missing', function(hash) {
|
this.chain.on('missing', function(hash) {
|
||||||
self._requestBlock(hash);
|
self._requestBlock(hash);
|
||||||
self._scheduleRequests();
|
self._scheduleRequests();
|
||||||
if (self.peers.load === null)
|
self._load();
|
||||||
self._addLoader();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
setInterval(function() {
|
||||||
|
console.log(self.chain.fifo.length, self.chain.blocks.length, self.chain.orphan.count, self.chain.hashes.length, self.chain.ts.length);
|
||||||
|
}, 5000);
|
||||||
};
|
};
|
||||||
|
|
||||||
Pool.prototype._addLoader = function _addLoader() {
|
Pool.prototype._addLoader = function _addLoader() {
|
||||||
assert(this.peers.load === null);
|
if (this.peers.load !== null)
|
||||||
|
return;
|
||||||
var socket = this.createConnection();
|
var socket = this.createConnection();
|
||||||
var peer = bcoin.peer(this, socket, this.options.peer);
|
var peer = bcoin.peer(this, socket, this.options.peer);
|
||||||
this.peers.load = peer;
|
this.peers.load = peer;
|
||||||
|
|
||||||
|
// Queue block's hash for search
|
||||||
|
if (this.block.loadTs < Infinity)
|
||||||
|
this.block.nextLoad.push(this.chain.hashesFrom(this.block.loadTs)[0]);
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
peer.once('error', function() {
|
peer.once('error', function() {
|
||||||
// Just ignore, it will result in `close` anyway
|
// Just ignore, it will result in `close` anyway
|
||||||
@ -93,13 +100,12 @@ Pool.prototype._addLoader = function _addLoader() {
|
|||||||
|
|
||||||
function destroy() {
|
function destroy() {
|
||||||
// Chain is full and up-to-date
|
// Chain is full and up-to-date
|
||||||
if (self.chain.isFull()) {
|
if (self.block.nextLoad.length === 0 && self.chain.isFull()) {
|
||||||
clearTimeout(timer);
|
clearTimeout(timer);
|
||||||
peer.removeListener('close', onclose);
|
peer.removeListener('close', onclose);
|
||||||
self._removePeer(peer);
|
self._removePeer(peer);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.block.lastSeen = null;
|
|
||||||
peer.destroy();
|
peer.destroy();
|
||||||
}
|
}
|
||||||
var timer = setTimeout(destroy, this.load.timeout);
|
var timer = setTimeout(destroy, this.load.timeout);
|
||||||
@ -107,7 +113,8 @@ Pool.prototype._addLoader = function _addLoader() {
|
|||||||
// 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) {
|
||||||
if (hashes.length === 0) {
|
if (hashes.length === 0) {
|
||||||
self.block.lastSeen = null;
|
// Reset search loads
|
||||||
|
self.block.loadTs = Infinity;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,7 +125,7 @@ Pool.prototype._addLoader = function _addLoader() {
|
|||||||
|
|
||||||
self._scheduleRequests();
|
self._scheduleRequests();
|
||||||
|
|
||||||
self.block.lastSeen = hashes[hashes.length - 1];
|
self.block.nextLoad.push(hashes[hashes.length - 1]);
|
||||||
|
|
||||||
clearTimeout(timer);
|
clearTimeout(timer);
|
||||||
// Reinstantiate timeout
|
// Reinstantiate timeout
|
||||||
@ -127,7 +134,13 @@ Pool.prototype._addLoader = function _addLoader() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
Pool.prototype._load = function load() {
|
Pool.prototype._load = function load(from) {
|
||||||
|
// Requested load of earlier block ids
|
||||||
|
if (from !== undefined && this.block.loadTs > from) {
|
||||||
|
this.block.loadTs = from;
|
||||||
|
this.block.nextLoad.push(this.chain.hashesFrom(from)[0]);
|
||||||
|
}
|
||||||
|
|
||||||
if (this.block.queue.length >= this.load.hwm) {
|
if (this.block.queue.length >= this.load.hwm) {
|
||||||
this.load.hiReached = true;
|
this.load.hiReached = true;
|
||||||
return false;
|
return false;
|
||||||
@ -136,11 +149,14 @@ Pool.prototype._load = function load() {
|
|||||||
|
|
||||||
// Load more blocks, starting from last hash
|
// Load more blocks, starting from last hash
|
||||||
var hash;
|
var hash;
|
||||||
if (this.block.lastSeen)
|
if (this.block.nextLoad.length) {
|
||||||
hash = this.block.lastSeen;
|
hash = this.block.nextLoad.shift();
|
||||||
else
|
} else {
|
||||||
hash = this.chain.getLast().hash;
|
hash = this.chain.getLast().hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.peers.load)
|
||||||
|
this._addLoader();
|
||||||
this.peers.load.loadBlocks(hash);
|
this.peers.load.loadBlocks(hash);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -225,24 +241,41 @@ Pool.prototype.watch = function watch(id) {
|
|||||||
this.peers.block[i].updateWatch();
|
this.peers.block[i].updateWatch();
|
||||||
};
|
};
|
||||||
|
|
||||||
Pool.prototype.search = function search(id, limit) {
|
Pool.prototype.search = function search(id, limit, cb) {
|
||||||
if (typeof id === 'string')
|
if (typeof id === 'string')
|
||||||
id = utils.toArray(id, 'hex');
|
id = utils.toArray(id, 'hex');
|
||||||
this.watch(id);
|
|
||||||
this.searches.push({
|
|
||||||
id: id,
|
|
||||||
|
|
||||||
// Last 30 days by default
|
// Optional limit argument
|
||||||
limit: limit || (+new Date / 1000 - 2592000)
|
if (typeof limit === 'function') {
|
||||||
});
|
cb = limit;
|
||||||
|
limit = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Last 5 days by default, this covers 1000 blocks that we have in the
|
||||||
|
// chain by default
|
||||||
|
if (!limit)
|
||||||
|
limit = (+new Date / 1000 - 432000);
|
||||||
|
|
||||||
|
this.watch(id);
|
||||||
|
|
||||||
|
if (!this.chain.covers(limit)) {
|
||||||
|
// Load some block hashes first
|
||||||
|
this._load(limit);
|
||||||
|
} else {
|
||||||
|
// Load the blocks themselves!
|
||||||
|
this.chain.hashesFrom(limit).forEach(function(hash) {
|
||||||
|
this._requestBlock(hash, true);
|
||||||
|
}, this);
|
||||||
|
this._scheduleRequests();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Pool.prototype._requestBlock = function _requestBlock(hash) {
|
Pool.prototype._requestBlock = function _requestBlock(hash, force) {
|
||||||
if (typeof hash === 'string')
|
if (typeof hash === 'string')
|
||||||
hash = utils.toArray(hash, 'hex');
|
hash = utils.toArray(hash, 'hex');
|
||||||
|
|
||||||
// Block is already in chain, or being requested
|
// Block should be not in chain, or be requested
|
||||||
if (this.chain.has(hash))
|
if (this.chain.has(hash, force))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var hex = utils.toHex(hash);
|
var hex = utils.toHex(hash);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user