new chaindb. allow for real fork resolution.
This commit is contained in:
parent
6de9e54667
commit
04c5f94997
1651
lib/bcoin/chain.js
1651
lib/bcoin/chain.js
File diff suppressed because it is too large
Load Diff
@ -18,7 +18,7 @@ var fs = bcoin.fs;
|
||||
* ChainBlock
|
||||
*/
|
||||
|
||||
function ChainBlock(chain, data) {
|
||||
function ChainBlock(chain, data, prev) {
|
||||
if (!(this instanceof ChainBlock))
|
||||
return new ChainBlock(chain, data);
|
||||
|
||||
@ -31,18 +31,14 @@ function ChainBlock(chain, data) {
|
||||
this.bits = data.bits;
|
||||
this.nonce = data.nonce;
|
||||
this.height = data.height;
|
||||
this.chainwork = data.chainwork || this.getChainwork();
|
||||
this.chainwork = data.chainwork || this.getChainwork(prev);
|
||||
|
||||
assert(this.chainwork);
|
||||
|
||||
this.previous = [];
|
||||
}
|
||||
|
||||
ChainBlock.BLOCK_SIZE = 112;
|
||||
|
||||
ChainBlock.prototype.__defineGetter__('prev', function() {
|
||||
return this.chain.db.getSync(this.height - 1);
|
||||
});
|
||||
|
||||
ChainBlock.prototype.__defineGetter__('next', function() {
|
||||
return this.chain.db.getSync(this.height + 1);
|
||||
});
|
||||
ChainBlock.BLOCK_SIZE = 116;
|
||||
|
||||
ChainBlock.prototype.getProof = function getProof() {
|
||||
var target = utils.fromCompact(this.bits);
|
||||
@ -51,16 +47,12 @@ ChainBlock.prototype.getProof = function getProof() {
|
||||
return new bn(1).ushln(256).div(target.addn(1));
|
||||
};
|
||||
|
||||
ChainBlock.prototype.getChainwork = function getChainwork() {
|
||||
var prev;
|
||||
|
||||
ChainBlock.prototype.getChainwork = function getChainwork(prev) {
|
||||
// Workaround for genesis block
|
||||
// being added _in_ chaindb.
|
||||
if (!this.chain.db)
|
||||
return this.getProof();
|
||||
|
||||
prev = this.prev;
|
||||
|
||||
return (prev ? prev.chainwork : new bn(0)).add(this.getProof());
|
||||
};
|
||||
|
||||
@ -68,43 +60,6 @@ ChainBlock.prototype.isGenesis = function isGenesis() {
|
||||
return this.hash === network.genesis.hash;
|
||||
};
|
||||
|
||||
ChainBlock.prototype.getMedianTime = function getMedianTime() {
|
||||
var entry = this;
|
||||
var median = [];
|
||||
var timeSpan = constants.block.medianTimespan;
|
||||
var i;
|
||||
|
||||
for (i = 0; i < timeSpan && entry; i++, entry = entry.prev)
|
||||
median.push(entry.ts);
|
||||
|
||||
median = median.sort();
|
||||
|
||||
return median[median.length / 2 | 0];
|
||||
};
|
||||
|
||||
ChainBlock.prototype.isOutdated = function isOutdated(version) {
|
||||
return this.isSuperMajority(version, network.block.majorityRejectOutdated);
|
||||
};
|
||||
|
||||
ChainBlock.prototype.isUpgraded = function isUpgraded(version) {
|
||||
return this.isSuperMajority(version, network.block.majorityEnforceUpgrade);
|
||||
};
|
||||
|
||||
ChainBlock.prototype.isSuperMajority = function isSuperMajority(version, required) {
|
||||
var entry = this;
|
||||
var found = 0;
|
||||
var majorityWindow = network.block.majorityWindow;
|
||||
var i;
|
||||
|
||||
for (i = 0; i < majorityWindow && found < required && entry; i++) {
|
||||
if (entry.version >= version)
|
||||
found++;
|
||||
entry = entry.prev;
|
||||
}
|
||||
|
||||
return found >= required;
|
||||
};
|
||||
|
||||
ChainBlock.prototype.toJSON = function toJSON() {
|
||||
return {
|
||||
hash: this.hash,
|
||||
@ -131,14 +86,14 @@ ChainBlock.prototype.toRaw = function toRaw() {
|
||||
utils.writeU32(res, this.ts, 68);
|
||||
utils.writeU32(res, this.bits, 72);
|
||||
utils.writeU32(res, this.nonce, 76);
|
||||
utils.copy(new Buffer(this.chainwork.toArray('be', 32)), res, 80);
|
||||
utils.writeU32(res, this.height, 80);
|
||||
utils.copy(new Buffer(this.chainwork.toArray('be', 32)), res, 84);
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
ChainBlock.fromRaw = function fromRaw(chain, height, p) {
|
||||
ChainBlock.fromRaw = function fromRaw(chain, p) {
|
||||
return new ChainBlock(chain, {
|
||||
height: height,
|
||||
hash: utils.toHex(utils.dsha256(p.slice(0, 80))),
|
||||
version: utils.read32(p, 0),
|
||||
prevBlock: utils.toHex(p.slice(4, 36)),
|
||||
@ -146,10 +101,153 @@ ChainBlock.fromRaw = function fromRaw(chain, height, p) {
|
||||
ts: utils.readU32(p, 68),
|
||||
bits: utils.readU32(p, 72),
|
||||
nonce: utils.readU32(p, 76),
|
||||
chainwork: new bn(p.slice(80, 112), 'be')
|
||||
height: utils.readU32(p, 80),
|
||||
chainwork: new bn(p.slice(84, 116), 'be')
|
||||
});
|
||||
};
|
||||
|
||||
ChainBlock.prototype.getMedianTimeAsync = function getMedianTime(callback) {
|
||||
var self = this;
|
||||
var median = [];
|
||||
var timeSpan = constants.block.medianTimespan;
|
||||
var i = 0;
|
||||
|
||||
(function next(err, entry) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
|
||||
if (!entry || i >= timeSpan) {
|
||||
median = median.sort();
|
||||
return callback(null, median[median.length / 2 | 0]);
|
||||
}
|
||||
|
||||
median[i] = entry.ts;
|
||||
i++;
|
||||
|
||||
self.chain.db.get(entry.prevBlock, next);
|
||||
})(null, this);
|
||||
};
|
||||
|
||||
ChainBlock.prototype.isOutdatedAsync = function isOutdated(version, callback) {
|
||||
return this.isSuperMajority(version, network.block.majorityRejectOutdated, callback);
|
||||
};
|
||||
|
||||
ChainBlock.prototype.isUpgradedAsync = function isUpgraded(version, callback) {
|
||||
return this.isSuperMajority(version, network.block.majorityEnforceUpgrade, callback);
|
||||
};
|
||||
|
||||
ChainBlock.prototype.isSuperMajorityAsync = function isSuperMajority(version, required, callback) {
|
||||
var self = this;
|
||||
var found = 0;
|
||||
var majorityWindow = network.block.majorityWindow;
|
||||
var i = 0;
|
||||
|
||||
(function next(err, entry) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
|
||||
if (!entry || i >= majorityWindow || found >= required)
|
||||
return callback(null, found >= required);
|
||||
|
||||
if (entry.version >= version)
|
||||
found++;
|
||||
|
||||
i++;
|
||||
|
||||
self.chain.db.get(entry.prevBlock, next);
|
||||
})(null, this);
|
||||
};
|
||||
|
||||
ChainBlock.prototype.alloc = function alloc(callback) {
|
||||
var majorityWindow = network.block.majorityWindow;
|
||||
var medianTimespan = constants.block.medianTimespan;
|
||||
var powDiffInterval = network.powDiffInterval;
|
||||
var allowMinDiff = network.powAllowMinDifficultyBlocks;
|
||||
var max = Math.max(majorityWindow, medianTimespan);
|
||||
if ((this.height + 1) % powDiffInterval === 0 || allowMinDiff)
|
||||
max = Math.max(max, powDiffInterval);
|
||||
return this._alloc(max, callback);
|
||||
};
|
||||
|
||||
ChainBlock.prototype._alloc = function _alloc(max, callback) {
|
||||
var self = this;
|
||||
var entry = this;
|
||||
|
||||
assert(this.previous.length === 0);
|
||||
|
||||
// Try to do this iteratively and synchronously
|
||||
// so we don't have to wait on nextTicks.
|
||||
for (;;) {
|
||||
this.previous.push(entry);
|
||||
|
||||
if (this.previous.length >= max)
|
||||
return callback();
|
||||
|
||||
if (!this.chain.db.cacheHash.has(entry.prevBlock))
|
||||
break;
|
||||
|
||||
entry = this.chain.db.cacheHash.get(entry.prevBlock);
|
||||
}
|
||||
|
||||
(function next(err, entry) {
|
||||
if (err) {
|
||||
self.free();
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if (!entry)
|
||||
return callback();
|
||||
|
||||
self.previous.push(entry);
|
||||
|
||||
if (self.previous.length >= max)
|
||||
return callback();
|
||||
|
||||
self.chain.db.get(entry.prevBlock, next);
|
||||
})(null, entry);
|
||||
};
|
||||
|
||||
ChainBlock.prototype.free = function free() {
|
||||
this.previous.length = 0;
|
||||
};
|
||||
|
||||
ChainBlock.prototype.getMedianTime = function getMedianTime() {
|
||||
var entry = this;
|
||||
var median = [];
|
||||
var timeSpan = constants.block.medianTimespan;
|
||||
var i;
|
||||
|
||||
for (i = 0; i < timeSpan && entry; i++, entry = this.previous[i])
|
||||
median.push(entry.ts);
|
||||
|
||||
median = median.sort();
|
||||
|
||||
return median[median.length / 2 | 0];
|
||||
};
|
||||
|
||||
ChainBlock.prototype.isOutdated = function isOutdated(version) {
|
||||
return this.isSuperMajority(version, network.block.majorityRejectOutdated);
|
||||
};
|
||||
|
||||
ChainBlock.prototype.isUpgraded = function isUpgraded(version) {
|
||||
return this.isSuperMajority(version, network.block.majorityEnforceUpgrade);
|
||||
};
|
||||
|
||||
ChainBlock.prototype.isSuperMajority = function isSuperMajority(version, required) {
|
||||
var entry = this;
|
||||
var found = 0;
|
||||
var majorityWindow = network.block.majorityWindow;
|
||||
var i;
|
||||
|
||||
for (i = 0; i < majorityWindow && found < required && entry; i++) {
|
||||
if (entry.version >= version)
|
||||
found++;
|
||||
entry = this.previous[i + 1];
|
||||
}
|
||||
|
||||
return found >= required;
|
||||
};
|
||||
|
||||
/**
|
||||
* Expose
|
||||
*/
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -37,8 +37,11 @@ CompactBlock.prototype.getCoinbaseHeight = function getCoinbaseHeight() {
|
||||
return this.coinbaseHeight;
|
||||
};
|
||||
|
||||
CompactBlock.prototype.toBlock = function toBlock(peer) {
|
||||
return new bcoin.block(bcoin.protocol.parser.parseBlock(this._raw));
|
||||
CompactBlock.prototype.toBlock = function toBlock() {
|
||||
var block = new bcoin.block(bcoin.protocol.parser.parseBlock(this._raw));
|
||||
if (this.valid != null)
|
||||
block.valid = this.valid;
|
||||
return block;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@ -81,6 +81,23 @@ LRU.prototype._compact = function _compact() {
|
||||
item.prev = null;
|
||||
};
|
||||
|
||||
LRU.prototype.reset = function reset() {
|
||||
var item, next;
|
||||
|
||||
for (item = this.head; item; item = next) {
|
||||
delete this.data[item.key];
|
||||
next = item.next;
|
||||
item.prev = null;
|
||||
item.next = null;
|
||||
}
|
||||
|
||||
assert(!item);
|
||||
|
||||
this.size = 0;
|
||||
this.head = null;
|
||||
this.tail = null;
|
||||
};
|
||||
|
||||
LRU.prototype.set = function set(key, value) {
|
||||
var item;
|
||||
|
||||
|
||||
@ -108,6 +108,7 @@ Fullnode.prototype._init = function _init() {
|
||||
self.emit('wallet tx', tx, ids);
|
||||
});
|
||||
|
||||
if (0)
|
||||
this.on('tx', function(tx) {
|
||||
self.walletdb.addTX(tx, function(err) {
|
||||
if (err)
|
||||
@ -116,6 +117,7 @@ Fullnode.prototype._init = function _init() {
|
||||
});
|
||||
|
||||
// Emit events for valid blocks and TXs.
|
||||
if (0)
|
||||
this.chain.on('block', function(block) {
|
||||
self.emit('block', block);
|
||||
block.txs.forEach(function(tx) {
|
||||
@ -128,6 +130,7 @@ Fullnode.prototype._init = function _init() {
|
||||
});
|
||||
|
||||
// Update the mempool.
|
||||
if (0)
|
||||
this.chain.on('add block', function(block) {
|
||||
self.mempool.addBlock(block);
|
||||
});
|
||||
|
||||
@ -243,7 +243,7 @@ Pool.prototype._init = function _init() {
|
||||
Pool.prototype.getBlocks = function getBlocks(peer, top, stop) {
|
||||
var self = this;
|
||||
this.chain.onFlush(function() {
|
||||
self.chain.getLocatorAsync(top, function(err, locator) {
|
||||
self.chain.getLocator(top, function(err, locator) {
|
||||
if (err)
|
||||
throw err;
|
||||
|
||||
@ -256,7 +256,7 @@ Pool.prototype.resolveOrphan = function resolveOrphan(peer, top, orphan) {
|
||||
var self = this;
|
||||
assert(orphan);
|
||||
this.chain.onFlush(function() {
|
||||
self.chain.getLocatorAsync(top, function(err, locator) {
|
||||
self.chain.getLocator(top, function(err, locator) {
|
||||
if (err)
|
||||
throw err;
|
||||
|
||||
@ -280,10 +280,10 @@ Pool.prototype.resolveOrphan = function resolveOrphan(peer, top, orphan) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.chain.has(orphan.soil)) {
|
||||
utils.debug('Already have orphan "soil". Race condition?');
|
||||
return;
|
||||
}
|
||||
// if (self.chain.has(orphan.soil)) {
|
||||
// utils.debug('Already have orphan "soil". Race condition?');
|
||||
// return;
|
||||
// }
|
||||
|
||||
peer.getBlocks(locator, orphan.root);
|
||||
});
|
||||
@ -293,7 +293,7 @@ Pool.prototype.resolveOrphan = function resolveOrphan(peer, top, orphan) {
|
||||
Pool.prototype.getHeaders = function getHeaders(peer, top, stop) {
|
||||
var self = this;
|
||||
this.chain.onFlush(function() {
|
||||
self.chain.getLocatorAsync(top, function(err, locator) {
|
||||
self.chain.getLocator(top, function(err, locator) {
|
||||
if (err)
|
||||
throw err;
|
||||
|
||||
@ -564,8 +564,11 @@ Pool.prototype._handleHeaders = function _handleHeaders(headers, peer) {
|
||||
if (!header.verify())
|
||||
break;
|
||||
|
||||
if (!self.chain.has(hash))
|
||||
self.getData(peer, self.block.type, hash);
|
||||
self.chain.has(hash, function(err, has) {
|
||||
assert(!err);
|
||||
if (!has)
|
||||
self.getData(peer, self.block.type, hash);
|
||||
});
|
||||
|
||||
last = hash;
|
||||
}
|
||||
@ -608,39 +611,51 @@ Pool.prototype._handleBlocks = function _handleBlocks(hashes, peer) {
|
||||
|
||||
this.emit('blocks', hashes);
|
||||
|
||||
var req = [];
|
||||
this.chain.onFlush(function() {
|
||||
for (i = 0; i < hashes.length; i++) {
|
||||
hash = hashes[i];
|
||||
|
||||
utils.forEachSerial(hashes, function(hash, next, i) {
|
||||
// Resolve orphan chain.
|
||||
if (self.chain.hasOrphan(hash)) {
|
||||
utils.debug('Peer sent a hash that is already a known orphan.');
|
||||
self.resolveOrphan(peer, null, hash);
|
||||
continue;
|
||||
return utils.nextTick(next);
|
||||
}
|
||||
|
||||
// Request a block if we don't have it.
|
||||
if (!self.chain.has(hash)) {
|
||||
self.getData(peer, self.block.type, hash);
|
||||
continue;
|
||||
}
|
||||
self.chain.has(hash, function(err, has) {
|
||||
assert(!err);
|
||||
|
||||
// Normally we request the hashContinue.
|
||||
// In the odd case where we already have
|
||||
// it, we can do one of two things: either
|
||||
// force re-downloading of the block to
|
||||
// continue the sync, or do a getblocks
|
||||
// from the last hash.
|
||||
if (i === hashes.length - 1) {
|
||||
// Request more hashes:
|
||||
// self.getBlocks(peer, hash, null);
|
||||
if (!has) {
|
||||
req.push([peer, self.block.type, hash]);
|
||||
return next();
|
||||
}
|
||||
|
||||
// Re-download the block (traditional method):
|
||||
self.getData(peer, self.block.type, hash, { force: true });
|
||||
// Normally we request the hashContinue.
|
||||
// In the odd case where we already have
|
||||
// it, we can do one of two things: either
|
||||
// force re-downloading of the block to
|
||||
// continue the sync, or do a getblocks
|
||||
// from the last hash.
|
||||
if (i === hashes.length - 1) {
|
||||
// Request more hashes:
|
||||
// self.getBlocks(peer, hash, null);
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// Re-download the block (traditional method):
|
||||
req.push([peer, self.block.type, hash, { force: true }]);
|
||||
|
||||
return next();
|
||||
}
|
||||
|
||||
return next();
|
||||
});
|
||||
}, function(err) {
|
||||
if (err)
|
||||
return self.emit('error', err);
|
||||
|
||||
req.forEach(function(item) {
|
||||
self.getData(item[0], item[1], item[2], item[3]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Reset interval to avoid calling getblocks unnecessarily
|
||||
@ -659,12 +674,15 @@ Pool.prototype._handleInv = function _handleInv(hashes, peer) {
|
||||
|
||||
for (i = 0; i < hashes.length; i++) {
|
||||
hash = utils.toHex(hashes[i]);
|
||||
if (!this.chain.has(hash)) {
|
||||
this.chain.has(hash, function(err, has) {
|
||||
assert(!err);
|
||||
if (has)
|
||||
return;
|
||||
if (this.options.headers)
|
||||
this.getHeaders(this.peers.load, null, hash);
|
||||
else
|
||||
this.getData(peer, this.block.type, hash);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@ -720,7 +738,7 @@ Pool.prototype._handleBlock = function _handleBlock(block, peer, callback) {
|
||||
self.chain.orphan.count,
|
||||
self.request.activeBlocks,
|
||||
peer.queue.block.length,
|
||||
self.chain.getCurrentTarget(),
|
||||
0,
|
||||
self.peers.all.length,
|
||||
self.chain.pending.length,
|
||||
self.chain.bestHeight,
|
||||
@ -1327,7 +1345,7 @@ Pool.prototype.searchWallet = function(wallet, callback) {
|
||||
if (!height || height === -1)
|
||||
height = self.chain.height - (7 * 24 * 6);
|
||||
|
||||
self.chain.resetHeightAsync(height, function(err) {
|
||||
self.chain.resetHeight(height, function(err) {
|
||||
if (err) {
|
||||
utils.debug('Failed to reset height: %s', err.stack + '');
|
||||
return callback(err);
|
||||
@ -1349,7 +1367,7 @@ Pool.prototype.searchWallet = function(wallet, callback) {
|
||||
if (!ts)
|
||||
ts = utils.now() - 7 * 24 * 3600;
|
||||
|
||||
self.chain.resetTimeAsync(ts, function(err) {
|
||||
self.chain.resetTime(ts, function(err) {
|
||||
if (err) {
|
||||
utils.debug('Failed to reset time: %s', err.stack + '');
|
||||
return callback(err);
|
||||
@ -1416,7 +1434,7 @@ Pool.prototype.search = function search(id, range, callback) {
|
||||
this.on('block', onBlock);
|
||||
|
||||
if (range.start < this.chain.tip.ts) {
|
||||
this.chain.resetTimeAsync(range.start, function(err) {
|
||||
this.chain.resetTime(range.start, function(err) {
|
||||
if (err)
|
||||
return done(err);
|
||||
|
||||
@ -1446,50 +1464,57 @@ Pool.prototype.getData = function getData(peer, type, hash, options, callback) {
|
||||
if (Buffer.isBuffer(hash))
|
||||
hash = utils.toHex(hash);
|
||||
|
||||
if (this.request.map[hash]) {
|
||||
if (callback)
|
||||
this.request.map[hash].callback.push(callback);
|
||||
return;
|
||||
function has(cb) {
|
||||
if (!options.force && type !== self.tx.type)
|
||||
return self.chain.has(hash, cb);
|
||||
return cb(null, false);
|
||||
}
|
||||
|
||||
if (!options.force && type !== self.tx.type) {
|
||||
if (this.chain.has(hash))
|
||||
has(function(err, res) {
|
||||
assert(!err);
|
||||
if (res)
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.noQueue)
|
||||
return;
|
||||
if (self.request.map[hash]) {
|
||||
if (callback)
|
||||
self.request.map[hash].callback.push(callback);
|
||||
return;
|
||||
}
|
||||
|
||||
item = new LoadRequest(this, peer, type, hash, callback);
|
||||
item = new LoadRequest(self, peer, type, hash, callback);
|
||||
|
||||
if (type === self.tx.type) {
|
||||
if (peer.queue.tx.length === 0) {
|
||||
utils.nextTick(function() {
|
||||
utils.debug(
|
||||
'Requesting %d/%d txs from %s with getdata',
|
||||
peer.queue.tx.length,
|
||||
self.request.activeTX,
|
||||
peer.host);
|
||||
if (options.noQueue)
|
||||
return;
|
||||
|
||||
peer.getData(peer.queue.tx);
|
||||
peer.queue.tx.length = 0;
|
||||
if (type === self.tx.type) {
|
||||
if (peer.queue.tx.length === 0) {
|
||||
utils.nextTick(function() {
|
||||
utils.debug(
|
||||
'Requesting %d/%d txs from %s with getdata',
|
||||
peer.queue.tx.length,
|
||||
self.request.activeTX,
|
||||
peer.host);
|
||||
|
||||
peer.getData(peer.queue.tx);
|
||||
peer.queue.tx.length = 0;
|
||||
});
|
||||
}
|
||||
|
||||
peer.queue.tx.push(item.start());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (peer.queue.block.length === 0) {
|
||||
self.chain.onFlush(function() {
|
||||
utils.nextTick(function() {
|
||||
self.scheduleRequests(peer);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
peer.queue.tx.push(item.start());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (peer.queue.block.length === 0) {
|
||||
this.chain.onFlush(function() {
|
||||
utils.nextTick(function() {
|
||||
self.scheduleRequests(peer);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
peer.queue.block.push(item);
|
||||
peer.queue.block.push(item);
|
||||
});
|
||||
};
|
||||
|
||||
Pool.prototype.scheduleRequests = function scheduleRequests(peer) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user