walletdb best hash. fix spv chain.
This commit is contained in:
parent
80d8c2b3c8
commit
a3bb955d95
@ -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);
|
||||
|
||||
@ -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);
|
||||
});
|
||||
};
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
});
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@ -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);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user