walletdb: fix reorg handling.

This commit is contained in:
Christopher Jeffrey 2016-08-22 17:55:44 -07:00
parent 908d2eb9a9
commit aec3548b26
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
5 changed files with 120 additions and 91 deletions

View File

@ -939,19 +939,19 @@ Chain.prototype.disconnect = function disconnect(entry, callback) {
if (err)
return callback(err);
entry.getPrevious(function(err, entry) {
entry.getPrevious(function(err, prev) {
if (err)
return callback(err);
assert(entry);
assert(prev);
self.tip = entry;
self.height = entry.height;
self.tip = prev;
self.height = prev.height;
self.bestHeight = entry.height;
self.network.updateHeight(entry.height);
self.bestHeight = prev.height;
self.network.updateHeight(prev.height);
self.emit('tip', entry);
self.emit('tip', prev);
self.emit('disconnect', entry, block);
callback();

View File

@ -42,6 +42,7 @@ function Miner(options) {
this.options = options;
this.address = bcoin.address(this.options.address);
this.coinbaseFlags = this.options.coinbaseFlags || 'mined by bcoin';
this.version = null;
this.pool = options.pool;
this.chain = options.chain;
@ -168,7 +169,7 @@ Miner.prototype._close = function close(callback) {
* @param {Number?} version - Custom block version.
*/
Miner.prototype.start = function start(version) {
Miner.prototype.start = function start() {
var self = this;
this.stop();
@ -176,7 +177,7 @@ Miner.prototype.start = function start(version) {
this.running = true;
// Create a new block and start hashing
this.createBlock(version, function(err, attempt) {
this.createBlock(function(err, attempt) {
if (err)
return self.emit('error', err);
@ -241,35 +242,39 @@ Miner.prototype.stop = function stop() {
* @param {Function} callback - Returns [Error, {@link MinerBlock}].
*/
Miner.prototype.createBlock = function createBlock(version, callback) {
Miner.prototype.createBlock = function createBlock(tip, callback) {
var self = this;
var ts = Math.max(bcoin.now(), this.chain.tip.ts + 1);
var i, attempt, txs, tx;
var i, ts, attempt, txs, tx;
if (typeof version === 'function') {
callback = version;
version = null;
if (typeof tip === 'function') {
callback = tip;
tip = null;
}
if (!tip)
tip = this.chain.tip;
ts = Math.max(bcoin.now(), tip.ts + 1);
function computeVersion(callback) {
if (version != null)
return callback(null, version);
self.chain.computeBlockVersion(self.chain.tip, callback);
if (self.version != null)
return callback(null, self.version);
self.chain.computeBlockVersion(tip, callback);
}
if (!this.loaded) {
this.open(function(err) {
if (err)
return callback(err);
self.createBlock(version, callback);
self.createBlock(tip, callback);
});
return;
}
assert(this.chain.tip);
assert(tip);
// Find target
this.chain.getTargetAsync(ts, this.chain.tip, function(err, target) {
this.chain.getTargetAsync(ts, tip, function(err, target) {
if (err)
return callback(err);
@ -280,7 +285,7 @@ Miner.prototype.createBlock = function createBlock(version, callback) {
attempt = new MinerBlock({
workerPool: self.workerPool,
tip: self.chain.tip,
tip: tip,
version: version,
target: target,
address: self.address,
@ -311,14 +316,14 @@ Miner.prototype.createBlock = function createBlock(version, callback) {
* @param {Function} callback - Returns [Error, [{@link Block}]].
*/
Miner.prototype.mineBlock = function mineBlock(version, callback) {
if (typeof version === 'function') {
callback = version;
version = null;
Miner.prototype.mineBlock = function mineBlock(tip, callback) {
if (typeof tip === 'function') {
callback = tip;
tip = null;
}
// Create a new block and start hashing
this.createBlock(version, function(err, attempt) {
this.createBlock(tip, function(err, attempt) {
if (err)
return callback(err);
@ -564,6 +569,7 @@ MinerBlock.prototype.findNonce = function findNonce() {
while (block.nonce <= 0xffffffff) {
// Hash and test against the next target.
if (rcmp(utils.hash256(data), target) <= 0) {
this.coinbase.mutable = false;
this.block.mutable = false;
return true;
}

View File

@ -594,6 +594,9 @@ TXDB.prototype._verify = function _verify(tx, info, callback) {
return callback(null, false);
}
self.logger.warning('Removing conflicting tx: %s.',
utils.revHex(spent.hash));
self._removeConflict(spent.hash, tx, function(err, tx, info) {
if (err)
return next(err);
@ -1226,7 +1229,7 @@ TXDB.prototype.unconfirm = function unconfirm(hash, callback, force) {
return callback(err);
if (!tx)
return callback(null, true);
return callback(null, false);
self.getInfo(tx, function(err, info) {
if (err)

View File

@ -1374,7 +1374,7 @@ WalletDB.prototype.unwriteBlock = function unwriteBlock(block, callback) {
*/
WalletDB.prototype.getBlock = function getBlock(hash, callback) {
this.db.fetch(layout.b(hash), function(err, data) {
this.db.fetch(layout.b(hash), function(data) {
return WalletBlock.fromRaw(hash, data);
}, callback);
};
@ -1438,6 +1438,11 @@ WalletDB.prototype.addBlock = function addBlock(entry, txs, callback) {
if (err)
return callback(err);
if (block.hashes.length > 0) {
self.logger.info('Connecting block %s (%d txs).',
utils.revHex(block.hash), block.hashes.length);
}
self.writeBlock(block, matches, callback);
});
};
@ -1451,12 +1456,15 @@ WalletDB.prototype.addBlock = function addBlock(entry, txs, callback) {
WalletDB.prototype.removeBlock = function removeBlock(entry, callback) {
var self = this;
var block;
callback = this._lockTX(removeBlock, [entry, callback]);
if (!callback)
return;
block = WalletBlock.fromEntry(entry);
// Note:
// If we crash during a reorg, there's not much to do.
// Reorgs cannot be rescanned. The database will be
@ -1464,14 +1472,17 @@ WalletDB.prototype.removeBlock = function removeBlock(entry, callback) {
// when they shouldn't be. That being said, this
// should eventually resolve itself when a new block
// comes in.
this.getBlock(entry.hash, function(err, block) {
this.getBlock(block.hash, function(err, data) {
if (err)
return callback(err);
// Not a saved block, but we
// still want to reset the tip.
if (!block)
block = WalletBlock.fromEntry(entry);
if (data)
block.hashes = data.hashes;
if (block.hashes.length > 0) {
self.logger.warning('Disconnecting block %s (%d txs).',
utils.revHex(block.hash), block.hashes.length);
}
// Unwrite the tip as fast as we can.
self.unwriteBlock(block, function(err) {
@ -1503,10 +1514,10 @@ WalletDB.prototype.removeBlock = function removeBlock(entry, callback) {
self.tip = block.hash;
self.height = block.height;
callback();
next();
});
});
});
}, callback);
});
});
};
@ -2128,21 +2139,21 @@ WalletBlock.fromJSON = function fromJSON(json) {
};
WalletBlock.fromRaw = function fromRaw(hash, data) {
return new WalletBlock().fromTip(hash, data);
return new WalletBlock().fromRaw(hash, data);
};
WalletBlock.fromTip = function fromTip(data) {
return new WalletBlock().fromTip(data);
};
WalletBlock.prototype.toTip = function toTip(data) {
WalletBlock.prototype.toTip = function toTip() {
var p = new BufferWriter();
p.writeHash(this.hash);
p.writeU32(this.height);
return p.render();
};
WalletBlock.prototype.toRaw = function toRaw(data) {
WalletBlock.prototype.toRaw = function toRaw() {
var p = new BufferWriter();
var i;

View File

@ -10,38 +10,28 @@ var opcodes = constants.opcodes;
constants.tx.COINBASE_MATURITY = 0;
describe('Chain', function() {
var chain, wallet, miner, walletdb;
var competingTip, oldTip, ch1, ch2, cb1, cb2;
var chain, wallet, node, miner, walletdb;
var competingTip, oldTip, tip1, tip2, cb1, cb2;
this.timeout(5000);
chain = new bcoin.chain({ name: 'chain-test', db: 'memory' });
walletdb = new bcoin.walletdb({ name: 'chain-test-wdb', db: 'memory' });
miner = new bcoin.miner({
chain: chain
});
node = new bcoin.fullnode({ db: 'memory' });
chain = node.chain;
walletdb = node.walletdb;
miner = node.miner;
node.on('error', function() {});
chain.on('error', function() {});
miner.on('error', function() {});
function mineBlock(entry, tx, callback) {
var realTip;
if (entry) {
realTip = chain.tip;
chain.tip = entry;
}
miner.createBlock(function(err, attempt) {
if (realTip)
chain.tip = realTip;
function mineBlock(tip, tx, callback) {
miner.createBlock(tip, function(err, attempt) {
assert.ifError(err);
if (tx) {
var redeemer = bcoin.mtx();
redeemer.addOutput({
address: wallet.getAddress(),
address: wallet.receiveAddress.getAddress(),
value: utils.satoshi('25.0')
});
redeemer.addOutput({
address: wallet.account.deriveAddress(false, 100).getAddress(),
address: wallet.changeAddress.getAddress(),
value: utils.satoshi('5.0')
});
redeemer.addInput(tx, 0);
@ -71,18 +61,16 @@ describe('Chain', function() {
}
it('should open chain and miner', function(cb) {
miner.open(cb);
miner.mempool = null;
node.open(cb);
});
it('should open walletdb', function(cb) {
walletdb.open(function(err) {
walletdb.create({}, function(err, w) {
assert.ifError(err);
walletdb.create({}, function(err, w) {
assert.ifError(err);
wallet = w;
miner.address = wallet.getAddress();
cb();
});
wallet = w;
miner.address = wallet.getAddress();
cb();
});
});
@ -96,29 +84,29 @@ describe('Chain', function() {
it('should mine competing chains', function(cb) {
utils.forRangeSerial(0, 10, function(i, next) {
mineBlock(ch1, cb1, function(err, chain1) {
mineBlock(tip1, cb1, function(err, block1) {
assert.ifError(err);
cb1 = chain1.txs[0];
mineBlock(ch2, cb2, function(err, chain2) {
cb1 = block1.txs[0];
mineBlock(tip2, cb2, function(err, block2) {
assert.ifError(err);
cb2 = chain2.txs[0];
deleteCoins(chain1);
chain.add(chain1, function(err) {
cb2 = block2.txs[0];
deleteCoins(block1);
chain.add(block1, function(err) {
assert.ifError(err);
deleteCoins(chain2);
chain.add(chain2, function(err) {
deleteCoins(block2);
chain.add(block2, function(err) {
assert.ifError(err);
assert(chain.tip.hash === chain1.hash('hex'));
competingTip = chain2.hash('hex');
chain.db.get(chain1.hash('hex'), function(err, entry1) {
assert(chain.tip.hash === block1.hash('hex'));
competingTip = block2.hash('hex');
chain.db.get(block1.hash('hex'), function(err, entry1) {
assert.ifError(err);
chain.db.get(chain2.hash('hex'), function(err, entry2) {
chain.db.get(block2.hash('hex'), function(err, entry2) {
assert.ifError(err);
assert(entry1);
assert(entry2);
ch1 = entry1;
ch2 = entry2;
chain.db.isMainChain(chain2.hash('hex'), function(err, result) {
tip1 = entry1;
tip2 = entry2;
chain.db.isMainChain(block2.hash('hex'), function(err, result) {
assert.ifError(err);
assert(!result);
next();
@ -133,25 +121,25 @@ describe('Chain', function() {
});
it('should handle a reorg', function(cb) {
assert.equal(walletdb.height, chain.height);
assert.equal(chain.height, 10);
oldTip = chain.tip;
chain.db.get(competingTip, function(err, entry) {
assert.ifError(err);
assert(entry);
assert(chain.height === entry.height);
chain.tip = entry;
miner.mineBlock(function(err, reorg) {
miner.mineBlock(entry, function(err, block) {
assert.ifError(err);
assert(reorg);
chain.tip = oldTip;
assert(block);
var forked = false;
chain.once('reorganize', function() {
forked = true;
});
deleteCoins(reorg);
chain.add(reorg, function(err) {
deleteCoins(block);
chain.add(block, function(err) {
assert.ifError(err);
assert(forked);
assert(chain.tip.hash === reorg.hash('hex'));
assert(chain.tip.hash === block.hash('hex'));
assert(chain.tip.chainwork.cmp(oldTip.chainwork) > 0);
cb();
});
@ -209,7 +197,8 @@ describe('Chain', function() {
assert.ifError(err);
chain.db.getCoin(block.txs[1].hash('hex'), 1, function(err, coin) {
assert.ifError(err);
assert.deepEqual(coin.toRaw(), bcoin.coin.fromTX(block.txs[1], 1).toRaw());
var output = bcoin.coin.fromTX(block.txs[1], 1);
assert.deepEqual(coin.toRaw(), output.toRaw());
cb();
});
});
@ -218,6 +207,26 @@ describe('Chain', function() {
});
});
it('should get balance', function(cb) {
setTimeout(function() {
wallet.getBalance(function(err, balance) {
assert.ifError(err);
assert.equal(balance.unconfirmed, 23000000000);
assert.equal(balance.confirmed, 97000000000);
assert.equal(balance.total, 120000000000);
assert.equal(wallet.account.receiveDepth, 8);
assert.equal(wallet.account.changeDepth, 7);
assert.equal(walletdb.height, chain.height);
assert.equal(walletdb.tip, chain.tip.hash);
wallet.getHistory(function(err, txs) {
assert.ifError(err);
assert.equal(txs.length, 44);
cb();
});
});
}, 100);
});
it('should rescan for transactions', function(cb) {
var txs = [];
walletdb.getAddressHashes(function(err, hashes) {