walletdb best hash. fix spv chain.

This commit is contained in:
Christopher Jeffrey 2016-07-06 16:06:11 -07:00
parent 80d8c2b3c8
commit a3bb955d95
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
7 changed files with 164 additions and 98 deletions

View File

@ -45,13 +45,13 @@ var VerifyError = bcoin.errors.VerifyError;
* @emits Chain#resolved * @emits Chain#resolved
* @emits Chain#checkpoint * @emits Chain#checkpoint
* @emits Chain#fork * @emits Chain#fork
* @emits Chain#reorganize
* @emits Chain#invalid * @emits Chain#invalid
* @emits Chain#exists * @emits Chain#exists
* @emits Chain#purge * @emits Chain#purge
* @emits Chain#add entry * @emits Chain#connect
* @emits Chain#remove entry * @emits Chain#reconnect
* @emits Chain#add block * @emits Chain#disconnect
* @emits Chain#remove block
*/ */
function Chain(options) { function Chain(options) {
@ -152,6 +152,15 @@ Chain.prototype._init = function _init() {
); );
}); });
this.on('reorganize', function(block, height, expected) {
self.logger.warning(
'Reorg at height %d: old=%s new=%s',
height,
utils.revHex(expected),
block.rhash
);
});
this.on('invalid', function(block, height) { this.on('invalid', function(block, height) {
self.logger.warning('Invalid block at height %d: hash=%s', self.logger.warning('Invalid block at height %d: hash=%s',
height, block.rhash); height, block.rhash);
@ -169,22 +178,6 @@ Chain.prototype._init = function _init() {
self.logger.debug('Warning: %d (%dmb) orphans cleared!', self.logger.debug('Warning: %d (%dmb) orphans cleared!',
count, utils.mb(size)); count, utils.mb(size));
}); });
this.db.on('add entry', function(entry) {
self.emit('add entry', entry);
});
this.db.on('remove entry', function(entry) {
self.emit('remove entry', entry);
});
this.db.on('add block', function(block) {
self.emit('add block', block);
});
this.db.on('remove block', function(block) {
self.emit('remove block', block);
});
}; };
/** /**
@ -1017,7 +1010,7 @@ Chain.prototype.reorganize = function reorganize(entry, block, callback) {
} }
// Connect blocks/txs. // Connect blocks/txs.
function connect(callback) { function reconnect(callback) {
var entries = []; var entries = [];
(function collect(entry) { (function collect(entry) {
@ -1044,7 +1037,7 @@ Chain.prototype.reorganize = function reorganize(entry, block, callback) {
entries.pop(); entries.pop();
utils.forEachSerial(entries, function(entry, next) { utils.forEachSerial(entries, function(entry, next) {
self.connect(entry, next); self.reconnect(entry, next);
}, callback); }, callback);
} }
} }
@ -1053,11 +1046,11 @@ Chain.prototype.reorganize = function reorganize(entry, block, callback) {
if (err) if (err)
return callback(err); return callback(err);
return connect(function(err) { return reconnect(function(err) {
if (err) if (err)
return callback(err); return callback(err);
self.emit('fork', block, tip.height, tip.hash); self.emit('reorganize', block, tip.height, tip.hash);
return callback(); return callback();
}); });
@ -1074,7 +1067,7 @@ Chain.prototype.reorganize = function reorganize(entry, block, callback) {
Chain.prototype.disconnect = function disconnect(entry, callback) { Chain.prototype.disconnect = function disconnect(entry, callback) {
var self = this; var self = this;
this.db.disconnect(entry, function(err) { this.db.disconnect(entry, function(err, entry, block) {
if (err) if (err)
return callback(err); return callback(err);
@ -1091,6 +1084,7 @@ Chain.prototype.disconnect = function disconnect(entry, callback) {
self.network.updateHeight(entry.height); self.network.updateHeight(entry.height);
self.emit('tip', entry); self.emit('tip', entry);
self.emit('disconnect', entry, block);
return callback(); return callback();
}); });
@ -1098,7 +1092,7 @@ Chain.prototype.disconnect = function disconnect(entry, callback) {
}; };
/** /**
* Connect an entry to the chain (updates the tip). * Reconnect an entry to the chain (updates the tip).
* This will do contextual-verification on the block * This will do contextual-verification on the block
* (necessary because we cannot validate the inputs * (necessary because we cannot validate the inputs
* in alternate chains when they come in). * in alternate chains when they come in).
@ -1106,14 +1100,17 @@ Chain.prototype.disconnect = function disconnect(entry, callback) {
* @param {Function} callback * @param {Function} callback
*/ */
Chain.prototype.connect = function connect(entry, callback) { Chain.prototype.reconnect = function reconnect(entry, callback) {
var self = this; var self = this;
this.db.getBlock(entry.hash, function(err, block) { this.db.getBlock(entry.hash, function(err, block) {
if (err) if (err)
return callback(err); return callback(err);
assert(block); if (!block) {
assert(self.options.spv);
block = entry.toHeaders();
}
entry.getPrevious(function(err, prev) { entry.getPrevious(function(err, prev) {
if (err) if (err)
@ -1130,7 +1127,7 @@ Chain.prototype.connect = function connect(entry, callback) {
return callback(err); return callback(err);
} }
self.db.connect(entry, block, view, function(err) { self.db.reconnect(entry, block, view, function(err) {
if (err) if (err)
return callback(err); return callback(err);
@ -1141,6 +1138,8 @@ Chain.prototype.connect = function connect(entry, callback) {
self.network.updateHeight(entry.height); self.network.updateHeight(entry.height);
self.emit('tip', entry); self.emit('tip', entry);
self.emit('reconnect', entry, block);
self.emit('connect', entry, block);
return callback(); return callback();
}); });
@ -1516,6 +1515,7 @@ Chain.prototype.add = function add(block, callback, force) {
// Emit our block (and potentially resolved // Emit our block (and potentially resolved
// orphan) only if it is on the main chain. // orphan) only if it is on the main chain.
self.emit('block', block, entry); self.emit('block', block, entry);
self.emit('connect', entry, block);
if (!initial) if (!initial)
self.emit('resolved', block, entry); self.emit('resolved', block, entry);

View File

@ -161,10 +161,6 @@ var layout = {
* @property {Number} keepBlocks * @property {Number} keepBlocks
* @emits ChainDB#open * @emits ChainDB#open
* @emits ChainDB#error * @emits ChainDB#error
* @emits ChainDB#add block
* @emits ChainDB#remove block
* @emits ChainDB#add entry
* @emits ChainDB#remove entry
*/ */
function ChainDB(chain, options) { function ChainDB(chain, options) {
@ -501,6 +497,7 @@ ChainDB.prototype.get = function get(hash, callback) {
* instead performed in {@link Chain#add}. * instead performed in {@link Chain#add}.
* @param {ChainEntry} entry * @param {ChainEntry} entry
* @param {Block} block * @param {Block} block
* @param {CoinView} view
* @param {Boolean} connect - Whether to connect the * @param {Boolean} connect - Whether to connect the
* block's inputs and add it as a tip. * block's inputs and add it as a tip.
* @param {Function} callback * @param {Function} callback
@ -532,8 +529,6 @@ ChainDB.prototype.save = function save(entry, block, view, connect, callback) {
batch.put(layout.H(entry.height), hash); batch.put(layout.H(entry.height), hash);
batch.put(layout.R, hash); batch.put(layout.R, hash);
this.emit('add entry', entry);
this.saveBlock(block, view, batch, true, function(err) { this.saveBlock(block, view, batch, true, function(err) {
if (err) if (err)
return callback(err); return callback(err);
@ -563,12 +558,15 @@ ChainDB.prototype.getTip = function getTip(callback) {
}; };
/** /**
* Connect the block to the chain. * Reconnect the block to the chain.
* @param {ChainEntry|Hash|Height} block - entry, height, or hash. * @param {ChainEntry} entry
* @param {Function} callback - Returns [Error, {@link ChainEntry}]. * @param {Block} block
* @param {CoinView} view
* @param {Function} callback -
* Returns [Error, {@link ChainEntry}, {@link Block}].
*/ */
ChainDB.prototype.connect = function connect(entry, block, view, callback) { ChainDB.prototype.reconnect = function reconnect(entry, block, view, callback) {
var batch = this.db.batch(); var batch = this.db.batch();
var hash = block.hash(); var hash = block.hash();
@ -579,7 +577,13 @@ ChainDB.prototype.connect = function connect(entry, block, view, callback) {
this.cacheHash.set(entry.hash, entry); this.cacheHash.set(entry.hash, entry);
this.cacheHeight.set(entry.height, entry); this.cacheHeight.set(entry.height, entry);
this.emit('add entry', entry); if (this.options.spv) {
return batch.write(function(err) {
if (err)
return callback(err);
return callback(null, entry, block);
});
}
this.connectBlock(block, view, batch, function(err) { this.connectBlock(block, view, batch, function(err) {
if (err) if (err)
@ -588,15 +592,16 @@ ChainDB.prototype.connect = function connect(entry, block, view, callback) {
batch.write(function(err) { batch.write(function(err) {
if (err) if (err)
return callback(err); return callback(err);
return callback(null, entry); return callback(null, entry, block);
}); });
}); });
}; };
/** /**
* Disconnect block from the chain. * Disconnect block from the chain.
* @param {ChainEntry|Hash|Height} block - Entry, height, or hash. * @param {ChainEntry} entry
* @param {Function} callback - Returns [Error, {@link ChainEntry}]. * @param {Function} callback -
* Returns [Error, {@link ChainEntry}, {@link Block}].
*/ */
ChainDB.prototype.disconnect = function disconnect(entry, callback) { ChainDB.prototype.disconnect = function disconnect(entry, callback) {
@ -609,7 +614,13 @@ ChainDB.prototype.disconnect = function disconnect(entry, callback) {
this.cacheHeight.remove(entry.height); this.cacheHeight.remove(entry.height);
this.emit('remove entry', entry); if (this.options.spv) {
return batch.write(function(err) {
if (err)
return callback(err);
return callback(null, entry, entry.toHeaders());
});
}
this.getBlock(entry.hash, function(err, block) { this.getBlock(entry.hash, function(err, block) {
if (err) if (err)
@ -625,7 +636,7 @@ ChainDB.prototype.disconnect = function disconnect(entry, callback) {
batch.write(function(err) { batch.write(function(err) {
if (err) if (err)
return callback(err); return callback(err);
return callback(null, entry); return callback(null, entry, block);
}); });
}); });
}); });
@ -724,8 +735,6 @@ ChainDB.prototype.reset = function reset(block, callback) {
batch.del(layout.e(tip.hash)); batch.del(layout.e(tip.hash));
batch.del(layout.n(tip.prevBlock)); batch.del(layout.n(tip.prevBlock));
self.emit('remove entry', tip);
self.removeBlock(tip.hash, batch, function(err) { self.removeBlock(tip.hash, batch, function(err) {
if (err) if (err)
return callback(err); return callback(err);
@ -819,10 +828,8 @@ ChainDB.prototype.connectBlock = function connectBlock(block, view, batch, callb
var undo = new BufferWriter(); var undo = new BufferWriter();
var i, j, tx, input, output, prev, addresses, address, hash, coins, raw; var i, j, tx, input, output, prev, addresses, address, hash, coins, raw;
if (this.options.spv) { if (this.options.spv)
this.emit('add block', block);
return utils.asyncify(callback)(null, block); return utils.asyncify(callback)(null, block);
}
// Genesis block's coinbase is unspendable. // Genesis block's coinbase is unspendable.
if (this.chain.isGenesis(block)) if (this.chain.isGenesis(block))
@ -893,8 +900,6 @@ ChainDB.prototype.connectBlock = function connectBlock(block, view, batch, callb
if (undo.written > 0) if (undo.written > 0)
batch.put(layout.u(block.hash()), undo.render()); batch.put(layout.u(block.hash()), undo.render());
this.emit('add block', block);
this._pruneBlock(block, batch, function(err) { this._pruneBlock(block, batch, function(err) {
if (err) if (err)
return callback(err); return callback(err);
@ -990,8 +995,6 @@ ChainDB.prototype.disconnectBlock = function disconnectBlock(block, batch, callb
batch.del(layout.u(block.hash())); batch.del(layout.u(block.hash()));
self.emit('remove block', block);
return callback(null, block); return callback(null, block);
}); });
}; };

View File

@ -179,35 +179,34 @@ Fullnode.prototype._init = function _init() {
self.emit('alert', details); self.emit('alert', details);
}); });
this.on('tx', function(tx) { this.mempool.on('tx', function(tx) {
self.emit('tx', tx);
self.walletdb.addTX(tx, function(err) { self.walletdb.addTX(tx, function(err) {
if (err) if (err)
self._error(err); self._error(err);
}); });
}); });
// Emit events for valid blocks and TXs.
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) { });
self.emit('tx', tx, block);
this.chain.on('connect', function(entry, block) {
self.walletdb.addBlock(block, function(err) {
if (err)
self._error(err);
}); });
});
this.mempool.on('tx', function(tx) {
self.emit('tx', tx);
});
this.chain.on('add block', function(block) {
if (!self.chain.isFull()) if (!self.chain.isFull())
return; return;
self.mempool.addBlock(block, function(err) { self.mempool.addBlock(block, function(err) {
if (err) if (err)
self._error(err); self._error(err);
}); });
}); });
this.chain.on('remove block', function(block) { this.chain.on('disconnect', function(entry, block) {
self.walletdb.removeBlock(block, function(err) { self.walletdb.removeBlock(block, function(err) {
if (err) if (err)
self._error(err); self._error(err);

View File

@ -119,27 +119,17 @@ SPVNode.prototype._init = function _init() {
self.emit('alert', details); self.emit('alert', details);
}); });
this.on('tx', function(tx) { this.pool.on('tx', function(tx) {
self.emit('tx', tx);
self.walletdb.addTX(tx, function(err) { self.walletdb.addTX(tx, function(err) {
if (err) if (err)
self._error(err); self._error(err);
}); });
}); });
// Emit events for valid blocks and TXs.
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) { self.walletdb.addBlock(block, function(err) {
self.emit('tx', tx, block);
});
});
this.pool.on('tx', function(tx) {
self.emit('tx', tx);
});
this.chain.on('remove entry', function(entry) {
self.walletdb.removeBlockSPV(entry, function(err) {
if (err) if (err)
self._error(err); self._error(err);
}); });

View File

@ -281,6 +281,93 @@ TXDB.prototype._getOrphans = function _getOrphans(key, callback) {
}); });
}; };
/**
* Add a block's transactions and write the new best hash.
* @param {Block} block
* @param {Function} callback
*/
TXDB.prototype.addBlock = function addBlock(block, callback, force) {
var self = this;
var unlock;
unlock = this._lock(addBlock, [block, callback], force);
if (!unlock)
return;
callback = utils.wrap(callback, unlock);
utils.forEachSerial(block.txs, function(tx, next) {
self.add(tx, next, true);
}, function(err) {
if (err)
return callback(err);
self.db.put('R', block.hash(), callback);
});
};
/**
* Unconfirm a block's transactions and write the new best hash.
* @param {Block} block
* @param {Function} callback
*/
TXDB.prototype.removeBlock = function removeBlock(block, callback, force) {
var self = this;
var unlock;
unlock = this._lock(removeBlock, [block, callback], force);
if (!unlock)
return;
callback = utils.wrap(callback, unlock);
utils.forEachSerial(block.txs, function(tx, next) {
self.unconfirm(tx.hash('hex'), next, true);
}, function(err) {
if (err)
return callback(err);
self.db.put('R', new Buffer(block.prevBlock, 'hex'), callback)
});
};
/**
* Unconfirm a block's transactions
* and write the new best hash (SPV version).
* @param {Block} block
* @param {Function} callback
*/
TXDB.prototype.removeBlockSPV = function removeBlockSPV(block, callback, force) {
var self = this;
var unlock;
unlock = this._lock(removeBlock, [block, callback], force);
if (!unlock)
return;
callback = utils.wrap(callback, unlock);
this.tx.getHeightHashes(block.height, function(err, hashes) {
if (err)
return callback(err);
utils.forEachSerial(hashes, function(hash, next) {
self.unconfirm(hash, next, true);
}, function(err) {
if (err)
return callback(err);
self.db.put('R', new Buffer(block.prevBlock, 'hex'), callback)
});
});
};
/** /**
* Add a transaction to the database, map addresses * Add a transaction to the database, map addresses
* to wallet IDs, potentially store orphans, resolve * to wallet IDs, potentially store orphans, resolve

View File

@ -1084,36 +1084,23 @@ WalletDB.prototype._getKey = function _getKey(id, account, errback, callback) {
}; };
/** /**
* Notify the database that a block has been * Add a block's transactions and write the new best hash.
* removed (reorg). Unconfirms transactions by height. * @param {Block} block
* @param {MerkleBlock|Block} block
* @param {Function} callback * @param {Function} callback
*/ */
WalletDB.prototype.removeBlockSPV = function removeBlockSPV(block, callback) { WalletDB.prototype.addBlock = function addBlock(block, callback) {
var self = this; this.tx.addBlock(block, callback);
this.tx.getHeightHashes(block.height, function(err, hashes) {
if (err)
return callback(err);
utils.forEachSerial(hashes, function(hash, next) {
self.tx.unconfirm(hash, next);
}, callback);
});
}; };
/** /**
* Notify the database that a block has been * Unconfirm a block's transactions and write the new best hash.
* removed (reorg). Unconfirms transactions.
* @param {Block} block * @param {Block} block
* @param {Function} callback * @param {Function} callback
*/ */
WalletDB.prototype.removeBlock = function removeBlock(block, callback) { WalletDB.prototype.removeBlock = function removeBlock(block, callback) {
var self = this; this.tx.removeBlock(block, callback);
utils.forEachSerial(block.txs, function(tx, next) {
self.tx.unconfirm(tx.hash('hex'), next);
}, callback);
}; };
/** /**

View File

@ -144,7 +144,7 @@ describe('Chain', function() {
assert(reorg); assert(reorg);
chain.tip = oldTip; chain.tip = oldTip;
var forked = false; var forked = false;
chain.once('fork', function() { chain.once('reorganize', function() {
forked = true; forked = true;
}); });
deleteCoins(reorg); deleteCoins(reorg);