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) if (err)
return callback(err); return callback(err);
entry.getPrevious(function(err, entry) { entry.getPrevious(function(err, prev) {
if (err) if (err)
return callback(err); return callback(err);
assert(entry); assert(prev);
self.tip = entry; self.tip = prev;
self.height = entry.height; self.height = prev.height;
self.bestHeight = entry.height; self.bestHeight = prev.height;
self.network.updateHeight(entry.height); self.network.updateHeight(prev.height);
self.emit('tip', entry); self.emit('tip', prev);
self.emit('disconnect', entry, block); self.emit('disconnect', entry, block);
callback(); callback();

View File

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

View File

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

View File

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

View File

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