198 lines
4.9 KiB
JavaScript
198 lines
4.9 KiB
JavaScript
var util = require('util');
|
|
var EventEmitter = require('events').EventEmitter;
|
|
|
|
var bcoin = require('../bcoin');
|
|
var constants = bcoin.protocol.constants;
|
|
var preload = bcoin.protocol.preload;
|
|
var utils = bcoin.utils;
|
|
|
|
function Chain(options) {
|
|
if (!(this instanceof Chain))
|
|
return new Chain(options);
|
|
|
|
EventEmitter.call(this);
|
|
|
|
this.options = options || {};
|
|
this.block = {
|
|
list: [],
|
|
bloom: new bcoin.bloom(8 * 1024 * 1024, 16, 0xdeadbeef)
|
|
};
|
|
this.orphan = {
|
|
map: {},
|
|
count: 0
|
|
};
|
|
this.index = {
|
|
bloom: new bcoin.bloom(28 * 1024 * 1024, 33, 0xdeadbee0),
|
|
hashes: preload.hashes.slice(),
|
|
ts: preload.ts.slice()
|
|
};
|
|
this.request = {
|
|
map: {},
|
|
count: 0
|
|
};
|
|
|
|
if (this.index.hashes.length === 0)
|
|
this.add(new bcoin.block(constants.genesis));
|
|
|
|
for (var i = 0; i < this.index.hashes.length; i++)
|
|
this.index.bloom.add(this.index.hashes[i], 'hex');
|
|
}
|
|
util.inherits(Chain, EventEmitter);
|
|
module.exports = Chain;
|
|
|
|
function compareTs(a, b) {
|
|
return a -b;
|
|
}
|
|
|
|
Chain.prototype.probeIndex = function probeIndex(hash, ts) {
|
|
if (!this.index.bloom.test(hash, 'hex'))
|
|
return false;
|
|
|
|
var start = 0;
|
|
var end = this.index.ts.length;
|
|
if (ts) {
|
|
start = utils.binaryInsert(this.index.ts, ts - 2 * 3600, compareTs, true);
|
|
start = Math.max(0, start - 1);
|
|
end = utils.binaryInsert(this.index.ts, ts + 2 * 3600, compareTs, true);
|
|
}
|
|
|
|
for (var i = start; i < end; i++)
|
|
if (this.index.hashes[i] === hash)
|
|
return true;
|
|
|
|
return false;
|
|
};
|
|
|
|
Chain.prototype.addIndex = function addIndex(hash, ts) {
|
|
if (this.probeIndex(hash, ts))
|
|
return;
|
|
|
|
var pos = utils.binaryInsert(this.index.ts, ts, compareTs);
|
|
this.index.hashes.splice(pos, 0, hash);
|
|
this.index.bloom.add(hash, 'hex');
|
|
};
|
|
|
|
Chain.prototype.getRange = function getRange(ts) {
|
|
var start = utils.binaryInsert(this.index.ts, ts - 2 * 3600, compareTs, true);
|
|
var end = utils.binaryInsert(this.index.ts, ts + 2 * 3600, compareTs, true);
|
|
|
|
if (start > 0)
|
|
start--;
|
|
if (end > 0)
|
|
end--;
|
|
|
|
return { start: this.index.hashes[start], end: this.index.hashes[end] };
|
|
};
|
|
|
|
Chain.prototype.add = function add(block) {
|
|
var res = false;
|
|
var initial = block;
|
|
do {
|
|
// No need to revalidate orphans
|
|
if (!res && !block.verify())
|
|
break;
|
|
|
|
var hash = block.hash('hex');
|
|
var prev = block.prevBlock;
|
|
|
|
// If previous block wasn't ever seen - add current to orphans
|
|
if (this.orphan.map[prev])
|
|
break;
|
|
if (!this.probeIndex(hash, block.ts) && !this.probeIndex(prev, block.ts)) {
|
|
this.orphan.count++;
|
|
this.orphan.map[prev] = block;
|
|
|
|
// Add block to bloom filter, as we now have it in memory
|
|
this.block.bloom.add(hash, 'hex');
|
|
this.emit('missing', prev, this.getRange(block.ts));
|
|
break;
|
|
}
|
|
|
|
// Validated known block at this point - add it to index
|
|
this.addIndex(hash, block.ts);
|
|
|
|
// Fullfill request
|
|
if (this.request.map[hash]) {
|
|
var req = this.request.map[hash];
|
|
delete this.request.map[hash];
|
|
this.request.count--;
|
|
req.forEach(function(cb) {
|
|
cb(block);
|
|
});
|
|
}
|
|
|
|
// At least one block was added
|
|
res = true;
|
|
this.block.list.push(block);
|
|
|
|
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.map[hash];
|
|
this.orphan.count--;
|
|
} while (true);
|
|
|
|
// Compress old blocks
|
|
this._compress();
|
|
|
|
return res;
|
|
};
|
|
|
|
Chain.prototype._compress = function compress() {
|
|
// Keep at least 1000 blocks and at most 2000
|
|
if (this.block.list.length < 2000)
|
|
return;
|
|
|
|
// Bloom filter rebuilt is needed
|
|
this.block.list = this.block.list.slice(-1000);
|
|
this.block.bloom.reset();
|
|
|
|
for (var i = 0; i < this.block.list.length; i++)
|
|
this.block.bloom.add(this.block.list[i].hash());
|
|
};
|
|
|
|
Chain.prototype.has = function has(hash) {
|
|
return this.probeIndex(hash) || !!this.orphan.map[hash];
|
|
};
|
|
|
|
Chain.prototype.get = function get(hash, cb) {
|
|
// Cached block found
|
|
if (this.block.bloom.test(hash, 'hex')) {
|
|
for (var i = 0; i < this.block.list.length; i++)
|
|
if (this.block.list[i].hash('hex') === hash)
|
|
return cb(this.block.list[i]);
|
|
assert(false);
|
|
}
|
|
|
|
if (this.request.map[hash]) {
|
|
this.request.map[hash].push(cb);
|
|
} else {
|
|
this.request.map[hash] = [ cb ];
|
|
this.request.count++;
|
|
this.emit('missing', hash);
|
|
}
|
|
};
|
|
|
|
Chain.prototype.isFull = function isFull() {
|
|
// < 10m since last block
|
|
return !this.request.count &&
|
|
(+new Date / 1000) - this.index.ts[this.index.ts.length - 1] < 10 * 60;
|
|
};
|
|
|
|
Chain.prototype.covers = function covers(ts) {
|
|
return ts >= this.index.ts[0];
|
|
};
|
|
|
|
Chain.prototype.hashesFrom = function hashesFrom(ts, index) {
|
|
var pos = utils.binaryInsert(this.index.ts, ts - 2 * 3600, compareTs, true);
|
|
if (pos > 0)
|
|
pos--;
|
|
return this.index.hashes.slice(pos);
|
|
};
|
|
|
|
Chain.prototype.getLast = function getLast() {
|
|
return this.index.hashes[this.index.hashes.length - 1];
|
|
};
|