contextual verification. misc.

This commit is contained in:
Christopher Jeffrey 2016-02-16 13:45:02 -08:00
parent e912f78814
commit fe46d1ada5
5 changed files with 351 additions and 116 deletions

View File

@ -581,8 +581,6 @@ BlockDB.prototype.getCoin = function getCoin(hash, index, callback) {
var id = 'u/t/' + hash + '/' + index;
this.index.get(id, function(err, record) {
var record;
if (err) {
if (err.type === 'NotFoundError')
return callback();
@ -731,8 +729,6 @@ BlockDB.prototype.getTX = function getTX(hash, callback) {
var id = 't/t/' + hash;
this.index.get(id, function(err, record) {
var record;
if (err) {
if (err.type === 'NotFoundError')
return callback();
@ -775,8 +771,6 @@ BlockDB.prototype.getBlock = function getBlock(hash, callback) {
id = 'b/h/' + value;
this.index.get(id, function(err, record) {
var record;
if (err) {
if (err.type === 'NotFoundError')
return callback();
@ -816,12 +810,34 @@ BlockDB.prototype.getBlock = function getBlock(hash, callback) {
});
};
BlockDB.prototype.hasCoin = function hasCoin(hash, index, callback) {
var id = 'u/t/' + hash + '/' + index;
this.index.get(id, function(err, record) {
if (err && err.type !== 'NotFoundError')
return callback(err);
return callback(null, record ? true : false);
});
};
BlockDB.prototype.hasTX = function hasTX(hash, callback) {
var id = 't/t/' + hash;
this.index.get(id, function(err, record) {
if (err && err.type !== 'NotFoundError')
return callback(err);
return callback(null, record ? true : false);
});
};
BlockDB.prototype.isSpent = function isSpent(hash, index, callback) {
this.getCoin(hash, index, function(err, coin) {
return this.hasCoin(hash, index, function(err, result) {
if (err)
return callback(err);
return callback(null, coin ? false : true);
return callback(null, !result);
});
};

View File

@ -260,6 +260,7 @@ Chain.prototype._preload = function _preload(callback) {
};
Chain.prototype._saveBlock = function _saveBlock(block, callback) {
var self = this;
var node = bcoin.node.global;
if (!node)
@ -275,74 +276,8 @@ Chain.prototype._saveBlock = function _saveBlock(block, callback) {
});
};
Chain.prototype._fillCoins = function _fillCoin(block, callback) {
var node = bcoin.node.global;
if (!node)
return callback();
node.block.fillCoins(block, callback);
};
Chain.prototype._verifyContext = function _verifyContext(block, prev, callback) {
var node = bcoin.node.global;
if (!node)
return callback(null, block.verifyContext());
var height = prev.height + 1;
var scriptChecks = true;
node.block.fillCoins(block, function(err) {
var pending;
if (err)
return callback(err);
pending = block.txs.length;
// If we are an ancestor of a checkpoint, we can
// skip the input verification.
if (height < network.checkpoints.lastHeight && !network.checkpoints[height])
scriptChecks = false;
if (!block.verifyContext())
return callback(null, false);
if (!pending)
return callback(null, true);
// Check all transactions
block.txs.forEach(function(tx) {
var i;
for (i = 0; j < tx.inputs.length; i++) {
input = tx.inputs[i];
// Ensure tx is not double spending an output
if (!input.output) {
utils.debug('Block is using spent inputs: %s (tx: %s, output: %s)',
this.rhash, tx.hash('hex'),
input.prevout.hash + '/' + input.prevout.index);
return callback(null, false);
}
}
// BIP30 - Ensure there are no duplicate txids
node.block.hasTX(tx.hash('hex'), function(err, has) {
// Blocks 91842 and 91880 created duplicate
// txids by using the same exact output script
// and extraNonce.
if (has) {
utils.debug('Block is overwriting txids: %s', this.rhash);
if (!(network.type === 'main' && (height === 91842 || height === 91880)))
return callback(null, false);
}
return callback(null, true);
});
});
});
};
Chain.prototype._removeBlock = function _removeBlock(tip, callback) {
var self = this;
var node = bcoin.node.global;
if (!node)
@ -353,9 +288,289 @@ Chain.prototype._removeBlock = function _removeBlock(tip, callback) {
return callback(err);
if (!block)
return;
return callback();
node.mempool.removeBlock(block);
self.emit('reorg block', block.hash('hex'));
block.txs.forEach(function(tx) {
self.emit('reorg tx', tx.hash('hex'));
});
return callback();
});
};
Chain.prototype._verifyContext = function _verifyContext(block, prev, callback) {
var self = this;
var flags;
flags = this._verify(block, prev);
if (flags === false)
return callback(null, false);
this._checkDuplicates(block, prev, function(err, result) {
if (err)
return callback(err);
if (!result)
return callback(null, false);
self._checkInputs(block, prev, flags, function(err, result) {
if (err)
return callback(err);
if (!result)
return callback(null, false);
return callback(null, true);
});
});
};
Chain.prototype._verify = function _verify(block, prev) {
// var flags = constants.flags.MANDATORY_VERIFY_FLAGS;
var flags = {};
var sigops = 0;
var height, ts, i, tx, cb, coinbaseHeight;
// Skip the genesis block
if (block.isGenesis())
return flags;
// Ensure it's not an orphan
if (!prev) {
utils.debug('Block has no previous entry: %s', block.rhash);
return false;
}
height = prev.height + 1;
// Ensure the timestamp is correct
if (block.ts <= prev.getMedianTime()) {
utils.debug('Block time is lower than median: %s', block.rhash);
return false;
}
// Ensure the miner's target is equal to what we expect
if (block.bits !== block.chain.getTarget(prev, block)) {
utils.debug('Block is using wrong target: %s', block.rhash);
return false;
}
// Only allow version 2 blocks (coinbase height)
// once the majority of blocks are using it.
if (block.version < 2 && prev.isOutdated(2)) {
utils.debug('Block is outdated (v2): %s', block.rhash);
return false;
}
// Only allow version 3 blocks (sig validation)
// once the majority of blocks are using it.
if (block.version < 3 && prev.isOutdated(3)) {
utils.debug('Block is outdated (v3): %s', block.rhash);
return false;
}
// Only allow version 4 blocks (checklocktimeverify)
// once the majority of blocks are using it.
if (block.version < 4 && prev.isOutdated(4)) {
utils.debug('Block is outdated (v4): %s', block.rhash);
return false;
}
// Only allow version 8 blocks (locktime median past)
// once the majority of blocks are using it.
// if (block.version < 8 && prev.isOutdated(8)) {
// utils.debug('Block is outdated (v8): %s', block.rhash);
// return false;
// }
// Make sure the height contained in the coinbase is correct.
if (block.version >= 2 && prev.isUpgraded(2))
coinbaseHeight = true;
// Signature validation is now enforced (bip66)
if (!(block.version >= 3 && prev.isUpgraded(3)))
flags.dersig = false;
// CHECKLOCKTIMEVERIFY is now usable (bip65)
if (!(block.version >= 4 && prev.isUpgraded(4)))
flags.checklocktimeverify = false;
// Use nLockTime median past (bip113)
// https://github.com/btcdrak/bips/blob/d4c9a236ecb947866c61aefb868b284498489c2b/bip-0113.mediawiki
// Support version bits:
// https://gist.github.com/sipa/bf69659f43e763540550
// http://lists.linuxfoundation.org/pipermail/bitcoin-dev/2015-August/010396.html
// if (block.version >= 8 && prev.isUpgraded(8))
// flags.locktimeMedian = true;
// Can't verify any further when merkleblock or headers.
if (block.subtype !== 'block')
return flags;
// Make sure the height contained in the coinbase is correct.
if (coinbaseHeight) {
cb = bcoin.script.getCoinbaseData(block.txs[0].inputs[0].script);
// Make sure the coinbase is parseable.
if (!cb) {
utils.debug('Block has malformed coinbase: %s', block.rhash);
return false;
}
// Make sure coinbase height is equal to the actual height.
if (cb.height !== height) {
utils.debug('Block has bad coinbase height: %s', block.rhash);
return false;
}
}
// Get timestamp for tx.isFinal().
ts = flags.locktimeMedian
? prev.getMedianTime()
: block.ts;
// Check all transactions
for (i = 0; i < block.txs.length; i++) {
tx = block.txs[i];
// Transactions must be finalized with
// regards to nSequence and nLockTime.
if (!tx.isFinal(height, ts)) {
utils.debug('TX is not final: %s (%s)', block.rhash, i);
return false;
}
// Check for tx sigops limits
// Bitcoind does not check for this when accepting
// a block even though it probably should.
// if (tx.getSigops(true) > constants.script.maxTxSigops) {
// // Block 71036 abused checksig to
// // include a huge number of sigops.
// utils.debug('Block TX has too many sigops: %s', block.rhash);
// if (!(network.type === 'main' && height === 71036))
// return false;
// }
// Check for block sigops limits
// Start counting P2SH sigops once block
// timestamps reach March 31st, 2012.
if (block.ts >= constants.block.bip16time)
sigops += tx.getSigops(true);
else
sigops += tx.getSigops();
if (sigops > constants.script.maxBlockSigops) {
utils.debug('Block has too many sigops: %s', block.rhash);
return false;
}
}
return flags;
};
Chain.prototype._checkDuplicates = function _checkDuplicates(block, prev, callback) {
var node = bcoin.node.global;
var height = prev.height + 1;
var pending = block.txs.length;
var called;
if (!node || block.subtype !== 'block')
return callback(null, true);
if (block.isGenesis())
return callback(null, true);
assert(pending);
function done(err, result) {
if (called)
return;
called = true;
callback(err, result);
}
// Check all transactions
block.txs.forEach(function(tx) {
var hash = tx.hash('hex');
// BIP30 - Ensure there are no duplicate txids
node.block.hasTX(hash, function(err, result) {
if (called)
return;
if (err)
return done(err);
// Blocks 91842 and 91880 created duplicate
// txids by using the same exact output script
// and extraNonce.
if (result) {
utils.debug('Block is overwriting txids: %s', block.rhash);
if (!(network.type === 'main' && (height === 91842 || height === 91880)))
return done(null, false);
}
if (!--pending)
return done(null, true);
});
});
};
Chain.prototype._checkInputs = function _checkInputs(block, prev, flags, callback) {
var node = bcoin.node.global;
var height = prev.height + 1;
if (!node || block.subtype !== 'block')
return callback(null, true);
if (block.isGenesis())
return callback(null, true);
// If we are an ancestor of a checkpoint, we can
// skip the input verification.
if (height < network.checkpoints.lastHeight && !network.checkpoints[height])
return callback(null, true);
node.block.fillCoins(block.txs, function(err) {
var i, j, input, hash;
if (err)
return callback(err);
// Check all transactions
for (i = 0; i < block.txs.length; i++) {
tx = blocks.txs[i];
hash = tx.hash('hex');
for (j = 0; j < tx.inputs.length; j++) {
input = tx.inputs[j];
// Coinbases do not have prevouts
if (tx.isCoinbase())
continue;
// Ensure tx is not double spending an output
if (!input.output) {
utils.debug('Block is using spent inputs: %s (tx: %s, output: %s)',
block.rhash, tx.rhash,
input.prevout.rhash + '/' + input.prevout.index);
return callback(null, false);
}
// Verify the scripts
if (!tx.verify(j, true, flags)) {
utils.debug('Block has invalid inputs: %s', block.rhash);
return callback(null, false);
}
}
}
return callback(null, true);
});
};
@ -371,7 +586,7 @@ Chain.prototype._addEntry = function _addEntry(entry, block, callback) {
return callback(null, Chain.codes.unchanged);
}
// Duplicate height
// Duplicate height (do a sync call here since this is cached)
existing = this.db.get(entry.height);
if (existing && existing.hash === entry.hash)
return callback(null, Chain.codes.unchanged);
@ -380,7 +595,7 @@ Chain.prototype._addEntry = function _addEntry(entry, block, callback) {
if (err)
return callback(err);
self._saveEntry(entry, function(err) {
self._saveEntry(entry, true, function(err) {
if (err)
return callback(err);
@ -389,7 +604,7 @@ Chain.prototype._addEntry = function _addEntry(entry, block, callback) {
});
};
Chain.prototype._saveEntry = function _saveEntry(entry, callback) {
Chain.prototype._saveEntry = function _saveEntry(entry, save, callback) {
this.heightLookup[entry.hash] = entry.height;
if (!this.tip || entry.height > this.tip.height) {
@ -397,11 +612,8 @@ Chain.prototype._saveEntry = function _saveEntry(entry, callback) {
this.emit('tip', this.tip);
}
if (callback) {
if (typeof callback !== 'function')
callback = null;
if (save)
this.db.save(entry, callback);
}
};
Chain.prototype.resetLastCheckpoint = function resetLastCheckpoint(height) {
@ -461,6 +673,12 @@ Chain.prototype.add = function add(block, peer, callback) {
var hash, prevHash, prevHeight, entry, tip, existing, checkpoint;
var total = 0;
callback = utils.asyncify(callback);
if (this._locked)
return callback(null, total);
// (function next(block) {
(function next() {
hash = block.hash('hex');
prevHash = block.prevBlock;
@ -553,17 +771,19 @@ Chain.prototype.add = function add(block, peer, callback) {
// The block has equal chainwork (an
// alternate tip). Reset the chain, find
// a new peer, and wait to see who wins.
self.resetHeight(entry.height - 1);
self.emit('fork', {
height: prevHeight + 1,
expected: tip.hash,
received: hash,
checkpoint: false
}, peer);
code = Chain.codes.forked;
self._locked = true;
return self._removeBlock(tip.hash, function(err) {
self._locked = false;
if (err)
return done(err);
self.resetHeight(entry.height - 1);
self.emit('fork', {
height: prevHeight + 1,
expected: tip.hash,
received: hash,
checkpoint: false
}, peer);
code = Chain.codes.forked;
return done(null, code);
});
}
@ -595,14 +815,12 @@ Chain.prototype.add = function add(block, peer, callback) {
}
}
// Could fill here for contextual verification.
// Also check isSpent here!
// self._fillCoins(block, function(err) {
// self._verifyContext(block, prev, function(err, verified) {
// Do "contextual" verification on our block
// now that we're certain its previous
// block is in the chain.
if (!block.verifyContext()) {
if (!block.verifyContext(prev)) {
code = Chain.codes.invalid;
self.emit('invalid', {
height: prevHeight + 1,

View File

@ -196,6 +196,9 @@ Mempool.prototype.addTX = function addTX(tx, peer, callback) {
if (this.txs[hash])
return callback(new Error('Already have TX.'));
if (tx.isCoinbase())
return callback(new Error('What?'));
this._lockTX(tx);
this.block.fillCoin(tx, function(err) {
@ -260,15 +263,6 @@ Mempool.prototype.addTX = function addTX(tx, peer, callback) {
for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i];
if (input.output.spent) {
return callback(new Error('TX is spending old outputs.'));
peer.reject({
data: tx.hash(),
reason: 'old-outputs'
});
pool.setMisbehavior(peer, 100);
return callback(new Error('TX is spending old outputs.'));
}
dup = self.spent[input.prevout.hash + '/' + input.prevout.index];
if (dup) {
// Replace-by-fee
@ -283,7 +277,6 @@ Mempool.prototype.addTX = function addTX(tx, peer, callback) {
data: tx.hash(),
reason: 'double-spend'
});
pool.setMisbehavior(peer, 100);
return callback(new Error('TX is double spending.'));
}
}
@ -301,7 +294,7 @@ Mempool.prototype.addTX = function addTX(tx, peer, callback) {
}
}
if (!tx.verify(true)) {
if (!tx.verify(null, true)) {
return callback(new Error('TX did not verify.'));
peer.reject({
data: tx.hash(),
@ -314,15 +307,12 @@ Mempool.prototype.addTX = function addTX(tx, peer, callback) {
for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i];
self.spent[input.prevout.hash + '/' + input.prevout.index] = tx;
self.size += input.output.getSize();
}
// Possibly do something bitcoinxt-like here with priority
priority = tx.getPriority();
self.txs[hash] = tx;
self.count++;
self.size += tx.getSize();
tx.inputs.forEach(function(input) {
var address = input.getAddress();
@ -346,6 +336,10 @@ Mempool.prototype.addTX = function addTX(tx, peer, callback) {
self.addresses[address][hash] = true;
});
self.txs[hash] = tx;
self.count++;
self.size += tx.getSize();
});
};
@ -402,11 +396,6 @@ Mempool.prototype.removeTX = function removeTX(hash, callback) {
delete this.spent[id];
}
delete this.txs[hash];
this.count--;
this.size -= tx.getSize();
tx.inputs.forEach(function(input) {
var address = input.getAddress();
@ -432,6 +421,10 @@ Mempool.prototype.removeTX = function removeTX(hash, callback) {
delete self.addresses[address];
}
});
delete this.txs[hash];
this.count--;
this.size -= tx.getSize();
};
// Need to lock the mempool when

View File

@ -918,6 +918,9 @@ TX.prototype.verify = function verify(index, force, flags) {
if (index != null)
assert(this.inputs[index]);
if (this.isCoinbase())
return true;
return this.inputs.every(function(input, i) {
if (index != null && i !== index)
return true;
@ -1552,6 +1555,10 @@ TX.prototype.isStandard = function isStandard() {
if (bcoin.script.getSize(input.script) > 1650)
return false;
// Not accurate?
if (this.isCoinbase())
continue;
if (!bcoin.script.isPushOnly(input.script))
return false;
}

View File

@ -1405,7 +1405,8 @@ utils.cmp = function(a, b) {
// This protects us against timing attacks when
// comparing an input against a secret string.
utils.ccmp = function(a, b) {
var res, i;
var res = 0;
var i;
assert(a.length === b.length);