new chaindb. allow for real fork resolution.

This commit is contained in:
Christopher Jeffrey 2016-03-05 02:18:39 -08:00
parent 6de9e54667
commit 04c5f94997
7 changed files with 1535 additions and 1495 deletions

File diff suppressed because it is too large Load Diff

View File

@ -18,7 +18,7 @@ var fs = bcoin.fs;
* ChainBlock * ChainBlock
*/ */
function ChainBlock(chain, data) { function ChainBlock(chain, data, prev) {
if (!(this instanceof ChainBlock)) if (!(this instanceof ChainBlock))
return new ChainBlock(chain, data); return new ChainBlock(chain, data);
@ -31,18 +31,14 @@ function ChainBlock(chain, data) {
this.bits = data.bits; this.bits = data.bits;
this.nonce = data.nonce; this.nonce = data.nonce;
this.height = data.height; 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.BLOCK_SIZE = 116;
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.prototype.getProof = function getProof() { ChainBlock.prototype.getProof = function getProof() {
var target = utils.fromCompact(this.bits); 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)); return new bn(1).ushln(256).div(target.addn(1));
}; };
ChainBlock.prototype.getChainwork = function getChainwork() { ChainBlock.prototype.getChainwork = function getChainwork(prev) {
var prev;
// Workaround for genesis block // Workaround for genesis block
// being added _in_ chaindb. // being added _in_ chaindb.
if (!this.chain.db) if (!this.chain.db)
return this.getProof(); return this.getProof();
prev = this.prev;
return (prev ? prev.chainwork : new bn(0)).add(this.getProof()); 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; 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() { ChainBlock.prototype.toJSON = function toJSON() {
return { return {
hash: this.hash, hash: this.hash,
@ -131,14 +86,14 @@ ChainBlock.prototype.toRaw = function toRaw() {
utils.writeU32(res, this.ts, 68); utils.writeU32(res, this.ts, 68);
utils.writeU32(res, this.bits, 72); utils.writeU32(res, this.bits, 72);
utils.writeU32(res, this.nonce, 76); 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; return res;
}; };
ChainBlock.fromRaw = function fromRaw(chain, height, p) { ChainBlock.fromRaw = function fromRaw(chain, p) {
return new ChainBlock(chain, { return new ChainBlock(chain, {
height: height,
hash: utils.toHex(utils.dsha256(p.slice(0, 80))), hash: utils.toHex(utils.dsha256(p.slice(0, 80))),
version: utils.read32(p, 0), version: utils.read32(p, 0),
prevBlock: utils.toHex(p.slice(4, 36)), prevBlock: utils.toHex(p.slice(4, 36)),
@ -146,10 +101,153 @@ ChainBlock.fromRaw = function fromRaw(chain, height, p) {
ts: utils.readU32(p, 68), ts: utils.readU32(p, 68),
bits: utils.readU32(p, 72), bits: utils.readU32(p, 72),
nonce: utils.readU32(p, 76), 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 * Expose
*/ */

File diff suppressed because it is too large Load Diff

View File

@ -37,8 +37,11 @@ CompactBlock.prototype.getCoinbaseHeight = function getCoinbaseHeight() {
return this.coinbaseHeight; return this.coinbaseHeight;
}; };
CompactBlock.prototype.toBlock = function toBlock(peer) { CompactBlock.prototype.toBlock = function toBlock() {
return new bcoin.block(bcoin.protocol.parser.parseBlock(this._raw)); var block = new bcoin.block(bcoin.protocol.parser.parseBlock(this._raw));
if (this.valid != null)
block.valid = this.valid;
return block;
}; };
/** /**

View File

@ -81,6 +81,23 @@ LRU.prototype._compact = function _compact() {
item.prev = null; 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) { LRU.prototype.set = function set(key, value) {
var item; var item;

View File

@ -108,6 +108,7 @@ Fullnode.prototype._init = function _init() {
self.emit('wallet tx', tx, ids); self.emit('wallet tx', tx, ids);
}); });
if (0)
this.on('tx', function(tx) { this.on('tx', function(tx) {
self.walletdb.addTX(tx, function(err) { self.walletdb.addTX(tx, function(err) {
if (err) if (err)
@ -116,6 +117,7 @@ Fullnode.prototype._init = function _init() {
}); });
// Emit events for valid blocks and TXs. // Emit events for valid blocks and TXs.
if (0)
this.chain.on('block', function(block) { this.chain.on('block', function(block) {
self.emit('block', block); self.emit('block', block);
block.txs.forEach(function(tx) { block.txs.forEach(function(tx) {
@ -128,6 +130,7 @@ Fullnode.prototype._init = function _init() {
}); });
// Update the mempool. // Update the mempool.
if (0)
this.chain.on('add block', function(block) { this.chain.on('add block', function(block) {
self.mempool.addBlock(block); self.mempool.addBlock(block);
}); });

View File

@ -243,7 +243,7 @@ Pool.prototype._init = function _init() {
Pool.prototype.getBlocks = function getBlocks(peer, top, stop) { Pool.prototype.getBlocks = function getBlocks(peer, top, stop) {
var self = this; var self = this;
this.chain.onFlush(function() { this.chain.onFlush(function() {
self.chain.getLocatorAsync(top, function(err, locator) { self.chain.getLocator(top, function(err, locator) {
if (err) if (err)
throw err; throw err;
@ -256,7 +256,7 @@ Pool.prototype.resolveOrphan = function resolveOrphan(peer, top, orphan) {
var self = this; var self = this;
assert(orphan); assert(orphan);
this.chain.onFlush(function() { this.chain.onFlush(function() {
self.chain.getLocatorAsync(top, function(err, locator) { self.chain.getLocator(top, function(err, locator) {
if (err) if (err)
throw err; throw err;
@ -280,10 +280,10 @@ Pool.prototype.resolveOrphan = function resolveOrphan(peer, top, orphan) {
return; return;
} }
if (self.chain.has(orphan.soil)) { // if (self.chain.has(orphan.soil)) {
utils.debug('Already have orphan "soil". Race condition?'); // utils.debug('Already have orphan "soil". Race condition?');
return; // return;
} // }
peer.getBlocks(locator, orphan.root); 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) { Pool.prototype.getHeaders = function getHeaders(peer, top, stop) {
var self = this; var self = this;
this.chain.onFlush(function() { this.chain.onFlush(function() {
self.chain.getLocatorAsync(top, function(err, locator) { self.chain.getLocator(top, function(err, locator) {
if (err) if (err)
throw err; throw err;
@ -564,8 +564,11 @@ Pool.prototype._handleHeaders = function _handleHeaders(headers, peer) {
if (!header.verify()) if (!header.verify())
break; break;
if (!self.chain.has(hash)) self.chain.has(hash, function(err, has) {
self.getData(peer, self.block.type, hash); assert(!err);
if (!has)
self.getData(peer, self.block.type, hash);
});
last = hash; last = hash;
} }
@ -608,39 +611,51 @@ Pool.prototype._handleBlocks = function _handleBlocks(hashes, peer) {
this.emit('blocks', hashes); this.emit('blocks', hashes);
var req = [];
this.chain.onFlush(function() { this.chain.onFlush(function() {
for (i = 0; i < hashes.length; i++) { utils.forEachSerial(hashes, function(hash, next, i) {
hash = hashes[i];
// Resolve orphan chain. // Resolve orphan chain.
if (self.chain.hasOrphan(hash)) { if (self.chain.hasOrphan(hash)) {
utils.debug('Peer sent a hash that is already a known orphan.'); utils.debug('Peer sent a hash that is already a known orphan.');
self.resolveOrphan(peer, null, hash); self.resolveOrphan(peer, null, hash);
continue; return utils.nextTick(next);
} }
// Request a block if we don't have it. // Request a block if we don't have it.
if (!self.chain.has(hash)) { self.chain.has(hash, function(err, has) {
self.getData(peer, self.block.type, hash); assert(!err);
continue;
}
// Normally we request the hashContinue. if (!has) {
// In the odd case where we already have req.push([peer, self.block.type, hash]);
// it, we can do one of two things: either return next();
// 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);
// Re-download the block (traditional method): // Normally we request the hashContinue.
self.getData(peer, self.block.type, hash, { force: true }); // 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 // 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++) { for (i = 0; i < hashes.length; i++) {
hash = utils.toHex(hashes[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) if (this.options.headers)
this.getHeaders(this.peers.load, null, hash); this.getHeaders(this.peers.load, null, hash);
else else
this.getData(peer, this.block.type, hash); this.getData(peer, this.block.type, hash);
} });
} }
}; };
@ -720,7 +738,7 @@ Pool.prototype._handleBlock = function _handleBlock(block, peer, callback) {
self.chain.orphan.count, self.chain.orphan.count,
self.request.activeBlocks, self.request.activeBlocks,
peer.queue.block.length, peer.queue.block.length,
self.chain.getCurrentTarget(), 0,
self.peers.all.length, self.peers.all.length,
self.chain.pending.length, self.chain.pending.length,
self.chain.bestHeight, self.chain.bestHeight,
@ -1327,7 +1345,7 @@ Pool.prototype.searchWallet = function(wallet, callback) {
if (!height || height === -1) if (!height || height === -1)
height = self.chain.height - (7 * 24 * 6); height = self.chain.height - (7 * 24 * 6);
self.chain.resetHeightAsync(height, function(err) { self.chain.resetHeight(height, function(err) {
if (err) { if (err) {
utils.debug('Failed to reset height: %s', err.stack + ''); utils.debug('Failed to reset height: %s', err.stack + '');
return callback(err); return callback(err);
@ -1349,7 +1367,7 @@ Pool.prototype.searchWallet = function(wallet, callback) {
if (!ts) if (!ts)
ts = utils.now() - 7 * 24 * 3600; ts = utils.now() - 7 * 24 * 3600;
self.chain.resetTimeAsync(ts, function(err) { self.chain.resetTime(ts, function(err) {
if (err) { if (err) {
utils.debug('Failed to reset time: %s', err.stack + ''); utils.debug('Failed to reset time: %s', err.stack + '');
return callback(err); return callback(err);
@ -1416,7 +1434,7 @@ Pool.prototype.search = function search(id, range, callback) {
this.on('block', onBlock); this.on('block', onBlock);
if (range.start < this.chain.tip.ts) { if (range.start < this.chain.tip.ts) {
this.chain.resetTimeAsync(range.start, function(err) { this.chain.resetTime(range.start, function(err) {
if (err) if (err)
return done(err); return done(err);
@ -1446,50 +1464,57 @@ Pool.prototype.getData = function getData(peer, type, hash, options, callback) {
if (Buffer.isBuffer(hash)) if (Buffer.isBuffer(hash))
hash = utils.toHex(hash); hash = utils.toHex(hash);
if (this.request.map[hash]) { function has(cb) {
if (callback) if (!options.force && type !== self.tx.type)
this.request.map[hash].callback.push(callback); return self.chain.has(hash, cb);
return; return cb(null, false);
} }
if (!options.force && type !== self.tx.type) { has(function(err, res) {
if (this.chain.has(hash)) assert(!err);
if (res)
return; return;
}
if (options.noQueue) if (self.request.map[hash]) {
return; 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 (options.noQueue)
if (peer.queue.tx.length === 0) { return;
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); if (type === self.tx.type) {
peer.queue.tx.length = 0; 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()); peer.queue.block.push(item);
});
return;
}
if (peer.queue.block.length === 0) {
this.chain.onFlush(function() {
utils.nextTick(function() {
self.scheduleRequests(peer);
});
});
}
peer.queue.block.push(item);
}; };
Pool.prototype.scheduleRequests = function scheduleRequests(peer) { Pool.prototype.scheduleRequests = function scheduleRequests(peer) {