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
*/
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

View File

@ -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;
};
/**

View File

@ -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;

View File

@ -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);
});

View File

@ -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) {