chain: preload and get

This commit is contained in:
Fedor Indutny 2014-05-02 14:50:21 +04:00
parent 9e0808eb39
commit c3239b7f36
6 changed files with 14082 additions and 64 deletions

View File

@ -1,23 +1,37 @@
var util = require('util');
var EventEmitter = require('events').EventEmitter;
var bcoin = require('../bcoin'); var bcoin = require('../bcoin');
var constants = bcoin.protocol.constants; var constants = bcoin.protocol.constants;
var preload = bcoin.protocol.preload;
var utils = bcoin.utils; var utils = bcoin.utils;
function Chain(options) { function Chain(options) {
if (!(this instanceof Chain)) if (!(this instanceof Chain))
return new Chain(options); return new Chain(options);
EventEmitter.call(this);
this.options = options || {}; this.options = options || {};
this.blocks = []; this.blocks = [];
this.hashes = []; this.hashes = preload.hashes.slice();
this.ts = []; this.ts = preload.ts.slice();
this.orphan = { this.orphan = {
map: {}, map: {},
count: 0 count: 0
}; };
this.requests = {};
this.bloom = new bcoin.bloom(28 * 1024 * 1024, 33, 0xdeadbeef); this.bloom = new bcoin.bloom(28 * 1024 * 1024, 33, 0xdeadbeef);
this.last = null;
this.add(new bcoin.block(constants.genesis)); if (this.hashes.length === 0) {
this.add(new bcoin.block(constants.genesis));
} else {
// Add all preloaded hashes to the bloom filter
for (var i = 0; i < this.hashes.length; i++)
this.bloom.add(utils.toArray(this.hashes[i], 'hex'));
}
} }
util.inherits(Chain, EventEmitter);
module.exports = Chain; module.exports = Chain;
Chain.prototype.add = function add(block) { Chain.prototype.add = function add(block) {
@ -29,24 +43,45 @@ Chain.prototype.add = function add(block) {
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) {
return a - b;
}, true);
var last = (this.hashes.length && pos > 0) ? this.hashes[pos - 1] : null;
// Add orphan // Add orphan
if (this.last && prev !== this.last) { if (last && prev !== last) {
if (!this.bloom.test(rhash) && !this.orphan.map[prev]) { if (!this.bloom.test(rhash) && !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);
this.emit('missing', prev);
} }
break; break;
} }
var hash = block.hash('hex');
this.bloom.add(rhash); this.bloom.add(rhash);
// Sorted insert
var pos = utils.binaryInsert(this.ts, block.ts, function(a, b) {
return a - b;
}, true);
var hash = block.hash('hex');
this.ts.splice(pos, 0, block.ts);
this.hashes.splice(pos, 0, hash);
// Blocks is a FIFO queue, acting like a cache for the requests
this.blocks.push(block); this.blocks.push(block);
this.hashes.push(hash);
this.ts.push(block.ts); // Fullfill requests
this.last = hash; if (this.requests[hash]) {
var req = this.requests[hash];
delete this.requests[hash];
req.forEach(function(cb) {
cb(block);
});
}
res = true; res = true;
// Compress old blocks // Compress old blocks
@ -75,9 +110,32 @@ Chain.prototype._compress = function compress() {
}; };
Chain.prototype.getLast = function getLast() { Chain.prototype.getLast = function getLast() {
return this.blocks[this.blocks.length - 1]; return {
hash: this.hashes[this.hashes.length - 1],
ts: this.ts[this.ts.length - 1]
};
}; };
Chain.prototype.has = function has(hash) { Chain.prototype.has = function has(hash) {
return this.bloom.test(hash); return this.bloom.test(hash) && !this.requests[utils.toHex(hash)];
};
Chain.prototype.get = function get(hash, cb) {
if (Array.isArray(hash))
hash = utils.toHex(hash);
for (var i = 0; i < this.blocks.length; i++)
if (this.blocks[i].hash('hex') === hash)
return cb(this.blocks[i]);
this.emit('missing', hash);
if (this.requests[hash])
this.requests[hash].push(cb);
else
this.requests[hash] = [ cb ];
};
Chain.prototype.isFull = function isFull() {
// < 10m since last block
return (+new Date / 1000) - this.ts[this.ts.length - 1] < 10 * 60;
}; };

View File

@ -1,4 +1,7 @@
var assert = require('assert'); var assert = require('assert');
var util = require('util');
var EventEmitter = require('events').EventEmitter;
var bcoin = require('../bcoin'); var bcoin = require('../bcoin');
var utils = bcoin.utils; var utils = bcoin.utils;
@ -6,11 +9,13 @@ function Pool(options) {
if (!(this instanceof Pool)) if (!(this instanceof Pool))
return new Pool(options); return new Pool(options);
EventEmitter.call(this);
this.options = options || {}; this.options = options || {};
this.size = options.size || 64; this.size = options.size || 2;
this.parallel = options.parallel || 8000; this.parallel = options.parallel || 1000;
this.load = { this.load = {
timeout: options.loadTimeout || 5000, timeout: options.loadTimeout || 10000,
window: options.loadWindow || 2500, window: options.loadWindow || 2500,
timer: null, timer: null,
lwm: options.lwm || this.parallel * 2, lwm: options.lwm || this.parallel * 2,
@ -33,41 +38,33 @@ function Pool(options) {
}; };
this.block = { this.block = {
lastSeen: null, lastSeen: null,
lastTs: null,
queue: [], queue: [],
active: 0, active: 0,
requests: {} requests: {}
}; };
this.finished = 0; this.searches = [];
this.createConnection = options.createConnection; this.createConnection = options.createConnection;
assert(this.createConnection); assert(this.createConnection);
this._init(); this._init();
var self = this;
setInterval(function() {
console.log('clen %d ocnt %d active %d queue %d reqs %d mem %d d %d',
self.chain.ts.length, self.chain.orphan.count,
self.block.active,
self.block.queue.length,
Object.keys(self.block.requests).length,
process.memoryUsage().heapUsed,
self.finished - self.chain.ts.length - self.chain.orphan.count);
}, 5000);
process.on('SIGUSR2', function() {
require('fs').writeFileSync('/tmp/1.json', JSON.stringify({
hashes: self.chain.hashes,
ts: self.chain.ts
}));
});
} }
util.inherits(Pool, EventEmitter);
module.exports = Pool; module.exports = Pool;
Pool.prototype._init = function _init() { Pool.prototype._init = function _init() {
this._addLoader(); this._addLoader();
for (var i = 0; i < this.size; i++) for (var i = 0; i < this.size; i++)
this._addPeer(); this._addPeer();
var self = this;
this.chain.on('missing', function(hash) {
self._requestBlock(hash);
self._scheduleRequests();
if (self.peers.load === null)
self._addLoader();
});
}; };
Pool.prototype._addLoader = function _addLoader() { Pool.prototype._addLoader = function _addLoader() {
@ -81,11 +78,12 @@ Pool.prototype._addLoader = function _addLoader() {
// Just ignore, it will result in `close` anyway // Just ignore, it will result in `close` anyway
}); });
peer.once('close', function() { peer.once('close', onclose);
function onclose() {
clearTimeout(timer); clearTimeout(timer);
self._removePeer(peer); self._removePeer(peer);
self._addLoader(); self._addLoader();
}); };
peer.once('ack', function() { peer.once('ack', function() {
peer.updateWatch(); peer.updateWatch();
@ -94,6 +92,13 @@ Pool.prototype._addLoader = function _addLoader() {
}); });
function destroy() { function destroy() {
// Chain is full and up-to-date
if (self.chain.isFull()) {
clearTimeout(timer);
peer.removeListener('close', onclose);
self._removePeer(peer);
}
self.block.lastSeen = null; self.block.lastSeen = null;
peer.destroy(); peer.destroy();
} }
@ -134,9 +139,8 @@ Pool.prototype._load = function load() {
if (this.block.lastSeen) if (this.block.lastSeen)
hash = this.block.lastSeen; hash = this.block.lastSeen;
else else
hash = this.chain.getLast().hash(); hash = this.chain.getLast().hash;
console.log('Loading from: ' + utils.toHex(hash.slice().reverse()));
this.peers.load.loadBlocks(hash); this.peers.load.loadBlocks(hash);
return true; return true;
@ -189,14 +193,13 @@ Pool.prototype._addPeer = function _addPeer() {
return; return;
var req = self.block.requests[utils.toHex(item.hash)]; var req = self.block.requests[utils.toHex(item.hash)];
console.log('notfound', !!req);
if (req) if (req)
req.finish(null); req.finish(null);
}); });
}); });
peer.on('tx', function(tx) { peer.on('tx', function(tx) {
console.log('got tx', tx.hash('hex')); self.emit('tx', tx);
}); });
}; };
@ -214,13 +217,30 @@ Pool.prototype._removePeer = function _removePeer(peer) {
}; };
Pool.prototype.watch = function watch(id) { Pool.prototype.watch = function watch(id) {
if (typeof id === 'string')
id = utils.toArray(id, 'hex');
if (id) if (id)
this.bloom.add(id); this.bloom.add(id);
for (var i = 0; i < this.peers.block.length; i++) for (var i = 0; i < this.peers.block.length; i++)
this.peers.block[i].updateWatch(); this.peers.block[i].updateWatch();
}; };
Pool.prototype.search = function search(id, limit) {
if (typeof id === 'string')
id = utils.toArray(id, 'hex');
this.watch(id);
this.searches.push({
id: id,
// Last 30 days by default
limit: limit || (+new Date / 1000 - 2592000)
});
};
Pool.prototype._requestBlock = function _requestBlock(hash) { Pool.prototype._requestBlock = function _requestBlock(hash) {
if (typeof hash === 'string')
hash = utils.toArray(hash, 'hex');
// Block is already in chain, or being requested // Block is already in chain, or being requested
if (this.chain.has(hash)) if (this.chain.has(hash))
return; return;
@ -295,28 +315,6 @@ function BlockRequest(pool, hash) {
}; };
} }
function binaryInsert(list, item, compare) {
var start = 0,
end = list.length;
while (start < end) {
var pos = (start + end) >> 1;
var cmp = compare(item, list[pos]);
if (cmp === 0) {
start = pos;
end = pos;
break;
} else if (cmp < 0) {
end = pos;
} else {
start = pos + 1;
}
}
list.splice(start, 0, item);
}
BlockRequest.prototype.start = function start(peer) { BlockRequest.prototype.start = function start(peer) {
var self = this; var self = this;
assert(!this.active); assert(!this.active);
@ -356,7 +354,7 @@ BlockRequest.prototype.clear = function clear() {
BlockRequest.prototype.retry = function retry() { BlockRequest.prototype.retry = function retry() {
// Put block into the queue, ensure that the queue is always sorted by ts // Put block into the queue, ensure that the queue is always sorted by ts
binaryInsert(this.pool.block.queue, this, BlockRequest.compare); utils.binaryInsert(this.pool.block.queue, this, BlockRequest.compare);
var peer = this.peer; var peer = this.peer;
this.clear(); this.clear();

View File

@ -190,7 +190,10 @@ Framer.prototype.getBlocks = function getBlocks(hashes, stop) {
p.length = off + 32 * (hashes.length + 1); p.length = off + 32 * (hashes.length + 1);
for (var i = 0; i < hashes.length; i++) { for (var i = 0; i < hashes.length; i++) {
var len = utils.copy(hashes[i], p, off); var hash = hashes[i];
if (typeof hash === 'string')
hash = utils.toArray(hash, 'hex');
var len = utils.copy(hash, p, off);
for (; len < 32; len++) for (; len < 32; len++)
p[off + len] = 0; p[off + len] = 0;
off += len; off += len;

View File

@ -3,3 +3,4 @@ var protocol = exports;
protocol.constants = require('./constants'); protocol.constants = require('./constants');
protocol.framer = require('./framer'); protocol.framer = require('./framer');
protocol.parser = require('./parser'); protocol.parser = require('./parser');
protocol.preload = require('./preload');

13933
lib/bcoin/protocol/preload.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -198,3 +198,28 @@ function toHex(msg) {
return res; return res;
} }
utils.toHex = toHex; utils.toHex = toHex;
function binaryInsert(list, item, compare, search) {
var start = 0,
end = list.length;
while (start < end) {
var pos = (start + end) >> 1;
var cmp = compare(item, list[pos]);
if (cmp === 0) {
start = pos;
end = pos;
break;
} else if (cmp < 0) {
end = pos;
} else {
start = pos + 1;
}
}
if (!search)
list.splice(start, 0, item);
return start;
}
utils.binaryInsert = binaryInsert;