more chain refactoring.
This commit is contained in:
parent
4d2d9b328c
commit
9b4e363a80
@ -75,7 +75,6 @@ AbstractBlock.prototype.verifyHeaders = function verifyHeaders(ret) {
|
||||
|
||||
// Check proof of work
|
||||
if (!utils.testTarget(this.bits, this.hash())) {
|
||||
utils.debug('Block failed POW test: %s', this.rhash);
|
||||
ret.reason = 'high-hash';
|
||||
ret.score = 50;
|
||||
return false;
|
||||
@ -83,7 +82,6 @@ AbstractBlock.prototype.verifyHeaders = function verifyHeaders(ret) {
|
||||
|
||||
// Check timestamp against now + 2 hours
|
||||
if (this.ts > utils.now() + 2 * 60 * 60) {
|
||||
utils.debug('Block timestamp is too high: %s', this.rhash);
|
||||
ret.reason = 'time-too-new';
|
||||
ret.score = 0;
|
||||
return false;
|
||||
|
||||
@ -181,7 +181,6 @@ Block.prototype._verify = function _verify(ret) {
|
||||
// Size can't be bigger than MAX_BLOCK_SIZE
|
||||
if (this.txs.length > constants.block.maxSize
|
||||
|| this.getVirtualSize() > constants.block.maxSize) {
|
||||
utils.debug('Block is too large: %s', this.rhash);
|
||||
ret.reason = 'bad-blk-length';
|
||||
ret.score = 100;
|
||||
return false;
|
||||
@ -189,7 +188,6 @@ Block.prototype._verify = function _verify(ret) {
|
||||
|
||||
// First TX must be a coinbase
|
||||
if (!this.txs.length || !this.txs[0].isCoinbase()) {
|
||||
utils.debug('Block has no coinbase: %s', this.rhash);
|
||||
ret.reason = 'bad-cb-missing';
|
||||
ret.score = 100;
|
||||
return false;
|
||||
@ -201,7 +199,6 @@ Block.prototype._verify = function _verify(ret) {
|
||||
|
||||
// The rest of the txs must not be coinbases
|
||||
if (i > 0 && tx.isCoinbase()) {
|
||||
utils.debug('Block more than one coinbase: %s', this.rhash);
|
||||
ret.reason = 'bad-cb-multiple';
|
||||
ret.score = 100;
|
||||
return false;
|
||||
@ -210,7 +207,6 @@ Block.prototype._verify = function _verify(ret) {
|
||||
// Check for duplicate txids
|
||||
hash = tx.hash('hex');
|
||||
if (uniq[hash]) {
|
||||
utils.debug('Block has duplicate txids: %s', this.rhash);
|
||||
ret.reason = 'bad-txns-duplicate';
|
||||
ret.score = 100;
|
||||
return false;
|
||||
@ -220,7 +216,6 @@ Block.prototype._verify = function _verify(ret) {
|
||||
|
||||
// Check merkle root
|
||||
if (this.getMerkleRoot() !== this.merkleRoot) {
|
||||
utils.debug('Block failed merkleroot test: %s', this.rhash);
|
||||
ret.reason = 'bad-txnmrkleroot';
|
||||
ret.score = 100;
|
||||
return false;
|
||||
|
||||
@ -62,25 +62,28 @@ utils.inherits(Chain, EventEmitter);
|
||||
Chain.prototype._init = function _init() {
|
||||
var self = this;
|
||||
|
||||
function getPeer() {
|
||||
function getHost() {
|
||||
var peer;
|
||||
|
||||
if (!self.node || !self.node.pool)
|
||||
return;
|
||||
|
||||
return self.node.pool.peers.load;
|
||||
}
|
||||
peer = self.node.pool.peers.load;
|
||||
|
||||
function getHost() {
|
||||
var peer = getPeer();
|
||||
if (peer)
|
||||
return peer.host;
|
||||
return 'unknown';
|
||||
if (!peer)
|
||||
return 'unknown';
|
||||
|
||||
return peer.host;
|
||||
}
|
||||
|
||||
// Hook into events for debugging
|
||||
// this.on('block', function(block, entry) {
|
||||
// utils.debug('Block %s (%d) added to chain (%s)',
|
||||
// utils.revHex(entry.hash), entry.height, getHost());
|
||||
// });
|
||||
this.on('block', function(block, entry) {
|
||||
if (self.height < 400000)
|
||||
return;
|
||||
|
||||
utils.debug('Block %s (%d) added to chain (%s)',
|
||||
utils.revHex(entry.hash), entry.height, getHost());
|
||||
});
|
||||
|
||||
this.on('competitor', function(block, entry) {
|
||||
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) {
|
||||
var self = this;
|
||||
var flags = constants.flags.MANDATORY_VERIFY_FLAGS;
|
||||
var lockFlags = constants.flags.MANDATORY_LOCKTIME_FLAGS;
|
||||
var height, ts, i, tx, coinbaseHeight;
|
||||
var medianTime, locktimeMedian, segwit;
|
||||
var medianTime, segwit;
|
||||
var ret = {};
|
||||
|
||||
function done(err, result) {
|
||||
@ -414,10 +418,8 @@ Chain.prototype._verify = function _verify(block, prev, callback) {
|
||||
return done(null, flags);
|
||||
|
||||
// Ensure it's not an orphan
|
||||
if (!prev) {
|
||||
utils.debug('Block has no previous entry: %s', block.rhash);
|
||||
if (!prev)
|
||||
return done(new VerifyError(block, 'invalid', 'bad-prevblk', 0));
|
||||
}
|
||||
|
||||
prev.ensureAncestors(function(err) {
|
||||
if (err)
|
||||
@ -427,15 +429,11 @@ Chain.prototype._verify = function _verify(block, prev, callback) {
|
||||
medianTime = prev.getMedianTime();
|
||||
|
||||
// Ensure the timestamp is correct
|
||||
if (block.ts <= medianTime) {
|
||||
utils.debug('Block time is lower than median: %s', block.rhash);
|
||||
if (block.ts <= medianTime)
|
||||
return done(new VerifyError(block, 'invalid', 'time-too-old', 0));
|
||||
}
|
||||
|
||||
if (block.bits !== self.getTarget(prev, block)) {
|
||||
utils.debug('Block is using wrong target: %s', block.rhash);
|
||||
if (block.bits !== self.getTarget(prev, block))
|
||||
return done(new VerifyError(block, 'invalid', 'bad-diffbits', 100));
|
||||
}
|
||||
|
||||
// For some reason bitcoind has p2sh in the
|
||||
// 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
|
||||
// appeared on march 7th 2012, only it did
|
||||
// not have a signature. See:
|
||||
// https://blockchain.info/tx/6a26d2ecb67f27d1fa5524763b49029d7106e91e3cc05743073461a719776192
|
||||
// https://blockchain.info/tx/9c08a4d78931342b37fd5f72900fb9983087e6f46c4a097d8a1f52c74e28eaf6
|
||||
// 6a26d2ecb67f27d1fa5524763b49029d7106e91e3cc05743073461a719776192
|
||||
// 9c08a4d78931342b37fd5f72900fb9983087e6f46c4a097d8a1f52c74e28eaf6
|
||||
if (block.ts < constants.block.bip16time)
|
||||
flags &= ~constants.flags.VERIFY_P2SH;
|
||||
|
||||
// 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);
|
||||
if (block.version < 2 && prev.isOutdated(2))
|
||||
return done(new VerifyError(block, 'obsolete', 'bad-version', 0));
|
||||
}
|
||||
|
||||
// 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);
|
||||
if (block.version < 3 && prev.isOutdated(3))
|
||||
return done(new VerifyError(block, 'obsolete', 'bad-version', 0));
|
||||
}
|
||||
|
||||
// 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);
|
||||
if (block.version < 4 && prev.isOutdated(4))
|
||||
return done(new VerifyError(block, 'obsolete', 'bad-version', 0));
|
||||
}
|
||||
|
||||
// Only allow version 5 blocks (segwit)
|
||||
// once the majority of blocks are using it.
|
||||
if (network.segwitHeight !== -1 && height >= network.segwitHeight) {
|
||||
if (block.version < 5 && prev.isOutdated(5)) {
|
||||
utils.debug('Block is outdated (v5): %s', block.rhash);
|
||||
if (block.version < 5 && prev.isOutdated(5))
|
||||
return done(new VerifyError(block, 'obsolete', 'bad-version', 0));
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
// }
|
||||
// if (block.version < 8 && prev.isOutdated(8))
|
||||
// return done(new VerifyError(block, 'obsolete', 'bad-version', 0));
|
||||
|
||||
// Make sure the height contained in the coinbase is correct.
|
||||
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))
|
||||
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 (block.version >= 5 && prev.isUpgraded(5)) {
|
||||
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.
|
||||
if (block.type !== 'block')
|
||||
return done(null, flags);
|
||||
|
||||
// Make sure the height contained in the coinbase is correct.
|
||||
if (coinbaseHeight) {
|
||||
if (block.getCoinbaseHeight() !== height) {
|
||||
utils.debug('Block has bad coinbase height: %s', block.rhash);
|
||||
if (block.getCoinbaseHeight() !== height)
|
||||
return done(new VerifyError(block, 'invalid', 'bad-cb-height', 100));
|
||||
}
|
||||
}
|
||||
|
||||
if (block.version >= 5 && segwit) {
|
||||
if (block.commitmentHash !== block.getCommitmentHash()) {
|
||||
utils.debug('Block failed witnessroot test: %s', block.rhash);
|
||||
return done(new VerifyError(block, 'invalid', 'bad-blk-wit-length', 100));
|
||||
return done(new VerifyError(block,
|
||||
'invalid',
|
||||
'bad-blk-wit-length',
|
||||
100));
|
||||
}
|
||||
} else {
|
||||
if (block.hasWitness()) {
|
||||
utils.debug('Unexpected witness data found: %s', block.rhash);
|
||||
return done(new VerifyError(block, 'invalid', 'unexpected-witness', 100));
|
||||
return done(new VerifyError(block,
|
||||
'invalid',
|
||||
'unexpected-witness',
|
||||
100));
|
||||
}
|
||||
}
|
||||
|
||||
// Get timestamp for tx.isFinal().
|
||||
ts = locktimeMedian ? medianTime : block.ts;
|
||||
ts = (lockFlags & constants.flags.MEDIAN_TIME_PAST)
|
||||
? medianTime
|
||||
: block.ts;
|
||||
|
||||
// Check all transactions
|
||||
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
|
||||
// regards to nSequence and nLockTime.
|
||||
if (!tx.isFinal(height, ts)) {
|
||||
utils.debug('TX is not final: %s (%s)', block.rhash, i);
|
||||
return done(new VerifyError(block, 'invalid', 'bad-txns-nonfinal', 10));
|
||||
return done(new VerifyError(block,
|
||||
'invalid',
|
||||
'bad-txns-nonfinal',
|
||||
10));
|
||||
}
|
||||
}
|
||||
|
||||
@ -572,13 +570,14 @@ Chain.prototype._checkDuplicates = function _checkDuplicates(block, prev, callba
|
||||
if (err)
|
||||
return next(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 next();
|
||||
// Blocks 91842 and 91880 created duplicate
|
||||
// txids by using the same exact output script
|
||||
// and extraNonce.
|
||||
if (network.type === 'main') {
|
||||
if (height === 91842 || height === 91880)
|
||||
return next();
|
||||
}
|
||||
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) {
|
||||
var self = this;
|
||||
var height = prev.height + 1;
|
||||
var scriptCheck = true;
|
||||
var historical = false;
|
||||
@ -631,25 +631,28 @@ Chain.prototype._checkInputs = function _checkInputs(block, prev, flags, callbac
|
||||
sigops += tx.getSigops();
|
||||
|
||||
if (sigops > constants.block.maxSigops) {
|
||||
utils.debug('Block has too many sigops: %s', block.rhash);
|
||||
return callback(new VerifyError(block, 'invalid', 'bad-blk-sigops', 100));
|
||||
return callback(new VerifyError(block,
|
||||
'invalid',
|
||||
'bad-blk-sigops',
|
||||
100));
|
||||
}
|
||||
|
||||
// Coinbases do not have prevouts
|
||||
if (tx.isCoinbase())
|
||||
continue;
|
||||
|
||||
if (!tx.checkInputs(height, ret))
|
||||
return callback(new VerifyError(block, 'invalid', ret.reason, ret.score));
|
||||
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];
|
||||
|
||||
// Ensure tx is not double spending an output
|
||||
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!');
|
||||
return callback(new VerifyError(block,
|
||||
'invalid',
|
||||
@ -665,17 +668,6 @@ Chain.prototype._checkInputs = function _checkInputs(block, prev, flags, callbac
|
||||
|
||||
// Verify the scripts
|
||||
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!');
|
||||
return callback(new VerifyError(block,
|
||||
'invalid',
|
||||
@ -699,7 +691,6 @@ Chain.prototype._checkInputs = function _checkInputs(block, prev, flags, callbac
|
||||
return callback(err);
|
||||
|
||||
if (!verified) {
|
||||
utils.debug('Block has invalid inputs: %s', block.rhash);
|
||||
assert(!historical, 'BUG: Invalid inputs in historical data!');
|
||||
return callback(new VerifyError(block,
|
||||
'invalid',
|
||||
@ -1011,17 +1002,74 @@ Chain.prototype.add = function add(block, callback, force) {
|
||||
chain: !!self.invalid[prevHash]
|
||||
});
|
||||
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?
|
||||
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) {
|
||||
if (err)
|
||||
return done(err);
|
||||
|
||||
if (existing || self.hasPending(hash)) {
|
||||
// Do we already have this block?
|
||||
if (existing) {
|
||||
self.emit('exists', block, {
|
||||
height: -1,
|
||||
height: block.getCoinbaseHeight(),
|
||||
hash: hash
|
||||
});
|
||||
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;
|
||||
|
||||
// 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.
|
||||
// We do this even for orphans (peers will send
|
||||
// us their highest block during the initial
|
||||
@ -1590,13 +1590,13 @@ Chain.prototype.getLocator = function getLocator(start, callback, force) {
|
||||
if (typeof height === 'string')
|
||||
return next();
|
||||
|
||||
self.db.get(height, function(err, existing) {
|
||||
self.db.getHash(height, function(err, hash) {
|
||||
if (err)
|
||||
return next(err);
|
||||
|
||||
assert(existing);
|
||||
assert(hash);
|
||||
|
||||
hashes[i] = existing.hash;
|
||||
hashes[i] = hash;
|
||||
|
||||
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
|
||||
*/
|
||||
|
||||
@ -61,6 +61,7 @@ utils.inherits(Mempool, EventEmitter);
|
||||
|
||||
Mempool.flags = constants.flags.STANDARD_VERIFY_FLAGS;
|
||||
Mempool.mandatory = constants.flags.MANDATORY_VERIFY_FLAGS;
|
||||
Mempool.lockFlags = constants.flags.STANDARD_LOCKTIME_FLAGS;
|
||||
|
||||
Mempool.ANCESTOR_LIMIT = 25;
|
||||
Mempool.MAX_MEMPOOL_SIZE = 300 << 20;
|
||||
@ -109,9 +110,13 @@ Mempool.prototype._init = function _init() {
|
||||
else
|
||||
self.size = size;
|
||||
|
||||
unlock();
|
||||
self.loaded = true;
|
||||
self.emit('open');
|
||||
self.chain.open(function(err) {
|
||||
if (err)
|
||||
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.addTX = function addTX(tx, callback, force) {
|
||||
var self = this;
|
||||
var hash, ts, height, now;
|
||||
var flags = Mempool.flags;
|
||||
var lockFlags = Mempool.lockFlags;
|
||||
var ret = {};
|
||||
var now;
|
||||
|
||||
var unlock = this._lock(addTX, [tx, callback], force);
|
||||
if (!unlock)
|
||||
@ -298,8 +304,6 @@ Mempool.prototype.addTX = function addTX(tx, callback, force) {
|
||||
flags |= constants.flags.VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM;
|
||||
}
|
||||
|
||||
hash = tx.hash('hex');
|
||||
|
||||
callback = utils.wrap(callback, unlock);
|
||||
callback = utils.asyncify(callback);
|
||||
|
||||
@ -321,70 +325,72 @@ Mempool.prototype.addTX = function addTX(tx, callback, force) {
|
||||
if (tx.isCoinbase())
|
||||
return callback(new VerifyError(tx, 'invalid', 'coinbase', 100));
|
||||
|
||||
// ts = locktimeFlags & LOCKTIME_MEDIAN_PAST
|
||||
// ? 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) {
|
||||
self.chain.checkFinal(self.chain.tip, tx, lockFlags, function(err, isFinal) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
|
||||
if (exists)
|
||||
return callback(new VerifyError(tx, 'alreadyknown', 'txn-already-in-mempool', 0));
|
||||
if (!isFinal)
|
||||
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)
|
||||
return callback(err);
|
||||
|
||||
if (doubleSpend) {
|
||||
if (exists) {
|
||||
return callback(new VerifyError(tx,
|
||||
'duplicate',
|
||||
'bad-txns-inputs-spent',
|
||||
'alreadyknown',
|
||||
'txn-already-in-mempool',
|
||||
0));
|
||||
}
|
||||
|
||||
self.node.fillCoins(tx, function(err) {
|
||||
self.tx.isDoubleSpend(tx, function(err, doubleSpend) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
|
||||
if (!tx.hasCoins()) {
|
||||
if (self.size > Mempool.MAX_MEMPOOL_SIZE) {
|
||||
return callback(new VerifyError(tx,
|
||||
'insufficientfee',
|
||||
'mempool full',
|
||||
0));
|
||||
}
|
||||
utils.debug('Added orphan %s to mempool.', tx.rhash);
|
||||
return self.storeOrphan(tx, callback);
|
||||
if (doubleSpend) {
|
||||
return callback(new VerifyError(tx,
|
||||
'duplicate',
|
||||
'bad-txns-inputs-spent',
|
||||
0));
|
||||
}
|
||||
|
||||
self.verify(tx, function(err) {
|
||||
self.node.fillCoins(tx, function(err) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
|
||||
self.limitMempoolSize(function(err, result) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
|
||||
if (!result) {
|
||||
if (!tx.hasCoins()) {
|
||||
if (self.size > Mempool.MAX_MEMPOOL_SIZE) {
|
||||
return callback(new VerifyError(tx,
|
||||
'insufficientfee',
|
||||
'mempool full',
|
||||
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) {
|
||||
var self = this;
|
||||
var height = this.chain.height + 1;
|
||||
var lockFlags = Mempool.lockFlags;
|
||||
var flags = Mempool.flags;
|
||||
var mandatory = Mempool.mandatory;
|
||||
var ret = {};
|
||||
@ -448,7 +455,7 @@ Mempool.prototype.verify = function verify(tx, callback) {
|
||||
mandatory |= constants.flags.VERIFY_WITNESS;
|
||||
}
|
||||
|
||||
this.checkMempoolLocks(tx, flags, function(err, result) {
|
||||
this.checkLocks(tx, lockFlags, function(err, result) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
|
||||
@ -756,86 +763,7 @@ Mempool.prototype.getSnapshot = function getSnapshot(callback) {
|
||||
return this.tx.getAllHashes(callback);
|
||||
};
|
||||
|
||||
Mempool.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_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) {
|
||||
Mempool.prototype.checkLocks = function checkLocks(tx, flags, callback) {
|
||||
var self = this;
|
||||
var tip = this.chain.tip;
|
||||
|
||||
@ -851,7 +779,7 @@ Mempool.prototype.checkMempoolLocks = function checkMempoolLocks(tx, flags, call
|
||||
chainwork: tip.chainwork
|
||||
});
|
||||
|
||||
this.checkLocks(tx, flags, index, callback);
|
||||
return this.chain.checkLocks(tx, flags, index, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@ -133,7 +133,6 @@ MerkleBlock.prototype._verify = function _verify(ret) {
|
||||
|
||||
// Verify the partial merkle tree if we are a merkleblock.
|
||||
if (!this._verifyPartial()) {
|
||||
utils.debug('Block failed merkle test: %s', this.rhash);
|
||||
ret.reason = 'bad-txnmrklroot';
|
||||
ret.score = 100;
|
||||
return false;
|
||||
|
||||
@ -294,7 +294,10 @@ exports.flags = {
|
||||
VERIFY_WITNESS: (1 << 10),
|
||||
VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM: (1 << 11),
|
||||
// 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
|
||||
@ -310,10 +313,16 @@ exports.flags.STANDARD_VERIFY_FLAGS =
|
||||
| exports.flags.VERIFY_DISCOURAGE_UPGRADABLE_NOPS
|
||||
| exports.flags.VERIFY_CLEANSTACK
|
||||
| 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_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 = {
|
||||
// What block version to use for new blocks (pre versionbits)
|
||||
|
||||
@ -1997,11 +1997,18 @@ if (utils.isBrowser) {
|
||||
|
||||
function VerifyError(object, code, reason, score) {
|
||||
Error.call(this);
|
||||
|
||||
if (Error.captureStackTrace)
|
||||
Error.captureStackTrace(this, VerifyError);
|
||||
|
||||
this.type = 'VerifyError';
|
||||
|
||||
this.hash = object.hash();
|
||||
this.height = object.height;
|
||||
|
||||
if (object.getCoinbaseHeight && this.height === -1)
|
||||
this.height = object.getCoinbaseHeight();
|
||||
|
||||
this.code = code;
|
||||
this.reason = score === -1 ? null : reason;
|
||||
this.score = score;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user