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#checkpoint
* @emits Chain#fork
* @emits Chain#reorganize
* @emits Chain#invalid
* @emits Chain#exists
* @emits Chain#purge
* @emits Chain#add entry
* @emits Chain#remove entry
* @emits Chain#add block
* @emits Chain#remove block
* @emits Chain#connect
* @emits Chain#reconnect
* @emits Chain#disconnect
*/
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) {
self.logger.warning('Invalid block at height %d: hash=%s',
height, block.rhash);
@ -169,22 +178,6 @@ Chain.prototype._init = function _init() {
self.logger.debug('Warning: %d (%dmb) orphans cleared!',
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.
function connect(callback) {
function reconnect(callback) {
var entries = [];
(function collect(entry) {
@ -1044,7 +1037,7 @@ Chain.prototype.reorganize = function reorganize(entry, block, callback) {
entries.pop();
utils.forEachSerial(entries, function(entry, next) {
self.connect(entry, next);
self.reconnect(entry, next);
}, callback);
}
}
@ -1053,11 +1046,11 @@ Chain.prototype.reorganize = function reorganize(entry, block, callback) {
if (err)
return callback(err);
return connect(function(err) {
return reconnect(function(err) {
if (err)
return callback(err);
self.emit('fork', block, tip.height, tip.hash);
self.emit('reorganize', block, tip.height, tip.hash);
return callback();
});
@ -1074,7 +1067,7 @@ Chain.prototype.reorganize = function reorganize(entry, block, callback) {
Chain.prototype.disconnect = function disconnect(entry, callback) {
var self = this;
this.db.disconnect(entry, function(err) {
this.db.disconnect(entry, function(err, entry, block) {
if (err)
return callback(err);
@ -1091,6 +1084,7 @@ Chain.prototype.disconnect = function disconnect(entry, callback) {
self.network.updateHeight(entry.height);
self.emit('tip', entry);
self.emit('disconnect', entry, block);
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
* (necessary because we cannot validate the inputs
* in alternate chains when they come in).
@ -1106,14 +1100,17 @@ Chain.prototype.disconnect = function disconnect(entry, callback) {
* @param {Function} callback
*/
Chain.prototype.connect = function connect(entry, callback) {
Chain.prototype.reconnect = function reconnect(entry, callback) {
var self = this;
this.db.getBlock(entry.hash, function(err, block) {
if (err)
return callback(err);
assert(block);
if (!block) {
assert(self.options.spv);
block = entry.toHeaders();
}
entry.getPrevious(function(err, prev) {
if (err)
@ -1130,7 +1127,7 @@ Chain.prototype.connect = function connect(entry, callback) {
return callback(err);
}
self.db.connect(entry, block, view, function(err) {
self.db.reconnect(entry, block, view, function(err) {
if (err)
return callback(err);
@ -1141,6 +1138,8 @@ Chain.prototype.connect = function connect(entry, callback) {
self.network.updateHeight(entry.height);
self.emit('tip', entry);
self.emit('reconnect', entry, block);
self.emit('connect', entry, block);
return callback();
});
@ -1516,6 +1515,7 @@ Chain.prototype.add = function add(block, callback, force) {
// Emit our block (and potentially resolved
// orphan) only if it is on the main chain.
self.emit('block', block, entry);
self.emit('connect', entry, block);
if (!initial)
self.emit('resolved', block, entry);

View File

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

View File

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

View File

@ -119,27 +119,17 @@ SPVNode.prototype._init = function _init() {
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) {
if (err)
self._error(err);
});
});
// Emit events for valid blocks and TXs.
this.chain.on('block', function(block) {
self.emit('block', block);
block.txs.forEach(function(tx) {
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) {
self.walletdb.addBlock(block, function(err) {
if (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
* 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
* removed (reorg). Unconfirms transactions by height.
* @param {MerkleBlock|Block} block
* Add a block's transactions and write the new best hash.
* @param {Block} block
* @param {Function} callback
*/
WalletDB.prototype.removeBlockSPV = function removeBlockSPV(block, callback) {
var self = this;
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);
});
WalletDB.prototype.addBlock = function addBlock(block, callback) {
this.tx.addBlock(block, callback);
};
/**
* Notify the database that a block has been
* removed (reorg). Unconfirms transactions.
* Unconfirm a block's transactions and write the new best hash.
* @param {Block} block
* @param {Function} callback
*/
WalletDB.prototype.removeBlock = function removeBlock(block, callback) {
var self = this;
utils.forEachSerial(block.txs, function(tx, next) {
self.tx.unconfirm(tx.hash('hex'), next);
}, callback);
this.tx.removeBlock(block, callback);
};
/**

View File

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