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)
|
if (err)
|
||||||
return callback(err);
|
return callback(err);
|
||||||
|
|
||||||
if (block.getClaimed().cmp(block.getReward()) > 0)
|
|
||||||
return callback(new VerifyError(block, 'invalid', 'bad-cb-amount', 100));
|
|
||||||
|
|
||||||
// Check all transactions
|
// Check all transactions
|
||||||
for (i = 0; i < block.txs.length; i++) {
|
for (i = 0; i < block.txs.length; i++) {
|
||||||
tx = block.txs[i];
|
tx = block.txs[i];
|
||||||
@ -780,13 +777,6 @@ Chain.prototype._checkInputs = function _checkInputs(block, prev, flags, callbac
|
|||||||
if (tx.isCoinbase())
|
if (tx.isCoinbase())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (!tx.checkInputs(height, ret)) {
|
|
||||||
return callback(new VerifyError(block,
|
|
||||||
'invalid',
|
|
||||||
ret.reason,
|
|
||||||
ret.score));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (j = 0; j < tx.inputs.length; j++) {
|
for (j = 0; j < tx.inputs.length; j++) {
|
||||||
input = tx.inputs[j];
|
input = tx.inputs[j];
|
||||||
|
|
||||||
@ -814,8 +804,18 @@ Chain.prototype._checkInputs = function _checkInputs(block, prev, flags, callbac
|
|||||||
100));
|
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)
|
if (self.options.verifySync === true)
|
||||||
return callback();
|
return callback();
|
||||||
|
|
||||||
@ -915,8 +915,9 @@ Chain.prototype._findFork = function _findFork(fork, longer, callback) {
|
|||||||
|
|
||||||
Chain.prototype._reorganize = function _reorganize(entry, block, callback) {
|
Chain.prototype._reorganize = function _reorganize(entry, block, callback) {
|
||||||
var self = this;
|
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)
|
if (err)
|
||||||
return callback(err);
|
return callback(err);
|
||||||
|
|
||||||
@ -926,6 +927,8 @@ Chain.prototype._reorganize = function _reorganize(entry, block, callback) {
|
|||||||
function disconnect(callback) {
|
function disconnect(callback) {
|
||||||
var entries = [];
|
var entries = [];
|
||||||
|
|
||||||
|
entries.push(tip);
|
||||||
|
|
||||||
(function collect(entry) {
|
(function collect(entry) {
|
||||||
entry.getPrevious(function(err, entry) {
|
entry.getPrevious(function(err, entry) {
|
||||||
if (err)
|
if (err)
|
||||||
@ -933,20 +936,20 @@ Chain.prototype._reorganize = function _reorganize(entry, block, callback) {
|
|||||||
|
|
||||||
assert(entry);
|
assert(entry);
|
||||||
|
|
||||||
entries.push(entry);
|
|
||||||
|
|
||||||
if (entry.hash === fork.hash)
|
if (entry.hash === fork.hash)
|
||||||
return finish();
|
return finish();
|
||||||
|
|
||||||
|
entries.push(entry);
|
||||||
|
|
||||||
collect(entry);
|
collect(entry);
|
||||||
});
|
});
|
||||||
})(self.tip);
|
})(tip);
|
||||||
|
|
||||||
function finish() {
|
function finish() {
|
||||||
assert(entries.length > 0);
|
assert(entries.length > 0);
|
||||||
|
|
||||||
utils.forEachSerial(entries, function(entry, next) {
|
utils.forEachSerial(entries, function(entry, next) {
|
||||||
self.db.disconnect(entry, next);
|
self.disconnect(entry, next);
|
||||||
}, callback);
|
}, callback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -962,11 +965,11 @@ Chain.prototype._reorganize = function _reorganize(entry, block, callback) {
|
|||||||
|
|
||||||
assert(entry);
|
assert(entry);
|
||||||
|
|
||||||
entries.push(entry);
|
|
||||||
|
|
||||||
if (entry.hash === fork.hash)
|
if (entry.hash === fork.hash)
|
||||||
return finish();
|
return finish();
|
||||||
|
|
||||||
|
entries.push(entry);
|
||||||
|
|
||||||
collect(entry);
|
collect(entry);
|
||||||
});
|
});
|
||||||
})(entry);
|
})(entry);
|
||||||
@ -976,7 +979,7 @@ Chain.prototype._reorganize = function _reorganize(entry, block, callback) {
|
|||||||
assert(entries.length > 0);
|
assert(entries.length > 0);
|
||||||
|
|
||||||
utils.forEachSerial(entries, function(entry, next) {
|
utils.forEachSerial(entries, function(entry, next) {
|
||||||
self.db.connect(entry, next);
|
self.connect(entry, next);
|
||||||
}, callback);
|
}, callback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -991,7 +994,7 @@ Chain.prototype._reorganize = function _reorganize(entry, block, callback) {
|
|||||||
|
|
||||||
self.emit('fork', block, {
|
self.emit('fork', block, {
|
||||||
height: fork.height,
|
height: fork.height,
|
||||||
expected: self.tip.hash,
|
expected: tip.hash,
|
||||||
received: entry.hash,
|
received: entry.hash,
|
||||||
checkpoint: false
|
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
|
* Set the best chain. This is called on every valid block
|
||||||
* that comes in. It may add and connect the block (main chain),
|
* 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).
|
* reorganize the chain (a higher fork).
|
||||||
* @private
|
* @private
|
||||||
* @param {ChainBlock} entry
|
* @param {ChainBlock} entry
|
||||||
|
* @param {ChainBlock} prev
|
||||||
* @param {Block|MerkleBlock} block
|
* @param {Block|MerkleBlock} block
|
||||||
* @param {Function} callback - Returns [{@link VerifyError}].
|
* @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;
|
var self = this;
|
||||||
|
|
||||||
function done(err) {
|
function done(err) {
|
||||||
if (err)
|
if (err)
|
||||||
return callback(err);
|
return callback(err);
|
||||||
|
|
||||||
// Save block and connect inputs.
|
self._verifyContext(block, prev, function(err) {
|
||||||
self.db.save(entry, block, true, function(err) {
|
if (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);
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
self.tip = entry;
|
// Save block and connect inputs.
|
||||||
self.height = entry.height;
|
self.db.save(entry, block, true, function(err) {
|
||||||
|
if (err)
|
||||||
|
return callback(err);
|
||||||
|
|
||||||
if (self.bestHeight === -1)
|
self.tip = entry;
|
||||||
network.height = entry.height;
|
self.height = entry.height;
|
||||||
|
|
||||||
self.emit('tip', entry);
|
if (self.bestHeight === -1)
|
||||||
|
network.height = entry.height;
|
||||||
|
|
||||||
// Return true (added to the main chain)
|
self.emit('tip', entry);
|
||||||
return callback(null, true);
|
|
||||||
|
return callback();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1054,21 +1156,10 @@ Chain.prototype._setBestChain = function _setBestChain(entry, block, callback) {
|
|||||||
return done();
|
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.
|
// 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)
|
if (entry.prevBlock === this.tip.hash)
|
||||||
return done();
|
return done();
|
||||||
|
|
||||||
@ -1169,7 +1260,22 @@ Chain.prototype.add = function add(block, callback, force) {
|
|||||||
(function next(block, initial) {
|
(function next(block, initial) {
|
||||||
var hash = block.hash('hex');
|
var hash = block.hash('hex');
|
||||||
var prevHash = block.prevBlock;
|
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.
|
// Do not revalidate known invalid blocks.
|
||||||
if (self.invalid[hash] || self.invalid[prevHash]) {
|
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.
|
// need access to height on txs.
|
||||||
block.setHeight(height);
|
block.setHeight(height);
|
||||||
|
|
||||||
// Do "contextual" verification on our block
|
// Create a new chain entry.
|
||||||
// now that we're certain its previous
|
entry = new bcoin.chainblock(self, {
|
||||||
// block is in the chain.
|
hash: hash,
|
||||||
self._verifyContext(block, prev, function(err) {
|
version: block.version,
|
||||||
var entry;
|
prevBlock: block.prevBlock,
|
||||||
|
merkleRoot: block.merkleRoot,
|
||||||
|
ts: block.ts,
|
||||||
|
bits: block.bits,
|
||||||
|
nonce: block.nonce,
|
||||||
|
height: height
|
||||||
|
}, prev);
|
||||||
|
|
||||||
if (err) {
|
// The block is on a side chain if the
|
||||||
// Couldn't verify block.
|
// chainwork is less than or equal to
|
||||||
// Revert the height.
|
// our tip's. Add the block but do _not_
|
||||||
block.setHeight(-1);
|
// connect the inputs.
|
||||||
|
if (entry.chainwork.cmp(self.tip.chainwork) <= 0) {
|
||||||
if (err.type === 'VerifyError') {
|
return self.db.save(entry, block, false, function(err) {
|
||||||
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) {
|
|
||||||
if (err)
|
if (err)
|
||||||
return done(err);
|
return callback(err);
|
||||||
|
|
||||||
// Keep track of the number of blocks we
|
|
||||||
// added and the number of orphans resolved.
|
|
||||||
total++;
|
|
||||||
|
|
||||||
// Emit our block (and potentially resolved
|
// Emit our block (and potentially resolved
|
||||||
// orphan) only if it is on the main chain.
|
// orphan) only if it is on the main chain.
|
||||||
if (mainChain) {
|
self.emit('competitor', block, entry);
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// No orphan chain.
|
if (!initial)
|
||||||
if (!self.orphan.map[hash])
|
self.emit('competitor resolved', block, entry);
|
||||||
return done();
|
|
||||||
|
|
||||||
// An orphan chain was found, start resolving.
|
handleOrphans();
|
||||||
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);
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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}].
|
* @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 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)
|
if (err)
|
||||||
return callback(err);
|
return callback(err);
|
||||||
|
|
||||||
if (!entry)
|
batch.write(function(err) {
|
||||||
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) {
|
|
||||||
if (err)
|
if (err)
|
||||||
return callback(err);
|
return callback(err);
|
||||||
|
return callback(null, entry);
|
||||||
batch.write(function(err) {
|
|
||||||
if (err)
|
|
||||||
return callback(err);
|
|
||||||
return callback(null, entry);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -555,7 +546,7 @@ ChainDB.prototype.disconnect = function disconnect(block, callback) {
|
|||||||
return callback(err);
|
return callback(err);
|
||||||
|
|
||||||
if (!entry)
|
if (!entry)
|
||||||
return callback();
|
return callback(new Error('Entry not found.'));
|
||||||
|
|
||||||
batch = self.db.batch();
|
batch = self.db.batch();
|
||||||
|
|
||||||
@ -869,14 +860,14 @@ ChainDB.prototype.disconnectBlock = function disconnectBlock(hash, batch, callba
|
|||||||
return callback(err);
|
return callback(err);
|
||||||
|
|
||||||
if (!block)
|
if (!block)
|
||||||
return callback();
|
return callback(new Error('Block not found.'));
|
||||||
|
|
||||||
if (self.options.paranoid) {
|
if (self.options.paranoid) {
|
||||||
if (typeof hash === 'string')
|
if (typeof hash === 'string')
|
||||||
assert(block.hash('hex') === hash, 'Database is corrupt.');
|
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 hash = tx.hash('hex');
|
||||||
var uniq = {};
|
var uniq = {};
|
||||||
|
|
||||||
@ -1448,7 +1439,7 @@ ChainDB.prototype.getBlock = function getBlock(hash, callback) {
|
|||||||
key = 'b/b/' + hash;
|
key = 'b/b/' + hash;
|
||||||
|
|
||||||
self.db.get(key, function(err, data) {
|
self.db.get(key, function(err, data) {
|
||||||
if (err && errr.type !== 'NotFoundError')
|
if (err && err.type !== 'NotFoundError')
|
||||||
return callback(err);
|
return callback(err);
|
||||||
|
|
||||||
if (!data)
|
if (!data)
|
||||||
|
|||||||
@ -247,7 +247,7 @@ Miner.prototype.createBlock = function createBlock(callback) {
|
|||||||
target: target,
|
target: target,
|
||||||
address: self.address,
|
address: self.address,
|
||||||
coinbaseFlags: self.coinbaseFlags,
|
coinbaseFlags: self.coinbaseFlags,
|
||||||
segwit: self.chain.segwitActive,
|
witness: self.chain.segwitActive,
|
||||||
dsha256: self.dsha256
|
dsha256: self.dsha256
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -310,7 +310,7 @@ Miner.prototype.mineBlock = function mineBlock(callback) {
|
|||||||
* @param {Number} options.target - Compact form.
|
* @param {Number} options.target - Compact form.
|
||||||
* @param {Function} options.dsha256
|
* @param {Function} options.dsha256
|
||||||
* @param {Base58Address} options.address - Payout address.
|
* @param {Base58Address} options.address - Payout address.
|
||||||
* @param {Boolean} options.segwit - Allow witness
|
* @param {Boolean} options.witness - Allow witness
|
||||||
* transactions, mine a witness block.
|
* transactions, mine a witness block.
|
||||||
* @property {Block} block
|
* @property {Block} block
|
||||||
* @property {TX} coinbase
|
* @property {TX} coinbase
|
||||||
@ -378,7 +378,7 @@ function MinerBlock(options) {
|
|||||||
|
|
||||||
this.block.txs.push(this.coinbase);
|
this.block.txs.push(this.coinbase);
|
||||||
|
|
||||||
if (options.segwit) {
|
if (options.witness) {
|
||||||
// Set up the witness nonce and
|
// Set up the witness nonce and
|
||||||
// commitment output for segwit.
|
// commitment output for segwit.
|
||||||
this.witness = true;
|
this.witness = true;
|
||||||
@ -444,13 +444,13 @@ MinerBlock.prototype.addTX = function addTX(tx) {
|
|||||||
var size = this.block.getVirtualSize(true) + tx.getVirtualSize();
|
var size = this.block.getVirtualSize(true) + tx.getVirtualSize();
|
||||||
|
|
||||||
// Deliver me from the block size debate, please
|
// Deliver me from the block size debate, please
|
||||||
if (size > constants.blocks.maxSize)
|
if (size > constants.block.maxSize)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (this.block.hasTX(tx))
|
if (this.block.hasTX(tx))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!this.block.witness && tx.hasWitness())
|
if (!this.witness && tx.hasWitness())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Add the tx to our block
|
// Add the tx to our block
|
||||||
|
|||||||
@ -159,7 +159,7 @@ bcoin.miner.minerblock.prototype.mineAsync = function mineAsync(callback) {
|
|||||||
target: this.block.bits,
|
target: this.block.bits,
|
||||||
address: this.options.address,
|
address: this.options.address,
|
||||||
coinbaseFlags: this.options.coinbaseFlags,
|
coinbaseFlags: this.options.coinbaseFlags,
|
||||||
segwit: this.options.segwit
|
witness: this.options.witness
|
||||||
};
|
};
|
||||||
return workers.call('mine', [attempt], callback);
|
return workers.call('mine', [attempt], callback);
|
||||||
};
|
};
|
||||||
@ -220,7 +220,7 @@ workers.mine = function mine(attempt) {
|
|||||||
target: attempt.target,
|
target: attempt.target,
|
||||||
address: attempt.address,
|
address: attempt.address,
|
||||||
coinbaseFlags: attempt.coinbaseFlags,
|
coinbaseFlags: attempt.coinbaseFlags,
|
||||||
segwit: attempt.segwit,
|
witness: attempt.witness,
|
||||||
dsha256: utils.dsha256
|
dsha256: utils.dsha256
|
||||||
});
|
});
|
||||||
attempt.on('status', function(stat) {
|
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 assert = utils.assert;
|
||||||
var opcodes = constants.opcodes;
|
var opcodes = constants.opcodes;
|
||||||
|
|
||||||
describe('Wallet', function() {
|
describe('Node', function() {
|
||||||
var node = new bcoin.fullnode();
|
var node = new bcoin.fullnode();
|
||||||
node.on('error', function() {});
|
node.on('error', function() {});
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user