walletdb: fix reorg handling.
This commit is contained in:
parent
908d2eb9a9
commit
aec3548b26
@ -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();
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user