validate sidechain's inputs after a reorg instead of before.
This commit is contained in:
parent
8efef35828
commit
0d75c8a621
@ -753,9 +753,6 @@ Chain.prototype._checkInputs = function _checkInputs(block, prev, flags, callbac
|
||||
if (err)
|
||||
return callback(err);
|
||||
|
||||
if (block.getClaimed().cmp(block.getReward()) > 0)
|
||||
return callback(new VerifyError(block, 'invalid', 'bad-cb-amount', 100));
|
||||
|
||||
// Check all transactions
|
||||
for (i = 0; i < block.txs.length; i++) {
|
||||
tx = block.txs[i];
|
||||
@ -780,13 +777,6 @@ Chain.prototype._checkInputs = function _checkInputs(block, prev, flags, callbac
|
||||
if (tx.isCoinbase())
|
||||
continue;
|
||||
|
||||
if (!tx.checkInputs(height, ret)) {
|
||||
return callback(new VerifyError(block,
|
||||
'invalid',
|
||||
ret.reason,
|
||||
ret.score));
|
||||
}
|
||||
|
||||
for (j = 0; j < tx.inputs.length; j++) {
|
||||
input = tx.inputs[j];
|
||||
|
||||
@ -814,8 +804,18 @@ Chain.prototype._checkInputs = function _checkInputs(block, prev, flags, callbac
|
||||
100));
|
||||
}
|
||||
}
|
||||
|
||||
if (!tx.checkInputs(height, ret)) {
|
||||
return callback(new VerifyError(block,
|
||||
'invalid',
|
||||
ret.reason,
|
||||
ret.score));
|
||||
}
|
||||
}
|
||||
|
||||
if (block.getClaimed().cmp(block.getReward()) > 0)
|
||||
return callback(new VerifyError(block, 'invalid', 'bad-cb-amount', 100));
|
||||
|
||||
if (self.options.verifySync === true)
|
||||
return callback();
|
||||
|
||||
@ -915,8 +915,9 @@ Chain.prototype._findFork = function _findFork(fork, longer, callback) {
|
||||
|
||||
Chain.prototype._reorganize = function _reorganize(entry, block, callback) {
|
||||
var self = this;
|
||||
var tip = this.tip;
|
||||
|
||||
return this._findFork(this.tip, entry, function(err, fork) {
|
||||
return this._findFork(tip, entry, function(err, fork) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
|
||||
@ -926,6 +927,8 @@ Chain.prototype._reorganize = function _reorganize(entry, block, callback) {
|
||||
function disconnect(callback) {
|
||||
var entries = [];
|
||||
|
||||
entries.push(tip);
|
||||
|
||||
(function collect(entry) {
|
||||
entry.getPrevious(function(err, entry) {
|
||||
if (err)
|
||||
@ -933,20 +936,20 @@ Chain.prototype._reorganize = function _reorganize(entry, block, callback) {
|
||||
|
||||
assert(entry);
|
||||
|
||||
entries.push(entry);
|
||||
|
||||
if (entry.hash === fork.hash)
|
||||
return finish();
|
||||
|
||||
entries.push(entry);
|
||||
|
||||
collect(entry);
|
||||
});
|
||||
})(self.tip);
|
||||
})(tip);
|
||||
|
||||
function finish() {
|
||||
assert(entries.length > 0);
|
||||
|
||||
utils.forEachSerial(entries, function(entry, next) {
|
||||
self.db.disconnect(entry, next);
|
||||
self.disconnect(entry, next);
|
||||
}, callback);
|
||||
}
|
||||
}
|
||||
@ -962,11 +965,11 @@ Chain.prototype._reorganize = function _reorganize(entry, block, callback) {
|
||||
|
||||
assert(entry);
|
||||
|
||||
entries.push(entry);
|
||||
|
||||
if (entry.hash === fork.hash)
|
||||
return finish();
|
||||
|
||||
entries.push(entry);
|
||||
|
||||
collect(entry);
|
||||
});
|
||||
})(entry);
|
||||
@ -976,7 +979,7 @@ Chain.prototype._reorganize = function _reorganize(entry, block, callback) {
|
||||
assert(entries.length > 0);
|
||||
|
||||
utils.forEachSerial(entries, function(entry, next) {
|
||||
self.db.connect(entry, next);
|
||||
self.connect(entry, next);
|
||||
}, callback);
|
||||
}
|
||||
}
|
||||
@ -991,7 +994,7 @@ Chain.prototype._reorganize = function _reorganize(entry, block, callback) {
|
||||
|
||||
self.emit('fork', block, {
|
||||
height: fork.height,
|
||||
expected: self.tip.hash,
|
||||
expected: tip.hash,
|
||||
received: entry.hash,
|
||||
checkpoint: false
|
||||
});
|
||||
@ -1002,6 +1005,85 @@ Chain.prototype._reorganize = function _reorganize(entry, block, callback) {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Disconnect an entry from the chain (updates the tip).
|
||||
* @param {ChainBlock} entry
|
||||
* @param {Function} callback
|
||||
*/
|
||||
|
||||
Chain.prototype.disconnect = function disconnect(entry, callback) {
|
||||
var self = this;
|
||||
|
||||
this.db.disconnect(entry, function(err) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
|
||||
entry.getPrevious(function(err, entry) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
|
||||
assert(entry);
|
||||
|
||||
self.tip = entry;
|
||||
self.height = entry.height;
|
||||
|
||||
if (self.bestHeight === -1)
|
||||
network.height = entry.height;
|
||||
|
||||
self.emit('tip', entry);
|
||||
|
||||
return callback();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Connect an entry to the chain (updates the tip).
|
||||
* This will do contextual-verification on the block
|
||||
* (necessary because we cannot validate the inputs
|
||||
* in side chains when they come in).
|
||||
* @param {ChainBlock} entry
|
||||
* @param {Function} callback
|
||||
*/
|
||||
|
||||
Chain.prototype.connect = function connect(entry, callback) {
|
||||
var self = this;
|
||||
|
||||
this.db.getBlock(entry.hash, function(err, block) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
|
||||
assert(block);
|
||||
|
||||
entry.getPrevious(function(err, prev) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
|
||||
assert(prev);
|
||||
|
||||
self._verifyContext(block, prev, function(err) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
|
||||
self.db.connect(entry, block, function(err) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
|
||||
self.tip = entry;
|
||||
self.height = entry.height;
|
||||
|
||||
if (self.bestHeight === -1)
|
||||
network.height = entry.height;
|
||||
|
||||
self.emit('tip', entry);
|
||||
|
||||
return callback();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the best chain. This is called on every valid block
|
||||
* that comes in. It may add and connect the block (main chain),
|
||||
@ -1009,32 +1091,52 @@ Chain.prototype._reorganize = function _reorganize(entry, block, callback) {
|
||||
* reorganize the chain (a higher fork).
|
||||
* @private
|
||||
* @param {ChainBlock} entry
|
||||
* @param {ChainBlock} prev
|
||||
* @param {Block|MerkleBlock} block
|
||||
* @param {Function} callback - Returns [{@link VerifyError}].
|
||||
*/
|
||||
|
||||
Chain.prototype._setBestChain = function _setBestChain(entry, block, callback) {
|
||||
Chain.prototype._setBestChain = function _setBestChain(entry, prev, block, callback) {
|
||||
var self = this;
|
||||
|
||||
function done(err) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
|
||||
// Save block and connect inputs.
|
||||
self.db.save(entry, block, true, function(err) {
|
||||
if (err)
|
||||
self._verifyContext(block, prev, function(err) {
|
||||
if (err) {
|
||||
// Couldn't verify block.
|
||||
// Revert the height.
|
||||
block.setHeight(-1);
|
||||
|
||||
if (err.type === 'VerifyError') {
|
||||
self.invalid[entry.hash] = true;
|
||||
self.emit('invalid', block, {
|
||||
height: entry.height,
|
||||
hash: entry.hash,
|
||||
seen: false,
|
||||
chain: false
|
||||
});
|
||||
}
|
||||
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
self.tip = entry;
|
||||
self.height = entry.height;
|
||||
// Save block and connect inputs.
|
||||
self.db.save(entry, block, true, function(err) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
|
||||
if (self.bestHeight === -1)
|
||||
network.height = entry.height;
|
||||
self.tip = entry;
|
||||
self.height = entry.height;
|
||||
|
||||
self.emit('tip', entry);
|
||||
if (self.bestHeight === -1)
|
||||
network.height = entry.height;
|
||||
|
||||
// Return true (added to the main chain)
|
||||
return callback(null, true);
|
||||
self.emit('tip', entry);
|
||||
|
||||
return callback();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -1054,21 +1156,10 @@ Chain.prototype._setBestChain = function _setBestChain(entry, block, callback) {
|
||||
return done();
|
||||
}
|
||||
|
||||
// The block is on a side chain if the
|
||||
// chainwork is less than or equal to
|
||||
// our tip's. Add the block but do _not_
|
||||
// connect the inputs.
|
||||
if (entry.chainwork.cmp(this.tip.chainwork) <= 0) {
|
||||
return this.db.save(entry, block, false, function(err) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
|
||||
// Return false (added to side chain)
|
||||
return callback(null, false);
|
||||
});
|
||||
}
|
||||
|
||||
// Everything is in order.
|
||||
// Do "contextual" verification on our block
|
||||
// now that we're certain its previous
|
||||
// block is in the chain.
|
||||
if (entry.prevBlock === this.tip.hash)
|
||||
return done();
|
||||
|
||||
@ -1169,7 +1260,22 @@ Chain.prototype.add = function add(block, callback, force) {
|
||||
(function next(block, initial) {
|
||||
var hash = block.hash('hex');
|
||||
var prevHash = block.prevBlock;
|
||||
var height, checkpoint, orphan;
|
||||
var height, checkpoint, orphan, entry;
|
||||
|
||||
function handleOrphans() {
|
||||
// No orphan chain.
|
||||
if (!self.orphan.map[hash])
|
||||
return done();
|
||||
|
||||
// An orphan chain was found, start resolving.
|
||||
block = self.orphan.map[hash];
|
||||
delete self.orphan.bmap[block.hash('hex')];
|
||||
delete self.orphan.map[hash];
|
||||
self.orphan.count--;
|
||||
self.orphan.size -= block.getSize();
|
||||
|
||||
next(block);
|
||||
}
|
||||
|
||||
// Do not revalidate known invalid blocks.
|
||||
if (self.invalid[hash] || self.invalid[prevHash]) {
|
||||
@ -1344,76 +1450,55 @@ Chain.prototype.add = function add(block, callback, force) {
|
||||
// need access to height on txs.
|
||||
block.setHeight(height);
|
||||
|
||||
// Do "contextual" verification on our block
|
||||
// now that we're certain its previous
|
||||
// block is in the chain.
|
||||
self._verifyContext(block, prev, function(err) {
|
||||
var entry;
|
||||
// Create a new chain entry.
|
||||
entry = new bcoin.chainblock(self, {
|
||||
hash: hash,
|
||||
version: block.version,
|
||||
prevBlock: block.prevBlock,
|
||||
merkleRoot: block.merkleRoot,
|
||||
ts: block.ts,
|
||||
bits: block.bits,
|
||||
nonce: block.nonce,
|
||||
height: height
|
||||
}, prev);
|
||||
|
||||
if (err) {
|
||||
// Couldn't verify block.
|
||||
// Revert the height.
|
||||
block.setHeight(-1);
|
||||
|
||||
if (err.type === 'VerifyError') {
|
||||
self.invalid[hash] = true;
|
||||
self.emit('invalid', block, {
|
||||
height: height,
|
||||
hash: hash,
|
||||
seen: false,
|
||||
chain: false
|
||||
});
|
||||
}
|
||||
|
||||
return done(err);
|
||||
}
|
||||
|
||||
// Create a new chain entry.
|
||||
entry = new bcoin.chainblock(self, {
|
||||
hash: hash,
|
||||
version: block.version,
|
||||
prevBlock: block.prevBlock,
|
||||
merkleRoot: block.merkleRoot,
|
||||
ts: block.ts,
|
||||
bits: block.bits,
|
||||
nonce: block.nonce,
|
||||
height: height
|
||||
}, prev);
|
||||
|
||||
// Attempt to add block to the chain index.
|
||||
self._setBestChain(entry, block, function(err, mainChain) {
|
||||
// The block is on a side chain if the
|
||||
// chainwork is less than or equal to
|
||||
// our tip's. Add the block but do _not_
|
||||
// connect the inputs.
|
||||
if (entry.chainwork.cmp(self.tip.chainwork) <= 0) {
|
||||
return self.db.save(entry, block, false, function(err) {
|
||||
if (err)
|
||||
return done(err);
|
||||
|
||||
// Keep track of the number of blocks we
|
||||
// added and the number of orphans resolved.
|
||||
total++;
|
||||
return callback(err);
|
||||
|
||||
// Emit our block (and potentially resolved
|
||||
// orphan) only if it is on the main chain.
|
||||
if (mainChain) {
|
||||
self.emit('block', block, entry);
|
||||
if (!initial)
|
||||
self.emit('resolved', block, entry);
|
||||
} else {
|
||||
self.emit('competitor', block, entry);
|
||||
if (!initial)
|
||||
self.emit('competitor resolved', block, entry);
|
||||
}
|
||||
self.emit('competitor', block, entry);
|
||||
|
||||
// No orphan chain.
|
||||
if (!self.orphan.map[hash])
|
||||
return done();
|
||||
if (!initial)
|
||||
self.emit('competitor resolved', block, entry);
|
||||
|
||||
// An orphan chain was found, start resolving.
|
||||
block = self.orphan.map[hash];
|
||||
delete self.orphan.bmap[block.hash('hex')];
|
||||
delete self.orphan.map[hash];
|
||||
self.orphan.count--;
|
||||
self.orphan.size -= block.getSize();
|
||||
|
||||
next(block);
|
||||
handleOrphans();
|
||||
});
|
||||
}
|
||||
|
||||
// Attempt to add block to the chain index.
|
||||
self._setBestChain(entry, prev, block, function(err) {
|
||||
if (err)
|
||||
return done(err);
|
||||
|
||||
// Keep track of the number of blocks we
|
||||
// added and the number of orphans resolved.
|
||||
total++;
|
||||
|
||||
// Emit our block (and potentially resolved
|
||||
// orphan) only if it is on the main chain.
|
||||
self.emit('block', block, entry);
|
||||
|
||||
if (!initial)
|
||||
self.emit('resolved', block, entry);
|
||||
|
||||
handleOrphans();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -505,37 +505,28 @@ ChainDB.prototype.getTip = function getTip(callback) {
|
||||
* @param {Function} callback - Returns [Error, {@link ChainBlock}].
|
||||
*/
|
||||
|
||||
ChainDB.prototype.connect = function connect(block, callback) {
|
||||
ChainDB.prototype.connect = function connect(entry, block, callback) {
|
||||
var self = this;
|
||||
var batch, hash;
|
||||
var batch = this.db.batch();
|
||||
var hash = new Buffer(entry.hash, 'hex');
|
||||
|
||||
this._ensureEntry(block, function(err, entry) {
|
||||
batch.put('c/n/' + entry.prevBlock, hash);
|
||||
batch.put('c/h/' + pad32(entry.height), hash);
|
||||
batch.put('c/t', hash);
|
||||
|
||||
this.cacheHash.set(entry.hash, entry);
|
||||
this.cacheHeight.set(entry.height, entry);
|
||||
|
||||
this.emit('add entry', entry);
|
||||
|
||||
this.connectBlock(block, batch, function(err) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
|
||||
if (!entry)
|
||||
return callback();
|
||||
|
||||
batch = self.db.batch();
|
||||
hash = new Buffer(entry.hash, 'hex');
|
||||
|
||||
batch.put('c/n/' + entry.prevBlock, hash);
|
||||
batch.put('c/h/' + pad32(entry.height), hash);
|
||||
batch.put('c/t', hash);
|
||||
|
||||
self.cacheHeight.set(entry.height, entry);
|
||||
|
||||
self.emit('add entry', entry);
|
||||
|
||||
self.connectBlock(entry.hash, batch, function(err) {
|
||||
batch.write(function(err) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
|
||||
batch.write(function(err) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
return callback(null, entry);
|
||||
});
|
||||
return callback(null, entry);
|
||||
});
|
||||
});
|
||||
};
|
||||
@ -555,7 +546,7 @@ ChainDB.prototype.disconnect = function disconnect(block, callback) {
|
||||
return callback(err);
|
||||
|
||||
if (!entry)
|
||||
return callback();
|
||||
return callback(new Error('Entry not found.'));
|
||||
|
||||
batch = self.db.batch();
|
||||
|
||||
@ -869,14 +860,14 @@ ChainDB.prototype.disconnectBlock = function disconnectBlock(hash, batch, callba
|
||||
return callback(err);
|
||||
|
||||
if (!block)
|
||||
return callback();
|
||||
return callback(new Error('Block not found.'));
|
||||
|
||||
if (self.options.paranoid) {
|
||||
if (typeof hash === 'string')
|
||||
assert(block.hash('hex') === hash, 'Database is corrupt.');
|
||||
}
|
||||
|
||||
block.txs.forEach(function(tx) {
|
||||
block.txs.slice().reverse().forEach(function(tx) {
|
||||
var hash = tx.hash('hex');
|
||||
var uniq = {};
|
||||
|
||||
@ -1448,7 +1439,7 @@ ChainDB.prototype.getBlock = function getBlock(hash, callback) {
|
||||
key = 'b/b/' + hash;
|
||||
|
||||
self.db.get(key, function(err, data) {
|
||||
if (err && errr.type !== 'NotFoundError')
|
||||
if (err && err.type !== 'NotFoundError')
|
||||
return callback(err);
|
||||
|
||||
if (!data)
|
||||
|
||||
@ -247,7 +247,7 @@ Miner.prototype.createBlock = function createBlock(callback) {
|
||||
target: target,
|
||||
address: self.address,
|
||||
coinbaseFlags: self.coinbaseFlags,
|
||||
segwit: self.chain.segwitActive,
|
||||
witness: self.chain.segwitActive,
|
||||
dsha256: self.dsha256
|
||||
});
|
||||
|
||||
@ -310,7 +310,7 @@ Miner.prototype.mineBlock = function mineBlock(callback) {
|
||||
* @param {Number} options.target - Compact form.
|
||||
* @param {Function} options.dsha256
|
||||
* @param {Base58Address} options.address - Payout address.
|
||||
* @param {Boolean} options.segwit - Allow witness
|
||||
* @param {Boolean} options.witness - Allow witness
|
||||
* transactions, mine a witness block.
|
||||
* @property {Block} block
|
||||
* @property {TX} coinbase
|
||||
@ -378,7 +378,7 @@ function MinerBlock(options) {
|
||||
|
||||
this.block.txs.push(this.coinbase);
|
||||
|
||||
if (options.segwit) {
|
||||
if (options.witness) {
|
||||
// Set up the witness nonce and
|
||||
// commitment output for segwit.
|
||||
this.witness = true;
|
||||
@ -444,13 +444,13 @@ MinerBlock.prototype.addTX = function addTX(tx) {
|
||||
var size = this.block.getVirtualSize(true) + tx.getVirtualSize();
|
||||
|
||||
// Deliver me from the block size debate, please
|
||||
if (size > constants.blocks.maxSize)
|
||||
if (size > constants.block.maxSize)
|
||||
return false;
|
||||
|
||||
if (this.block.hasTX(tx))
|
||||
return false;
|
||||
|
||||
if (!this.block.witness && tx.hasWitness())
|
||||
if (!this.witness && tx.hasWitness())
|
||||
return false;
|
||||
|
||||
// Add the tx to our block
|
||||
|
||||
@ -159,7 +159,7 @@ bcoin.miner.minerblock.prototype.mineAsync = function mineAsync(callback) {
|
||||
target: this.block.bits,
|
||||
address: this.options.address,
|
||||
coinbaseFlags: this.options.coinbaseFlags,
|
||||
segwit: this.options.segwit
|
||||
witness: this.options.witness
|
||||
};
|
||||
return workers.call('mine', [attempt], callback);
|
||||
};
|
||||
@ -220,7 +220,7 @@ workers.mine = function mine(attempt) {
|
||||
target: attempt.target,
|
||||
address: attempt.address,
|
||||
coinbaseFlags: attempt.coinbaseFlags,
|
||||
segwit: attempt.segwit,
|
||||
witness: attempt.witness,
|
||||
dsha256: utils.dsha256
|
||||
});
|
||||
attempt.on('status', function(stat) {
|
||||
|
||||
180
test/chain-test.js
Normal file
180
test/chain-test.js
Normal file
@ -0,0 +1,180 @@
|
||||
var bn = require('bn.js');
|
||||
delete process.env.BCOIN_NETWORK;
|
||||
var bcoin = require('../')({ network: 'regtest', db: 'memory' });
|
||||
process.env.BCOIN_NETWORK = 'main';
|
||||
var constants = bcoin.protocol.constants;
|
||||
var utils = bcoin.utils;
|
||||
var assert = utils.assert;
|
||||
var opcodes = constants.opcodes;
|
||||
|
||||
constants.tx.coinbaseMaturity = 0;
|
||||
|
||||
describe('Chain', function() {
|
||||
var chain, wallet, miner;
|
||||
var competingTip, oldTip, ch1, ch2, cb1, cb2;
|
||||
|
||||
chain = new bcoin.chain();
|
||||
wallet = new bcoin.wallet();
|
||||
miner = new bcoin.miner({
|
||||
chain: chain,
|
||||
address: wallet.getAddress()
|
||||
});
|
||||
|
||||
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;
|
||||
assert.noError(err);
|
||||
if (tx) {
|
||||
var redeemer = bcoin.mtx();
|
||||
redeemer.addOutput({
|
||||
address: wallet.getAddress(),
|
||||
value: utils.satoshi('25.0')
|
||||
});
|
||||
redeemer.addInput(tx, 0);
|
||||
wallet.sign(redeemer);
|
||||
attempt.addTX(redeemer);
|
||||
}
|
||||
callback(null, attempt.mineSync());
|
||||
});
|
||||
}
|
||||
|
||||
function deleteCoins(tx) {
|
||||
if (Array.isArray(tx)) {
|
||||
tx.forEach(deleteCoins);
|
||||
return;
|
||||
}
|
||||
tx.inputs.forEach(function(input) {
|
||||
delete input.coin;
|
||||
});
|
||||
}
|
||||
|
||||
it('should open chain and miner', function(cb) {
|
||||
miner.open(cb);
|
||||
});
|
||||
|
||||
it('should mine a block', function(cb) {
|
||||
miner.mineBlock(function(err, block) {
|
||||
assert.noError(err);
|
||||
assert(block);
|
||||
cb();
|
||||
});
|
||||
});
|
||||
|
||||
it('should mine competing chains', function(cb) {
|
||||
utils.forRangeSerial(0, 10, function(i, next) {
|
||||
mineBlock(ch1, cb1, function(err, chain1) {
|
||||
assert.noError(err);
|
||||
cb1 = chain1.txs[0];
|
||||
mineBlock(ch2, cb2, function(err, chain2) {
|
||||
assert.noError(err);
|
||||
cb2 = chain2.txs[0];
|
||||
deleteCoins(chain1.txs);
|
||||
chain.add(chain1, function(err) {
|
||||
assert.noError(err);
|
||||
deleteCoins(chain2.txs);
|
||||
chain.add(chain2, function(err) {
|
||||
assert.noError(err);
|
||||
assert(chain.tip.hash === chain1.hash('hex'));
|
||||
competingTip = chain2.hash('hex');
|
||||
chain.db.get(chain1.hash('hex'), function(err, entry1) {
|
||||
assert.noError(err);
|
||||
chain.db.get(chain2.hash('hex'), function(err, entry2) {
|
||||
assert.noError(err);
|
||||
assert(entry1);
|
||||
assert(entry2);
|
||||
ch1 = entry1;
|
||||
ch2 = entry2;
|
||||
chain.db.isMainChain(chain2.hash('hex'), function(err, result) {
|
||||
assert.noError(err);
|
||||
assert(!result);
|
||||
next();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}, cb);
|
||||
});
|
||||
|
||||
it('should handle a reorg', function(cb) {
|
||||
oldTip = chain.tip;
|
||||
chain.db.get(competingTip, function(err, entry) {
|
||||
assert.noError(err);
|
||||
assert(entry);
|
||||
assert(chain.height === entry.height);
|
||||
chain.tip = entry;
|
||||
miner.mineBlock(function(err, reorg) {
|
||||
assert.noError(err);
|
||||
assert(reorg);
|
||||
chain.tip = oldTip;
|
||||
var forked = false;
|
||||
chain.once('fork', function() {
|
||||
forked = true;
|
||||
});
|
||||
deleteCoins(reorg.txs);
|
||||
chain.add(reorg, function(err) {
|
||||
assert.noError(err);
|
||||
assert(forked);
|
||||
assert(chain.tip.hash === reorg.hash('hex'));
|
||||
assert(chain.tip.chainwork.cmp(oldTip.chainwork) > 0);
|
||||
cb();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should check main chain', function(cb) {
|
||||
chain.db.isMainChain(oldTip, function(err, result) {
|
||||
assert.noError(err);
|
||||
assert(!result);
|
||||
cb();
|
||||
});
|
||||
});
|
||||
|
||||
it('should mine a block after a reorg', function(cb) {
|
||||
mineBlock(null, cb2, function(err, block) {
|
||||
assert.noError(err);
|
||||
deleteCoins(block.txs);
|
||||
chain.add(block, function(err) {
|
||||
assert.noError(err);
|
||||
chain.db.get(block.hash('hex'), function(err, entry) {
|
||||
assert.noError(err);
|
||||
assert(entry);
|
||||
assert(chain.tip.hash === entry.hash);
|
||||
chain.db.isMainChain(entry.hash, function(err, result) {
|
||||
assert.noError(err);
|
||||
assert(result);
|
||||
cb();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail to mine a block with coins on a side chain', function(cb) {
|
||||
mineBlock(null, cb1, function(err, block) {
|
||||
assert.noError(err);
|
||||
deleteCoins(block.txs);
|
||||
chain.add(block, function(err) {
|
||||
assert(err);
|
||||
cb();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should cleanup', function(cb) {
|
||||
constants.tx.coinbaseMaturity = 100;
|
||||
cb();
|
||||
});
|
||||
});
|
||||
@ -5,7 +5,7 @@ var utils = bcoin.utils;
|
||||
var assert = utils.assert;
|
||||
var opcodes = constants.opcodes;
|
||||
|
||||
describe('Wallet', function() {
|
||||
describe('Node', function() {
|
||||
var node = new bcoin.fullnode();
|
||||
node.on('error', function() {});
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user