chain: refactor and optimize.

This commit is contained in:
Christopher Jeffrey 2016-11-25 18:54:02 -08:00
parent 024ff3e211
commit c0fd199f2d
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
8 changed files with 168 additions and 141 deletions

View File

@ -1084,7 +1084,7 @@ Chain.prototype.replay = co(function* replay(block) {
*/
Chain.prototype._replay = co(function* replay(block) {
var entry = yield this.db.get(block);
var entry = yield this.db.getEntry(block);
if (!entry)
throw new Error('Block not found.');
@ -1255,7 +1255,7 @@ Chain.prototype._add = co(function* add(block) {
throw new VerifyError(block, 'invalid', ret.reason, ret.score);
}
existing = yield this.db.has(hash);
existing = yield this.db.hasEntry(hash);
// Do we already have this block?
if (existing) {
@ -1264,7 +1264,7 @@ Chain.prototype._add = co(function* add(block) {
}
// Find the previous block height/index.
prev = yield this.db.get(prevBlock);
prev = yield this.db.getEntry(prevBlock);
if (prev)
height = prev.height + 1;
@ -1547,7 +1547,7 @@ Chain.prototype.has = co(function* has(hash) {
if (hash === this.currentBlock)
return true;
return yield this.hasBlock(hash);
return yield this.hasEntry(hash);
});
/**
@ -1569,7 +1569,7 @@ Chain.prototype.byTime = co(function* byTime(ts) {
// timestamp.
while (start < end) {
pos = (start + end) >>> 1;
entry = yield this.db.get(pos);
entry = yield this.db.getEntry(pos);
if (!entry)
return;
@ -1594,8 +1594,8 @@ Chain.prototype.byTime = co(function* byTime(ts) {
* @returns {Promise} - Returns Boolean.
*/
Chain.prototype.hasBlock = function hasBlock(hash) {
return this.db.has(hash);
Chain.prototype.hasEntry = function hasEntry(hash) {
return this.db.hasEntry(hash);
};
/**
@ -1625,7 +1625,7 @@ Chain.prototype.hasPending = function hasPending(hash) {
*/
Chain.prototype.getEntry = function getEntry(hash, callback) {
return this.db.get(hash);
return this.db.getEntry(hash);
};
/**
@ -1715,7 +1715,7 @@ Chain.prototype._getLocator = co(function* getLocator(start) {
if (start == null)
start = this.tip.hash;
entry = yield this.db.get(start);
entry = yield this.db.getEntry(start);
if (!entry) {
// We could simply return `start` here,
@ -1755,7 +1755,7 @@ Chain.prototype._getLocator = co(function* getLocator(start) {
continue;
}
entry = yield entry.getAncestorByHeight(height);
entry = yield entry.getAncestor(height);
if (!entry)
break;
@ -1956,7 +1956,7 @@ Chain.prototype.getState = co(function* getState(prev, deployment) {
var thresholdStates = constants.thresholdStates;
var bit = deployment.bit;
var compute = [];
var i, entry, count, state;
var i, entry, count, state, cached;
var block, time, height;
if (!prev)
@ -1964,7 +1964,7 @@ Chain.prototype.getState = co(function* getState(prev, deployment) {
if (((prev.height + 1) % period) !== 0) {
height = prev.height - ((prev.height + 1) % period);
prev = yield prev.getAncestorByHeight(height);
prev = yield prev.getAncestor(height);
if (prev) {
assert(prev.height === height);
@ -1976,8 +1976,10 @@ Chain.prototype.getState = co(function* getState(prev, deployment) {
state = thresholdStates.DEFINED;
while (entry) {
if (this.db.stateCache.get(bit, entry) !== -1) {
state = this.db.stateCache.get(bit, entry);
cached = this.db.stateCache.get(bit, entry);
if (cached !== -1) {
state = cached;
break;
}
@ -1992,7 +1994,7 @@ Chain.prototype.getState = co(function* getState(prev, deployment) {
compute.push(entry);
height = entry.height - period;
entry = yield entry.getAncestorByHeight(height);
entry = yield entry.getAncestor(height);
}
while (compute.length) {
@ -2161,7 +2163,7 @@ Chain.prototype.getLocks = co(function* getLocks(prev, tx, flags) {
continue;
}
entry = yield prev.getAncestorByHeight(Math.max(coinHeight - 1, 0));
entry = yield prev.getAncestor(Math.max(coinHeight - 1, 0));
assert(entry, 'Database is corrupt.');
coinTime = yield entry.getMedianTimeAsync();

View File

@ -83,7 +83,7 @@ function ChainDB(chain) {
this.cacheWindow = (this.network.pow.retargetInterval + 1) * 2 + 100;
// We want to keep the last 5 blocks of unspents in memory.
this.coinWindow = 25 << 20;
this.coinWindow = 65 << 20;
this.coinCache = new LRU.Nil();
this.cacheHash = new LRU(this.cacheWindow);
@ -280,32 +280,32 @@ ChainDB.prototype.commit = co(function* commit() {
/**
* Test the cache for a present entry hash or height.
* @param {Hash|Number} hash - Hash or height.
* @param {Hash|Number} block - Hash or height.
*/
ChainDB.prototype.hasCache = function hasCache(hash) {
checkHash(hash);
ChainDB.prototype.hasCache = function hasCache(block) {
if (typeof block === 'number')
return this.cacheHeight.has(block);
if (typeof hash === 'number')
return this.cacheHeight.has(hash);
assert(typeof block === 'string');
return this.cacheHash.has(hash);
return this.cacheHash.has(block);
};
/**
* Get an entry directly from the LRU cache. This is
* useful for optimization if we don't want to wait on a
* nextTick during a `get()` call.
* @param {Hash|Number} hash - Hash or height.
* @param {Hash|Number} block - Hash or height.
*/
ChainDB.prototype.getCache = function getCache(hash) {
checkHash(hash);
ChainDB.prototype.getCache = function getCache(block) {
if (typeof block === 'number')
return this.cacheHeight.get(block);
if (typeof hash === 'number')
return this.cacheHeight.get(hash);
assert(typeof block === 'string');
return this.cacheHash.get(hash);
return this.cacheHash.get(block);
};
/**
@ -317,11 +317,11 @@ ChainDB.prototype.getCache = function getCache(hash) {
ChainDB.prototype.getHeight = co(function* getHeight(hash) {
var entry, height;
checkHash(hash);
if (typeof hash === 'number')
return hash;
assert(typeof hash === 'string');
if (hash === constants.NULL_HASH)
return -1;
@ -348,11 +348,14 @@ ChainDB.prototype.getHeight = co(function* getHeight(hash) {
ChainDB.prototype.getHash = co(function* getHash(height) {
var entry, hash;
checkHash(height);
if (typeof height === 'string')
return height;
assert(typeof height === 'number');
if (height < 0)
return;
entry = this.cacheHeight.get(height);
if (entry)
@ -366,37 +369,17 @@ ChainDB.prototype.getHash = co(function* getHash(height) {
return hash.toString('hex');
});
/**
* Get the current chain height from the tip record.
* @returns {Promise} - Returns Number.
*/
ChainDB.prototype.getChainHeight = co(function* getChainHeight() {
var entry = yield this.getTip();
if (!entry)
return -1;
return entry.height;
});
/**
* Get both hash and height depending on the value passed in.
* @param {Hash|Number} block - Can be a has or height.
* @returns {Promise} - Returns {@link Hash}, Number.
* @param {Hash|Number} block - Can be a hash or height.
* @returns {Promise}
*/
ChainDB.prototype.getBoth = co(function* getBoth(block) {
var hash, height;
checkHash(block);
if (typeof block === 'string')
hash = block;
else
if (typeof block === 'number') {
height = block;
if (!hash) {
hash = yield this.getHash(height);
if (hash == null)
@ -405,6 +388,9 @@ ChainDB.prototype.getBoth = co(function* getBoth(block) {
return new BlockPair(hash, height);
}
assert(typeof block === 'string');
hash = block;
height = yield this.getHeight(hash);
if (height === -1)
@ -414,19 +400,58 @@ ChainDB.prototype.getBoth = co(function* getBoth(block) {
});
/**
* Retrieve a chain entry but do _not_ add it to the LRU cache.
* Retrieve a chain entry by height.
* @param {Number} height
* @returns {Promise} - Returns {@link ChainEntry}.
*/
ChainDB.prototype.getEntryByHeight = co(function* getEntryByHeight(height) {
var state, entry, hash;
assert(typeof height === 'number');
if (height < 0)
return;
entry = this.cacheHeight.get(height);
if (entry)
return entry;
hash = yield this.db.get(layout.H(height));
if (!hash)
return;
hash = hash.toString('hex');
state = this.chain.state;
entry = yield this.getEntryByHash(hash);
if (!entry)
return;
// By the time getEntry has completed,
// a reorg may have occurred. This entry
// may not be on the main chain anymore.
if (this.chain.state === state)
this.cacheHeight.set(entry.height, entry);
return entry;
});
/**
* Retrieve a chain entry by hash.
* @param {Hash} hash
* @returns {Promise} - Returns {@link ChainEntry}.
*/
ChainDB.prototype.getEntry = co(function* getEntry(hash) {
var entry;
ChainDB.prototype.getEntryByHash = co(function* getEntryByHash(hash) {
var entry, raw;
checkHash(hash);
assert(typeof hash === 'string');
hash = yield this.getHash(hash);
if (!hash)
if (hash === constants.NULL_HASH)
return;
entry = this.cacheHash.get(hash);
@ -434,25 +459,12 @@ ChainDB.prototype.getEntry = co(function* getEntry(hash) {
if (entry)
return entry;
entry = yield this.db.get(layout.e(hash));
raw = yield this.db.get(layout.e(hash));
if (!entry)
if (!raw)
return;
return ChainEntry.fromRaw(this.chain, entry);
});
/**
* Retrieve a chain entry and add it to the LRU cache.
* @param {Hash} hash
* @returns {Promise} - Returns {@link ChainEntry}.
*/
ChainDB.prototype.get = co(function* get(hash) {
var entry = yield this.getEntry(hash);
if (!entry)
return;
entry = ChainEntry.fromRaw(this.chain, raw);
// There's no efficient way to check whether
// this is in the main chain or not, so
@ -462,6 +474,18 @@ ChainDB.prototype.get = co(function* get(hash) {
return entry;
});
/**
* Retrieve a chain entry.
* @param {Number|Hash} block - Height or hash.
* @returns {Promise} - Returns {@link ChainEntry}.
*/
ChainDB.prototype.getEntry = function getEntry(block) {
if (typeof block === 'number')
return this.getEntryByHeight(block);
return this.getEntryByHash(block);
};
/**
* Test whether the chain contains a block in the
* main chain or an alternate chain. Alternate chains will only
@ -470,7 +494,7 @@ ChainDB.prototype.get = co(function* get(hash) {
* @returns {Promise} - Returns Boolean.
*/
ChainDB.prototype.has = co(function* has(block) {
ChainDB.prototype.hasEntry = co(function* hasEntry(block) {
var item = yield this.getBoth(block);
return item.hash != null;
});
@ -481,7 +505,7 @@ ChainDB.prototype.has = co(function* has(block) {
*/
ChainDB.prototype.getTip = function getTip() {
return this.get(this.state.hash);
return this.getEntry(this.state.hash);
};
/**
@ -534,6 +558,8 @@ ChainDB.prototype.getNextHash = co(function* getNextHash(hash) {
*/
ChainDB.prototype.isMainChain = co(function* isMainChain(hash) {
var entry;
assert(typeof hash === 'string');
if (hash === this.chain.tip.hash
@ -541,6 +567,17 @@ ChainDB.prototype.isMainChain = co(function* isMainChain(hash) {
return true;
}
if (hash === constants.NULL_HASH)
return false;
entry = this.cacheHash.get(hash);
if (entry) {
entry = this.cacheHeight.get(entry.height);
if (entry)
return entry.hash === hash;
}
if (yield this.getNextHash(hash))
return true;
@ -1373,7 +1410,7 @@ ChainDB.prototype.saveUpdates = function saveUpdates() {
*/
ChainDB.prototype.reset = co(function* reset(block) {
var entry = yield this.get(block);
var entry = yield this.getEntry(block);
var tip;
if (!entry)
@ -1436,7 +1473,7 @@ ChainDB.prototype.reset = co(function* reset(block) {
this.cacheHeight.remove(tip.height);
this.cacheHash.remove(tip.hash);
tip = yield this.get(tip.prevBlock);
tip = yield this.getEntry(tip.prevBlock);
assert(tip);
}
@ -1475,7 +1512,7 @@ ChainDB.prototype.removeChains = co(function* removeChains() {
*/
ChainDB.prototype._removeChain = co(function* removeChain(hash) {
var tip = yield this.get(hash);
var tip = yield this.getEntry(hash);
if (!tip)
throw new Error('Alternate chain tip not found.');
@ -1498,7 +1535,7 @@ ChainDB.prototype._removeChain = co(function* removeChain(hash) {
// on successful write.
this.cacheHash.unpush(tip.hash);
tip = yield this.get(tip.prevBlock);
tip = yield this.getEntry(tip.prevBlock);
assert(tip);
}
});
@ -2140,11 +2177,6 @@ function getSize(value) {
return 80 + value.length;
}
function checkHash(hash) {
assert(typeof hash === 'string' || typeof hash === 'number',
'Must pass in height or hash.');
}
function BlockPair(hash, height) {
this.hash = hash;
this.height = height;

View File

@ -191,8 +191,6 @@ ChainEntry.prototype.getAncestors = co(function* getAncestors(max) {
assert(util.isNumber(max));
// Try to do this iteratively and synchronously
// so we don't have to wait on nextTicks.
for (;;) {
ancestors.push(entry);
@ -247,61 +245,38 @@ ChainEntry.prototype.isMainChain = co(function* isMainChain() {
});
/**
* Collect ancestors up to `height`.
* Get ancestor by `height`.
* @param {Number} height
* @returns {Promise} - Returns ChainEntry[].
*/
ChainEntry.prototype.getAncestorByHeight = co(function* getAncestorByHeight(height) {
var entry;
ChainEntry.prototype.getAncestor = co(function* getAncestor(height) {
var entry = this;
if (height < 0)
return yield co.wait();
return;
assert(height >= 0);
assert(height <= this.height);
if (yield this.isMainChain())
return yield this.chain.db.get(height);
return yield this.chain.db.getEntry(height);
entry = yield this.getAncestor(this.height - height);
if (!entry)
return;
assert(entry.height === height);
while (entry.height !== height) {
entry = yield entry.getPrevious();
assert(entry);
}
return entry;
});
/**
* Get a single ancestor by index. Note that index-0 is
* the same entry. This is done for sane porting of
* bitcoind functions to BCoin.
* @param {Number} index
* @returns {Function} callback - Returns [Error, ChainEntry].
*/
ChainEntry.prototype.getAncestor = co(function* getAncestor(index) {
var ancestors;
assert(index >= 0);
ancestors = yield this.getAncestors(index + 1);
if (ancestors.length < index + 1)
return;
return ancestors[index];
});
/**
* Get previous entry.
* @returns {Promise} - Returns ChainEntry.
*/
ChainEntry.prototype.getPrevious = function getPrevious() {
return this.chain.db.get(this.prevBlock);
return this.chain.db.getEntry(this.prevBlock);
};
/**
@ -313,7 +288,25 @@ ChainEntry.prototype.getNext = co(function* getNext() {
var hash = yield this.chain.db.getNextHash(this.hash);
if (!hash)
return;
return yield this.chain.db.get(hash);
return yield this.chain.db.getEntry(hash);
});
/**
* Get next entry.
* @returns {Promise} - Returns ChainEntry.
*/
ChainEntry.prototype.getNextEntry = co(function* getNextEntry() {
var entry = yield this.chain.db.getEntry(this.height + 1);
if (!entry)
return;
// Not on main chain.
if (entry.prevBlock !== this.hash)
return;
return entry;
});
/**

View File

@ -738,7 +738,7 @@ RPC.prototype.getblock = co(function* getblock(args) {
if (args.length > 1)
verbose = toBool(args[1]);
entry = yield this.chain.db.get(hash);
entry = yield this.chain.db.getEntry(hash);
if (!entry)
throw new RPCError('Block not found');
@ -836,7 +836,7 @@ RPC.prototype.getblockhash = co(function* getblockhash(args) {
if (height < 0 || height > this.chain.height)
throw new RPCError('Block height out of range.');
entry = yield this.chain.db.get(height);
entry = yield this.chain.db.getEntry(height);
if (!entry)
throw new RPCError('Not found.');
@ -860,7 +860,7 @@ RPC.prototype.getblockheader = co(function* getblockheader(args) {
if (args.length > 1)
verbose = toBool(args[1], true);
entry = yield this.chain.db.get(hash);
entry = yield this.chain.db.getEntry(hash);
if (!entry)
throw new RPCError('Block not found');
@ -935,7 +935,7 @@ RPC.prototype.getchaintips = co(function* getchaintips(args) {
for (i = 0; i < tips.length; i++) {
hash = tips[i];
entry = yield this.chain.db.get(hash);
entry = yield this.chain.db.getEntry(hash);
assert(entry);
fork = yield this._findFork(entry);
@ -1253,7 +1253,7 @@ RPC.prototype.verifytxoutproof = co(function* verifytxoutproof(args) {
if (!block.verify())
return res;
entry = yield this.chain.db.get(block.hash('hex'));
entry = yield this.chain.db.getEntry(block.hash('hex'));
if (!entry)
throw new RPCError('Block not found in chain.');
@ -1848,7 +1848,7 @@ RPC.prototype._hashps = co(function* _hashps(lookup, height) {
var i, minTime, maxTime, workDiff, timeDiff, ps, entry;
if (height !== -1)
tip = yield this.chain.db.get(height);
tip = yield this.chain.db.getEntry(height);
if (!tip)
return 0;

View File

@ -1389,7 +1389,7 @@ HTTPServer.prototype._initIO = function _initIO() {
}
try {
entry = yield self.chain.db.get(block);
entry = yield self.chain.db.getEntry(block);
if (!(yield entry.isMainChain()))
entry = null;
} catch (e) {

View File

@ -1405,7 +1405,7 @@ Peer.prototype._handleGetHeaders = co(function* _handleGetHeaders(packet) {
}
if (hash)
entry = yield this.chain.db.get(hash);
entry = yield this.chain.db.getEntry(hash);
while (entry) {
headers.push(entry.toHeaders());

View File

@ -106,7 +106,7 @@ NodeClient.prototype.getTip = function getTip() {
*/
NodeClient.prototype.getEntry = co(function* getEntry(hash) {
var entry = yield this.node.chain.db.get(hash);
var entry = yield this.node.chain.db.getEntry(hash);
if (!entry)
return;

View File

@ -109,8 +109,8 @@ describe('Chain', function() {
assert(chain.tip.hash === block1.hash('hex'));
tip1 = yield chain.db.get(block1.hash('hex'));
tip2 = yield chain.db.get(block2.hash('hex'));
tip1 = yield chain.db.getEntry(block1.hash('hex'));
tip2 = yield chain.db.getEntry(block2.hash('hex'));
assert(tip1);
assert(tip2);
@ -135,7 +135,7 @@ describe('Chain', function() {
assert.equal(walletdb.state.height, chain.height);
assert.equal(chain.height, 11);
entry = yield chain.db.get(tip2.hash);
entry = yield chain.db.getEntry(tip2.hash);
assert(entry);
assert(chain.height === entry.height);
@ -178,7 +178,7 @@ describe('Chain', function() {
deleteCoins(block);
yield chain.add(block);
entry = yield chain.db.get(block.hash('hex'));
entry = yield chain.db.getEntry(block.hash('hex'));
assert(entry);
assert(chain.tip.hash === entry.hash);
@ -228,8 +228,8 @@ describe('Chain', function() {
assert.equal(balance.unconfirmed, 1250 * 1e8);
assert.equal(balance.confirmed, 750 * 1e8);
assert(wallet.account.receiveDepth >= 8);
assert(wallet.account.changeDepth >= 7);
assert(wallet.account.receiveDepth >= 7);
assert(wallet.account.changeDepth >= 6);
assert.equal(walletdb.state.height, chain.height);