more chain refactoring.

This commit is contained in:
Christopher Jeffrey 2016-03-30 17:32:39 -07:00
parent 4d2d9b328c
commit 9b4e363a80
7 changed files with 299 additions and 268 deletions

View File

@ -75,7 +75,6 @@ AbstractBlock.prototype.verifyHeaders = function verifyHeaders(ret) {
// Check proof of work // Check proof of work
if (!utils.testTarget(this.bits, this.hash())) { if (!utils.testTarget(this.bits, this.hash())) {
utils.debug('Block failed POW test: %s', this.rhash);
ret.reason = 'high-hash'; ret.reason = 'high-hash';
ret.score = 50; ret.score = 50;
return false; return false;
@ -83,7 +82,6 @@ AbstractBlock.prototype.verifyHeaders = function verifyHeaders(ret) {
// Check timestamp against now + 2 hours // Check timestamp against now + 2 hours
if (this.ts > utils.now() + 2 * 60 * 60) { if (this.ts > utils.now() + 2 * 60 * 60) {
utils.debug('Block timestamp is too high: %s', this.rhash);
ret.reason = 'time-too-new'; ret.reason = 'time-too-new';
ret.score = 0; ret.score = 0;
return false; return false;

View File

@ -181,7 +181,6 @@ Block.prototype._verify = function _verify(ret) {
// Size can't be bigger than MAX_BLOCK_SIZE // Size can't be bigger than MAX_BLOCK_SIZE
if (this.txs.length > constants.block.maxSize if (this.txs.length > constants.block.maxSize
|| this.getVirtualSize() > constants.block.maxSize) { || this.getVirtualSize() > constants.block.maxSize) {
utils.debug('Block is too large: %s', this.rhash);
ret.reason = 'bad-blk-length'; ret.reason = 'bad-blk-length';
ret.score = 100; ret.score = 100;
return false; return false;
@ -189,7 +188,6 @@ Block.prototype._verify = function _verify(ret) {
// First TX must be a coinbase // First TX must be a coinbase
if (!this.txs.length || !this.txs[0].isCoinbase()) { if (!this.txs.length || !this.txs[0].isCoinbase()) {
utils.debug('Block has no coinbase: %s', this.rhash);
ret.reason = 'bad-cb-missing'; ret.reason = 'bad-cb-missing';
ret.score = 100; ret.score = 100;
return false; return false;
@ -201,7 +199,6 @@ Block.prototype._verify = function _verify(ret) {
// The rest of the txs must not be coinbases // The rest of the txs must not be coinbases
if (i > 0 && tx.isCoinbase()) { if (i > 0 && tx.isCoinbase()) {
utils.debug('Block more than one coinbase: %s', this.rhash);
ret.reason = 'bad-cb-multiple'; ret.reason = 'bad-cb-multiple';
ret.score = 100; ret.score = 100;
return false; return false;
@ -210,7 +207,6 @@ Block.prototype._verify = function _verify(ret) {
// Check for duplicate txids // Check for duplicate txids
hash = tx.hash('hex'); hash = tx.hash('hex');
if (uniq[hash]) { if (uniq[hash]) {
utils.debug('Block has duplicate txids: %s', this.rhash);
ret.reason = 'bad-txns-duplicate'; ret.reason = 'bad-txns-duplicate';
ret.score = 100; ret.score = 100;
return false; return false;
@ -220,7 +216,6 @@ Block.prototype._verify = function _verify(ret) {
// Check merkle root // Check merkle root
if (this.getMerkleRoot() !== this.merkleRoot) { if (this.getMerkleRoot() !== this.merkleRoot) {
utils.debug('Block failed merkleroot test: %s', this.rhash);
ret.reason = 'bad-txnmrkleroot'; ret.reason = 'bad-txnmrkleroot';
ret.score = 100; ret.score = 100;
return false; return false;

View File

@ -62,25 +62,28 @@ utils.inherits(Chain, EventEmitter);
Chain.prototype._init = function _init() { Chain.prototype._init = function _init() {
var self = this; var self = this;
function getPeer() { function getHost() {
var peer;
if (!self.node || !self.node.pool) if (!self.node || !self.node.pool)
return; return;
return self.node.pool.peers.load; peer = self.node.pool.peers.load;
}
function getHost() { if (!peer)
var peer = getPeer(); return 'unknown';
if (peer)
return peer.host; return peer.host;
return 'unknown';
} }
// Hook into events for debugging // Hook into events for debugging
// this.on('block', function(block, entry) { this.on('block', function(block, entry) {
// utils.debug('Block %s (%d) added to chain (%s)', if (self.height < 400000)
// utils.revHex(entry.hash), entry.height, getHost()); return;
// });
utils.debug('Block %s (%d) added to chain (%s)',
utils.revHex(entry.hash), entry.height, getHost());
});
this.on('competitor', function(block, entry) { this.on('competitor', function(block, entry) {
utils.debug('Heads up: Competing chain at height %d:' utils.debug('Heads up: Competing chain at height %d:'
@ -397,8 +400,9 @@ Chain.prototype._verifyContext = function _verifyContext(block, prev, callback)
Chain.prototype._verify = function _verify(block, prev, callback) { Chain.prototype._verify = function _verify(block, prev, callback) {
var self = this; var self = this;
var flags = constants.flags.MANDATORY_VERIFY_FLAGS; var flags = constants.flags.MANDATORY_VERIFY_FLAGS;
var lockFlags = constants.flags.MANDATORY_LOCKTIME_FLAGS;
var height, ts, i, tx, coinbaseHeight; var height, ts, i, tx, coinbaseHeight;
var medianTime, locktimeMedian, segwit; var medianTime, segwit;
var ret = {}; var ret = {};
function done(err, result) { function done(err, result) {
@ -414,10 +418,8 @@ Chain.prototype._verify = function _verify(block, prev, callback) {
return done(null, flags); return done(null, flags);
// Ensure it's not an orphan // Ensure it's not an orphan
if (!prev) { if (!prev)
utils.debug('Block has no previous entry: %s', block.rhash);
return done(new VerifyError(block, 'invalid', 'bad-prevblk', 0)); return done(new VerifyError(block, 'invalid', 'bad-prevblk', 0));
}
prev.ensureAncestors(function(err) { prev.ensureAncestors(function(err) {
if (err) if (err)
@ -427,15 +429,11 @@ Chain.prototype._verify = function _verify(block, prev, callback) {
medianTime = prev.getMedianTime(); medianTime = prev.getMedianTime();
// Ensure the timestamp is correct // Ensure the timestamp is correct
if (block.ts <= medianTime) { if (block.ts <= medianTime)
utils.debug('Block time is lower than median: %s', block.rhash);
return done(new VerifyError(block, 'invalid', 'time-too-old', 0)); return done(new VerifyError(block, 'invalid', 'time-too-old', 0));
}
if (block.bits !== self.getTarget(prev, block)) { if (block.bits !== self.getTarget(prev, block))
utils.debug('Block is using wrong target: %s', block.rhash);
return done(new VerifyError(block, 'invalid', 'bad-diffbits', 100)); return done(new VerifyError(block, 'invalid', 'bad-diffbits', 100));
}
// For some reason bitcoind has p2sh in the // For some reason bitcoind has p2sh in the
// mandatory flags by default, when in reality // mandatory flags by default, when in reality
@ -443,47 +441,37 @@ Chain.prototype._verify = function _verify(block, prev, callback) {
// The first p2sh output and redeem script // The first p2sh output and redeem script
// appeared on march 7th 2012, only it did // appeared on march 7th 2012, only it did
// not have a signature. See: // not have a signature. See:
// https://blockchain.info/tx/6a26d2ecb67f27d1fa5524763b49029d7106e91e3cc05743073461a719776192 // 6a26d2ecb67f27d1fa5524763b49029d7106e91e3cc05743073461a719776192
// https://blockchain.info/tx/9c08a4d78931342b37fd5f72900fb9983087e6f46c4a097d8a1f52c74e28eaf6 // 9c08a4d78931342b37fd5f72900fb9983087e6f46c4a097d8a1f52c74e28eaf6
if (block.ts < constants.block.bip16time) if (block.ts < constants.block.bip16time)
flags &= ~constants.flags.VERIFY_P2SH; flags &= ~constants.flags.VERIFY_P2SH;
// Only allow version 2 blocks (coinbase height) // Only allow version 2 blocks (coinbase height)
// once the majority of blocks are using it. // once the majority of blocks are using it.
if (block.version < 2 && prev.isOutdated(2)) { if (block.version < 2 && prev.isOutdated(2))
utils.debug('Block is outdated (v2): %s', block.rhash);
return done(new VerifyError(block, 'obsolete', 'bad-version', 0)); return done(new VerifyError(block, 'obsolete', 'bad-version', 0));
}
// Only allow version 3 blocks (sig validation) // Only allow version 3 blocks (sig validation)
// once the majority of blocks are using it. // once the majority of blocks are using it.
if (block.version < 3 && prev.isOutdated(3)) { if (block.version < 3 && prev.isOutdated(3))
utils.debug('Block is outdated (v3): %s', block.rhash);
return done(new VerifyError(block, 'obsolete', 'bad-version', 0)); return done(new VerifyError(block, 'obsolete', 'bad-version', 0));
}
// Only allow version 4 blocks (checklocktimeverify) // Only allow version 4 blocks (checklocktimeverify)
// once the majority of blocks are using it. // once the majority of blocks are using it.
if (block.version < 4 && prev.isOutdated(4)) { if (block.version < 4 && prev.isOutdated(4))
utils.debug('Block is outdated (v4): %s', block.rhash);
return done(new VerifyError(block, 'obsolete', 'bad-version', 0)); return done(new VerifyError(block, 'obsolete', 'bad-version', 0));
}
// Only allow version 5 blocks (segwit) // Only allow version 5 blocks (segwit)
// once the majority of blocks are using it. // once the majority of blocks are using it.
if (network.segwitHeight !== -1 && height >= network.segwitHeight) { if (network.segwitHeight !== -1 && height >= network.segwitHeight) {
if (block.version < 5 && prev.isOutdated(5)) { if (block.version < 5 && prev.isOutdated(5))
utils.debug('Block is outdated (v5): %s', block.rhash);
return done(new VerifyError(block, 'obsolete', 'bad-version', 0)); return done(new VerifyError(block, 'obsolete', 'bad-version', 0));
}
} }
// Only allow version 8 blocks (locktime median past) // Only allow version 8 blocks (locktime median past)
// once the majority of blocks are using it. // once the majority of blocks are using it.
// if (block.version < 8 && prev.isOutdated(8)) { // if (block.version < 8 && prev.isOutdated(8))
// utils.debug('Block is outdated (v8): %s', block.rhash); // return done(new VerifyError(block, 'obsolete', 'bad-version', 0));
// return false);
// }
// Make sure the height contained in the coinbase is correct. // Make sure the height contained in the coinbase is correct.
if (network.block.bip34height !== -1 && height >= network.block.bip34height) { if (network.block.bip34height !== -1 && height >= network.block.bip34height) {
@ -499,7 +487,7 @@ Chain.prototype._verify = function _verify(block, prev, callback) {
if (block.version >= 4 && prev.isUpgraded(4)) if (block.version >= 4 && prev.isUpgraded(4))
flags |= constants.flags.VERIFY_CHECKLOCKTIMEVERIFY; flags |= constants.flags.VERIFY_CHECKLOCKTIMEVERIFY;
// Segregrated witness is now usable (the-bip-that-really-needs-to-be-rewritten) // Segregrated witness is now usable
if (network.segwitHeight !== -1 && height >= network.segwitHeight) { if (network.segwitHeight !== -1 && height >= network.segwitHeight) {
if (block.version >= 5 && prev.isUpgraded(5)) { if (block.version >= 5 && prev.isUpgraded(5)) {
flags |= constants.flags.VERIFY_WITNESS; flags |= constants.flags.VERIFY_WITNESS;
@ -510,32 +498,40 @@ Chain.prototype._verify = function _verify(block, prev, callback) {
} }
} }
// Locktime median time past is now enforced.
// if (block.version >= 8 && prev.isUpgraded(8))
// lockFlags |= constants.flags.MEDIAN_TIME_PAST;
// Can't verify any further when merkleblock or headers. // Can't verify any further when merkleblock or headers.
if (block.type !== 'block') if (block.type !== 'block')
return done(null, flags); return done(null, flags);
// Make sure the height contained in the coinbase is correct. // Make sure the height contained in the coinbase is correct.
if (coinbaseHeight) { if (coinbaseHeight) {
if (block.getCoinbaseHeight() !== height) { if (block.getCoinbaseHeight() !== height)
utils.debug('Block has bad coinbase height: %s', block.rhash);
return done(new VerifyError(block, 'invalid', 'bad-cb-height', 100)); return done(new VerifyError(block, 'invalid', 'bad-cb-height', 100));
}
} }
if (block.version >= 5 && segwit) { if (block.version >= 5 && segwit) {
if (block.commitmentHash !== block.getCommitmentHash()) { if (block.commitmentHash !== block.getCommitmentHash()) {
utils.debug('Block failed witnessroot test: %s', block.rhash); return done(new VerifyError(block,
return done(new VerifyError(block, 'invalid', 'bad-blk-wit-length', 100)); 'invalid',
'bad-blk-wit-length',
100));
} }
} else { } else {
if (block.hasWitness()) { if (block.hasWitness()) {
utils.debug('Unexpected witness data found: %s', block.rhash); return done(new VerifyError(block,
return done(new VerifyError(block, 'invalid', 'unexpected-witness', 100)); 'invalid',
'unexpected-witness',
100));
} }
} }
// Get timestamp for tx.isFinal(). // Get timestamp for tx.isFinal().
ts = locktimeMedian ? medianTime : block.ts; ts = (lockFlags & constants.flags.MEDIAN_TIME_PAST)
? medianTime
: block.ts;
// Check all transactions // Check all transactions
for (i = 0; i < block.txs.length; i++) { for (i = 0; i < block.txs.length; i++) {
@ -544,8 +540,10 @@ Chain.prototype._verify = function _verify(block, prev, callback) {
// Transactions must be finalized with // Transactions must be finalized with
// regards to nSequence and nLockTime. // regards to nSequence and nLockTime.
if (!tx.isFinal(height, ts)) { if (!tx.isFinal(height, ts)) {
utils.debug('TX is not final: %s (%s)', block.rhash, i); return done(new VerifyError(block,
return done(new VerifyError(block, 'invalid', 'bad-txns-nonfinal', 10)); 'invalid',
'bad-txns-nonfinal',
10));
} }
} }
@ -572,13 +570,14 @@ Chain.prototype._checkDuplicates = function _checkDuplicates(block, prev, callba
if (err) if (err)
return next(err); return next(err);
// Blocks 91842 and 91880 created duplicate
// txids by using the same exact output script
// and extraNonce.
if (result) { if (result) {
utils.debug('Block is overwriting txids: %s', block.rhash); // Blocks 91842 and 91880 created duplicate
if (network.type === 'main' && (height === 91842 || height === 91880)) // txids by using the same exact output script
return next(); // and extraNonce.
if (network.type === 'main') {
if (height === 91842 || height === 91880)
return next();
}
return next(new VerifyError(block, 'invalid', 'bad-txns-BIP30', 100)); return next(new VerifyError(block, 'invalid', 'bad-txns-BIP30', 100));
} }
@ -588,6 +587,7 @@ Chain.prototype._checkDuplicates = function _checkDuplicates(block, prev, callba
}; };
Chain.prototype._checkInputs = function _checkInputs(block, prev, flags, callback) { Chain.prototype._checkInputs = function _checkInputs(block, prev, flags, callback) {
var self = this;
var height = prev.height + 1; var height = prev.height + 1;
var scriptCheck = true; var scriptCheck = true;
var historical = false; var historical = false;
@ -631,25 +631,28 @@ Chain.prototype._checkInputs = function _checkInputs(block, prev, flags, callbac
sigops += tx.getSigops(); sigops += tx.getSigops();
if (sigops > constants.block.maxSigops) { if (sigops > constants.block.maxSigops) {
utils.debug('Block has too many sigops: %s', block.rhash); return callback(new VerifyError(block,
return callback(new VerifyError(block, 'invalid', 'bad-blk-sigops', 100)); 'invalid',
'bad-blk-sigops',
100));
} }
// Coinbases do not have prevouts // Coinbases do not have prevouts
if (tx.isCoinbase()) if (tx.isCoinbase())
continue; continue;
if (!tx.checkInputs(height, ret)) if (!tx.checkInputs(height, ret)) {
return callback(new VerifyError(block, 'invalid', ret.reason, ret.score)); 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];
// Ensure tx is not double spending an output // Ensure tx is not double spending an output
if (!input.coin) { if (!input.coin) {
utils.debug('Block is using spent inputs: %s (tx: %s, output: %s)',
block.rhash, tx.rhash,
utils.revHex(input.prevout.hash) + '/' + input.prevout.index);
assert(!historical, 'BUG: Spent inputs in historical data!'); assert(!historical, 'BUG: Spent inputs in historical data!');
return callback(new VerifyError(block, return callback(new VerifyError(block,
'invalid', 'invalid',
@ -665,17 +668,6 @@ Chain.prototype._checkInputs = function _checkInputs(block, prev, flags, callbac
// Verify the scripts // Verify the scripts
if (!tx.verify(j, true, flags)) { if (!tx.verify(j, true, flags)) {
utils.debug('Block has invalid inputs: %s (%s/%d)',
block.rhash, tx.rhash, j);
utils.debug(input);
utils.debug('Signature Hash v0: %s',
utils.toHex(tx.signatureHash(j, input.coin.script, 'all', 0)));
utils.debug('Signature Hash v1: %s',
utils.toHex(tx.signatureHash(j, input.coin.script, 'all', 1)));
utils.debug('Raw Script: %s',
utils.toHex(input.coin.script.encode()));
utils.debug('Reserialized Script: %s',
utils.toHex(input.coin.script.clone().encode()));
assert(!historical, 'BUG: Invalid inputs in historical data!'); assert(!historical, 'BUG: Invalid inputs in historical data!');
return callback(new VerifyError(block, return callback(new VerifyError(block,
'invalid', 'invalid',
@ -699,7 +691,6 @@ Chain.prototype._checkInputs = function _checkInputs(block, prev, flags, callbac
return callback(err); return callback(err);
if (!verified) { if (!verified) {
utils.debug('Block has invalid inputs: %s', block.rhash);
assert(!historical, 'BUG: Invalid inputs in historical data!'); assert(!historical, 'BUG: Invalid inputs in historical data!');
return callback(new VerifyError(block, return callback(new VerifyError(block,
'invalid', 'invalid',
@ -1011,17 +1002,74 @@ Chain.prototype.add = function add(block, callback, force) {
chain: !!self.invalid[prevHash] chain: !!self.invalid[prevHash]
}); });
self.invalid[hash] = true; self.invalid[hash] = true;
return done(new VerifyError(block, 'duplicate', 'duplicate', 0)); return done(new VerifyError(block, 'duplicate', 'duplicate', 100));
} }
// Do we already have this block? // Do we already have this block?
if (self.hasPending(hash)) {
self.emit('exists', block, {
height: block.getCoinbaseHeight(),
hash: hash
});
return done(new VerifyError(block, 'duplicate', 'duplicate', 0));
}
// If the block is already known to be
// an orphan, ignore it.
orphan = self.orphan.map[prevHash];
if (orphan) {
// If the orphan chain forked, simply
// reset the orphans.
if (orphan.hash('hex') !== hash) {
self.purgeOrphans();
self.purgePending();
self.emit('fork', block, {
height: block.getCoinbaseHeight(),
expected: orphan.hash('hex'),
received: hash,
checkpoint: false
});
return done(new VerifyError(block, 'duplicate', 'duplicate', 0));
}
self.emit('orphan', block, {
height: block.getCoinbaseHeight(),
hash: hash,
seen: true
});
return done(new VerifyError(block, 'invalid', 'bad-prevblk', 0));
}
// Special case for genesis block.
if (block.isGenesis())
return done();
// Validate the block we want to add.
// This is only necessary for new
// blocks coming in, not the resolving
// orphans.
if (initial && !block.verify(ret)) {
self.invalid[hash] = true;
self.emit('invalid', block, {
height: block.getCoinbaseHeight(),
hash: hash,
seen: false,
chain: false
});
return done(new VerifyError(block, 'invalid', ret.reason, ret.score));
}
self.db.has(hash, function(err, existing) { self.db.has(hash, function(err, existing) {
if (err) if (err)
return done(err); return done(err);
if (existing || self.hasPending(hash)) { // Do we already have this block?
if (existing) {
self.emit('exists', block, { self.emit('exists', block, {
height: -1, height: block.getCoinbaseHeight(),
hash: hash hash: hash
}); });
return done(new VerifyError(block, 'duplicate', 'duplicate', 0)); return done(new VerifyError(block, 'duplicate', 'duplicate', 0));
@ -1034,54 +1082,6 @@ Chain.prototype.add = function add(block, callback, force) {
height = !prev ? -1 : prev.height + 1; height = !prev ? -1 : prev.height + 1;
// Validate the block we want to add.
// This is only necessary for new
// blocks coming in, not the resolving
// orphans.
if (initial && !block.verify(ret)) {
self.invalid[hash] = true;
self.emit('invalid', block, {
height: height,
hash: hash,
seen: false,
chain: false
});
return done(new VerifyError(block, 'invalid', ret.reason, ret.score));
}
// Special case for genesis block.
if (block.isGenesis())
return done();
// If the block is already known to be
// an orphan, ignore it.
orphan = self.orphan.map[prevHash];
if (orphan) {
// If the orphan chain forked, simply
// reset the orphans.
if (orphan.hash('hex') !== hash) {
self.purgeOrphans();
self.purgePending();
self.emit('fork', block, {
height: block.getCoinbaseHeight(),
expected: orphan.hash('hex'),
received: hash,
checkpoint: false
});
return done();
}
self.emit('orphan', block, {
height: block.getCoinbaseHeight(),
hash: hash,
seen: true
});
return done(new VerifyError(block, 'invalid', 'bad-prevblk', 0));
}
// Update the best height based on the coinbase. // Update the best height based on the coinbase.
// We do this even for orphans (peers will send // We do this even for orphans (peers will send
// us their highest block during the initial // us their highest block during the initial
@ -1590,13 +1590,13 @@ Chain.prototype.getLocator = function getLocator(start, callback, force) {
if (typeof height === 'string') if (typeof height === 'string')
return next(); return next();
self.db.get(height, function(err, existing) { self.db.getHash(height, function(err, hash) {
if (err) if (err)
return next(err); return next(err);
assert(existing); assert(hash);
hashes[i] = existing.hash; hashes[i] = hash;
next(); next();
}); });
@ -1949,6 +1949,101 @@ Chain.prototype.isSegwitActive = function isSegwitActive(callback) {
}); });
}; };
Chain.prototype.checkFinal = function checkFinal(prev, tx, flags, callback) {
var height = prev.height + 1;
function check(err, ts) {
if (err)
return callback(err);
return callback(null, tx.isFinal(ts, height));
}
if (flags & constants.flags.MEDIAN_TIME_PAST)
return prev.getMedianTimeAsync(check);
utils.asyncify(check)(null, utils.now());
};
Chain.prototype.getLocks = function getLocks(tx, flags, entry, callback) {
var self = this;
var mask = constants.sequenceLocktimeMask;
var granularity = constants.sequenceLocktimeGranularity;
var disableFlag = constants.sequenceLocktimeDisableFlag;
var typeFlag = constants.sequenceLocktimeTypeFlag;
var hasFlag = flags & constants.flags.VERIFY_SEQUENCE;
var minHeight = -1;
var minTime = -1;
var coinHeight;
if (tx.version < 2 || !hasFlag)
return utils.asyncify(callback)(null, minHeight, minTime);
utils.forEachSerial(tx.inputs, function(input, next) {
if (input.sequence & disableFlag)
return next();
coinHeight = input.coin.height === -1
? self.chain.tip + 1
: input.coin.height;
if ((input.sequence & typeFlag) === 0) {
coinHeight += (input.sequence & mask) - 1;
minHeight = Math.max(minHeight, coinHeight);
return next();
}
entry.getAncestorByHeight(Math.max(coinHeight - 1, 0), function(err, entry) {
if (err)
return next(err);
assert(entry, 'Database is corrupt.');
entry.getMedianTimeAsync(function(err, coinTime) {
if (err)
return next(err);
coinTime += ((input.sequence & mask) << granularity) - 1;
minTime = Math.max(minTime, coinTime);
next();
});
});
}, function(err) {
if (err)
return callback(err);
return callback(null, minHeight, minTime);
});
};
Chain.prototype.evalLocks = function evalLocks(entry, minHeight, minTime, callback) {
if (minHeight >= entry.height)
return utils.asyncify(callback)(null, false);
if (minTime === -1)
return utils.asyncify(callback)(null, true);
entry.getMedianTimeAsync(function(err, medianTime) {
if (err)
return callback(err);
if (minTime >= medianTime)
return callback(null, false);
return callback(null, true);
});
};
Chain.prototype.checkLocks = function checkLocks(tx, flags, entry, callback) {
var self = this;
this.getLocks(tx, flags, entry, function(err, minHeight, minTime) {
if (err)
return callback(err);
self.evalLocks(entry, minHeight, minTime, callback);
});
};
/** /**
* Expose * Expose
*/ */

View File

@ -61,6 +61,7 @@ utils.inherits(Mempool, EventEmitter);
Mempool.flags = constants.flags.STANDARD_VERIFY_FLAGS; Mempool.flags = constants.flags.STANDARD_VERIFY_FLAGS;
Mempool.mandatory = constants.flags.MANDATORY_VERIFY_FLAGS; Mempool.mandatory = constants.flags.MANDATORY_VERIFY_FLAGS;
Mempool.lockFlags = constants.flags.STANDARD_LOCKTIME_FLAGS;
Mempool.ANCESTOR_LIMIT = 25; Mempool.ANCESTOR_LIMIT = 25;
Mempool.MAX_MEMPOOL_SIZE = 300 << 20; Mempool.MAX_MEMPOOL_SIZE = 300 << 20;
@ -109,9 +110,13 @@ Mempool.prototype._init = function _init() {
else else
self.size = size; self.size = size;
unlock(); self.chain.open(function(err) {
self.loaded = true; if (err)
self.emit('open'); self.emit('error', err);
unlock();
self.loaded = true;
self.emit('open');
});
}); });
}); });
}); });
@ -285,9 +290,10 @@ Mempool.prototype.hasTX = function hasTX(hash, callback) {
Mempool.prototype.add = Mempool.prototype.add =
Mempool.prototype.addTX = function addTX(tx, callback, force) { Mempool.prototype.addTX = function addTX(tx, callback, force) {
var self = this; var self = this;
var hash, ts, height, now;
var flags = Mempool.flags; var flags = Mempool.flags;
var lockFlags = Mempool.lockFlags;
var ret = {}; var ret = {};
var now;
var unlock = this._lock(addTX, [tx, callback], force); var unlock = this._lock(addTX, [tx, callback], force);
if (!unlock) if (!unlock)
@ -298,8 +304,6 @@ Mempool.prototype.addTX = function addTX(tx, callback, force) {
flags |= constants.flags.VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM; flags |= constants.flags.VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM;
} }
hash = tx.hash('hex');
callback = utils.wrap(callback, unlock); callback = utils.wrap(callback, unlock);
callback = utils.asyncify(callback); callback = utils.asyncify(callback);
@ -321,70 +325,72 @@ Mempool.prototype.addTX = function addTX(tx, callback, force) {
if (tx.isCoinbase()) if (tx.isCoinbase())
return callback(new VerifyError(tx, 'invalid', 'coinbase', 100)); return callback(new VerifyError(tx, 'invalid', 'coinbase', 100));
// ts = locktimeFlags & LOCKTIME_MEDIAN_PAST self.chain.checkFinal(self.chain.tip, tx, lockFlags, function(err, isFinal) {
// ? self.chain.tip.getMedianTime()
// : utils.now();
ts = utils.now();
height = this.chain.height + 1;
if (!tx.isFinal(ts, height))
return callback(new VerifyError(tx, 'nonstandard', 'non-final', 0));
if (this.requireStandard) {
if (!tx.isStandard(flags, ret))
return callback(new VerifyError(tx, ret.reason, 0));
}
this._hasTX(tx, function(err, exists) {
if (err) if (err)
return callback(err); return callback(err);
if (exists) if (!isFinal)
return callback(new VerifyError(tx, 'alreadyknown', 'txn-already-in-mempool', 0)); return callback(new VerifyError(tx, 'nonstandard', 'non-final', 0));
self.tx.isDoubleSpend(tx, function(err, doubleSpend) { if (self.requireStandard) {
if (!tx.isStandard(flags, ret))
return callback(new VerifyError(tx, ret.reason, 0));
}
self._hasTX(tx, function(err, exists) {
if (err) if (err)
return callback(err); return callback(err);
if (doubleSpend) { if (exists) {
return callback(new VerifyError(tx, return callback(new VerifyError(tx,
'duplicate', 'alreadyknown',
'bad-txns-inputs-spent', 'txn-already-in-mempool',
0)); 0));
} }
self.node.fillCoins(tx, function(err) { self.tx.isDoubleSpend(tx, function(err, doubleSpend) {
if (err) if (err)
return callback(err); return callback(err);
if (!tx.hasCoins()) { if (doubleSpend) {
if (self.size > Mempool.MAX_MEMPOOL_SIZE) { return callback(new VerifyError(tx,
return callback(new VerifyError(tx, 'duplicate',
'insufficientfee', 'bad-txns-inputs-spent',
'mempool full', 0));
0));
}
utils.debug('Added orphan %s to mempool.', tx.rhash);
return self.storeOrphan(tx, callback);
} }
self.verify(tx, function(err) { self.node.fillCoins(tx, function(err) {
if (err) if (err)
return callback(err); return callback(err);
self.limitMempoolSize(function(err, result) { if (!tx.hasCoins()) {
if (err) if (self.size > Mempool.MAX_MEMPOOL_SIZE) {
return callback(err);
if (!result) {
return callback(new VerifyError(tx, return callback(new VerifyError(tx,
'insufficientfee', 'insufficientfee',
'mempool full', 'mempool full',
0)); 0));
} }
utils.debug('Added orphan %s to mempool.', tx.rhash);
return self.storeOrphan(tx, callback);
}
self.addUnchecked(tx, callback); self.verify(tx, function(err) {
if (err)
return callback(err);
self.limitMempoolSize(function(err, result) {
if (err)
return callback(err);
if (!result) {
return callback(new VerifyError(tx,
'insufficientfee',
'mempool full',
0));
}
self.addUnchecked(tx, callback);
});
}); });
}); });
}); });
@ -437,6 +443,7 @@ Mempool.prototype.removeUnchecked = function removeUnchecked(tx, callback) {
Mempool.prototype.verify = function verify(tx, callback) { Mempool.prototype.verify = function verify(tx, callback) {
var self = this; var self = this;
var height = this.chain.height + 1; var height = this.chain.height + 1;
var lockFlags = Mempool.lockFlags;
var flags = Mempool.flags; var flags = Mempool.flags;
var mandatory = Mempool.mandatory; var mandatory = Mempool.mandatory;
var ret = {}; var ret = {};
@ -448,7 +455,7 @@ Mempool.prototype.verify = function verify(tx, callback) {
mandatory |= constants.flags.VERIFY_WITNESS; mandatory |= constants.flags.VERIFY_WITNESS;
} }
this.checkMempoolLocks(tx, flags, function(err, result) { this.checkLocks(tx, lockFlags, function(err, result) {
if (err) if (err)
return callback(err); return callback(err);
@ -756,86 +763,7 @@ Mempool.prototype.getSnapshot = function getSnapshot(callback) {
return this.tx.getAllHashes(callback); return this.tx.getAllHashes(callback);
}; };
Mempool.prototype.getLocks = function getLocks(tx, flags, entry, callback) { Mempool.prototype.checkLocks = function checkLocks(tx, flags, callback) {
var self = this;
var mask = constants.sequenceLocktimeMask
var granularity = constants.sequenceLocktimeGranularity;
var disableFlag = constants.sequenceLocktimeDisableFlag;
var typeFlag = constants.sequenceLocktimeTypeFlag;
var hasFlag = flags & constants.flags.VERIFY_CHECKSEQUENCEVERIFY;
var minHeight = -1;
var minTime = -1;
var coinHeight;
if (tx.version < 2 || !hasFlag)
return utils.asyncify(callback)(null, minHeight, minTime);
utils.forEachSerial(tx.inputs, function(input, next) {
if (input.sequence & disableFlag)
return next();
coinHeight = input.coin.height === -1
? self.chain.tip + 1
: input.coin.height;
if ((input.sequence & typeFlag) === 0) {
coinHeight += (input.sequence & mask) - 1;
minHeight = Math.max(minHeight, coinHeight);
return next();
}
entry.getAncestorByHeight(Math.max(coinHeight - 1, 0), function(err, entry) {
if (err)
return next(err);
assert(entry, 'Database is corrupt.');
entry.getMedianTimeAsync(function(err, coinTime) {
if (err)
return next(err);
coinTime += ((input.sequence & mask) << granularity) - 1;
minTime = Math.max(minTime, coinTime);
next();
});
});
}, function(err) {
if (err)
return callback(err);
return callback(null, minHeight, minTime);
});
};
Mempool.prototype.evalLocks = function evalLocks(entry, minHeight, minTime, callback) {
if (minHeight >= entry.height)
return utils.asyncify(callback)(null, false);
if (minTime === -1)
return utils.asyncify(callback)(null, true);
entry.getMedianTimeAsync(function(err, medianTime) {
if (err)
return callback(err);
if (minTime >= medianTime)
return callback(null, false);
return callback(null, true);
});
};
Mempool.prototype.checkLocks = function checkLocks(tx, flags, entry, callback) {
var self = this;
this.getLocks(tx, flags, entry, function(err, minHeight, minTime) {
if (err)
return callback(err);
self.evalLocks(entry, minHeight, minTime, callback);
});
};
Mempool.prototype.checkMempoolLocks = function checkMempoolLocks(tx, flags, callback) {
var self = this; var self = this;
var tip = this.chain.tip; var tip = this.chain.tip;
@ -851,7 +779,7 @@ Mempool.prototype.checkMempoolLocks = function checkMempoolLocks(tx, flags, call
chainwork: tip.chainwork chainwork: tip.chainwork
}); });
this.checkLocks(tx, flags, index, callback); return this.chain.checkLocks(tx, flags, index, callback);
}; };
/** /**

View File

@ -133,7 +133,6 @@ MerkleBlock.prototype._verify = function _verify(ret) {
// Verify the partial merkle tree if we are a merkleblock. // Verify the partial merkle tree if we are a merkleblock.
if (!this._verifyPartial()) { if (!this._verifyPartial()) {
utils.debug('Block failed merkle test: %s', this.rhash);
ret.reason = 'bad-txnmrklroot'; ret.reason = 'bad-txnmrklroot';
ret.score = 100; ret.score = 100;
return false; return false;

View File

@ -294,7 +294,10 @@ exports.flags = {
VERIFY_WITNESS: (1 << 10), VERIFY_WITNESS: (1 << 10),
VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM: (1 << 11), VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM: (1 << 11),
// NOTE: Should be (1 << 10) - but that conflicts with segwit // NOTE: Should be (1 << 10) - but that conflicts with segwit
VERIFY_CHECKSEQUENCEVERIFY: (1 << 12) VERIFY_CHECKSEQUENCEVERIFY: (1 << 12),
VERIFY_SEQUENCE: (1 << 0),
MEDIAN_TIME_PAST: (1 << 1)
}; };
// Block validation // Block validation
@ -310,10 +313,16 @@ exports.flags.STANDARD_VERIFY_FLAGS =
| exports.flags.VERIFY_DISCOURAGE_UPGRADABLE_NOPS | exports.flags.VERIFY_DISCOURAGE_UPGRADABLE_NOPS
| exports.flags.VERIFY_CLEANSTACK | exports.flags.VERIFY_CLEANSTACK
| exports.flags.VERIFY_CHECKLOCKTIMEVERIFY | exports.flags.VERIFY_CHECKLOCKTIMEVERIFY
| exports.flags.VERIFY_LOW_S; | exports.flags.VERIFY_LOW_S
| exports.flags.VERIFY_CHECKSEQUENCEVERIFY;
// | exports.flags.VERIFY_WITNESS // | exports.flags.VERIFY_WITNESS
// | exports.flags.VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM // | exports.flags.VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM
// | exports.flags.VERIFY_CHECKSEQUENCEVERIFY;
exports.flags.MANDATORY_LOCKTIME_FLAGS = 0;
exports.flags.STANDARD_LOCKTIME_FLAGS =
exports.flags.VERIFY_SEQUENCE
| exports.flags.MEDIAN_TIME_PAST;
exports.versionbits = { exports.versionbits = {
// What block version to use for new blocks (pre versionbits) // What block version to use for new blocks (pre versionbits)

View File

@ -1997,11 +1997,18 @@ if (utils.isBrowser) {
function VerifyError(object, code, reason, score) { function VerifyError(object, code, reason, score) {
Error.call(this); Error.call(this);
if (Error.captureStackTrace) if (Error.captureStackTrace)
Error.captureStackTrace(this, VerifyError); Error.captureStackTrace(this, VerifyError);
this.type = 'VerifyError'; this.type = 'VerifyError';
this.hash = object.hash(); this.hash = object.hash();
this.height = object.height; this.height = object.height;
if (object.getCoinbaseHeight && this.height === -1)
this.height = object.getCoinbaseHeight();
this.code = code; this.code = code;
this.reason = score === -1 ? null : reason; this.reason = score === -1 ? null : reason;
this.score = score; this.score = score;