more block verification.

This commit is contained in:
Christopher Jeffrey 2016-01-06 13:08:50 -08:00
parent e1ec89fe7d
commit 8d120a4fd6
11 changed files with 372 additions and 71 deletions

View File

@ -271,79 +271,171 @@ Block.prototype._verify = function _verify() {
};
Block.prototype.postVerify = function postVerify() {
var prev, i, tx, cb, sigops;
var flags = {};
var bip16time = 1333238400;
var strictp2sh = this.ts >= bip16time;
var prev, height, i, j, tx, cb, sigops, input;
if (this.subtype !== 'block')
return true;
if (this.isGenesis())
return true;
if (!this.chain)
return true;
prev = this.chain.getBlock(this.prevBlock);
height = prev.height + 1;
// Ensure it's not an orphan
if (!prev)
if (!prev) {
console.log('NO PREV: %s', height);
return false;
}
// Ensure the timestamp is correct
if (this.ts <= prev.getMedianTime())
if (this.ts <= prev.getMedianTime()) {
console.log('BAD TIME: %s', height);
return false;
}
// Test all txs
for (i = 0; i < this.txs.length; i++) {
tx = this.txs[i];
// TXs must be finalized with regards to seq and locktime
if (!tx.isFinal(this, prev))
if (!tx.isFinal(this, prev)) {
console.log('IS NOT FINAL: %s', height);
return false;
}
}
if (this.bits !== this.chain.target(prev, this))
// Ensure the miner's target is equal to what we expect
if (this.bits !== this.chain.target(prev, this)) {
console.log('BAD TARGET: %s', height);
return false;
}
if (this.version < 2 && prev.isOutdated(2))
// Only allow version 2 blocks (coinbase height)
// once the majority of blocks are using it.
if (this.version < 2 && prev.isOutdated(2)) {
console.log('OUTDATED 2: %s', height);
return false;
}
if (this.version < 3 && prev.isOutdated(3))
// Only allow version 3 blocks (sig validation)
// once the majority of blocks are using it.
if (this.version < 3 && prev.isOutdated(3)) {
console.log('OUTDATED 3: %s', height);
return false;
}
if (this.version < 4 && prev.isOutdated(4))
// Only allow version 4 blocks (checklocktimeverify)
// once the majority of blocks are using it.
if (this.version < 4 && prev.isOutdated(4)) {
console.log('OUTDATED 4: %s', height);
return false;
}
// Enforce height in coinbase
if (this.version >= 2 && prev.needsUpgrade(2)) {
// Make sure the height contained in the
// coinbase is correct.
if (this.version >= 2 && prev.isUpgraded(2)) {
cb = bcoin.script.isCoinbase(this.txs[0].inputs[0].script, this);
if (!cb)
if (!cb) {
console.log('BAD COINBASE: %s', height);
return false;
}
if (cb.height !== prev.height + 1)
if (cb.height !== height) {
console.log('BAD COINBASE HEIGHT: %s', height);
return false;
}
}
// sig validation (bip66)
if (this.version >= 3 && prev.needsUpgrade(3))
// Signature validation is now enforced (bip66)
if (this.version >= 3 && prev.isUpgraded(3))
flags.strictder = true;
// checklocktimeverify (bip65)
if (this.version >= 4 && prev.needsUpgrade(4))
// CHECKLOCKTIMEVERIFY is now usable (bip65)
if (this.version >= 4 && prev.isUpgraded(4))
flags.cltv = true;
// Check for sigops limits
sigops = 0;
for (i = 0; i < this.txs.length; i++) {
if (tx.sigops(true) > constants.script.maxTxSigops)
return false;
sigops += tx.sigops(strictp2sh);
if (sigops > constants.script.maxBlockSigops)
tx = this.txs[i];
// Bitcoind does not check for this when accepting
// a block even though it probably should.
// if (tx.sigops(true) > constants.script.maxTxSigops) {
// // Block 71036 abused checksig to
// // include a huge number of sigops.
// if (!(network.type === 'main' && height === 71036))
// return false;
// }
// Start counting P2SH sigops once block
// timestamps reach March 31st, 2012.
if (this.ts >= constants.block.bip16time)
sigops += tx.sigops(true);
else
sigops += tx.sigops();
if (sigops > constants.script.maxBlockSigops) {
console.log('BAD SIGOPS: %s', height);
return false;
}
}
// BIP30 - Ensure there are no duplicate txids
for (i = 0; i < this.txs.length; i++) {
tx = this.txs[i];
if (this.chain.index[tx.hash('hex')]) {
// Blocks 91842 and 91880 created duplicate
// txids by carefully crafting the coinbases.
if (!(network.type === 'main' && height === 91842 && height === 91880))
return false;
}
}
// If we are an ancestor of a checkpoint,
// we can skip the input verification.
if (height < network.checkpoints.lastHeight && !network.checkpoints[height])
flags.noScriptChecks = true;
// Verify the inputs of every tx (CheckInputs)
for (i = 0; i < this.txs.length; i++) {
tx = this.txs[i];
if (tx.isCoinbase())
continue;
if (flags.noScriptChecks)
continue;
for (j = 0; j < tx.inputs.length; j++) {
input = tx.inputs[j];
if (!input.out.tx)
continue;
assert(input.out.tx);
if (!tx.verify(j, true, flags))
return false;
// if (this.chain.isSpent(input.out.hash, input.out.index))
// return false;
}
}
return true;
};
Block.prototype.isGenesis = function isGenesis() {
return this.hash('hex') === utils.toHex(network.genesis._hash);
};
Block.prototype.getHeight = function getHeight() {
if (!this.chain)
return -1;

View File

@ -600,16 +600,17 @@ Chain.prototype.height = function height() {
return this.getTip().height;
};
// /home/chjj/bitcoin/src/pow.cpp
Chain.prototype.target = function target(last, block) {
var powLimit = utils.toCompact(network.powLimit);
var interval = network.powTargetTimespan / network.powTargetSpacing | 0;
var first, ts;
var ts, first, i;
// Genesis
if (!last)
last = this.getTip();
return powLimit;
// Do not retarget
if ((last.height + 1) % interval) {
if ((last.height + 1) % network.powDiffInterval !== 0) {
if (network.powAllowMinDifficultyBlocks) {
// Special behavior for testnet:
ts = block ? (block.ts || block) : utils.now();
@ -617,7 +618,7 @@ Chain.prototype.target = function target(last, block) {
return powLimit;
while (last.prev
&& last.height % interval !== 0
&& last.height % network.powDiffInterval !== 0
&& last.bits !== powLimit) {
last = last.prev;
}
@ -628,34 +629,39 @@ Chain.prototype.target = function target(last, block) {
}
// Back 2 weeks
first = this.index.entries[last.height - (interval - 1)];
// first = last;
// for (i = 0; first && i < network.powDiffInterval - 1; i++)
// first = first.prev;
if (!first)
return 0;
// Back 2 weeks
first = this.index.entries[last.height - (network.powDiffInterval - 1)];
assert(first);
return this.retarget(last, first);
};
Chain.prototype.retarget = function retarget(last, first) {
var powTargetTimespan = new bn(network.powTargetTimespan);
var actualTimespan, powLimit, target;
var actualTimespan, target;
if (network.powNoRetargeting)
return last.bits;
actualTimespan = new bn(last.ts).subn(first.ts);
actualTimespan = new bn(last.ts - first.ts);
target = utils.fromCompact(last.bits);
if (actualTimespan.cmp(powTargetTimespan.divn(4)) < 0)
actualTimespan = powTargetTimespan.divn(4);
if (actualTimespan.cmp(powTargetTimespan.muln(4)) > 0)
actualTimespan = powTargetTimespan.muln(4);
powLimit = network.powLimit;
target = utils.fromCompact(last.bits);
target.imul(actualTimespan);
target = target.div(powTargetTimespan);
if (target.cmp(powLimit) > 0)
target = powLimit.clone();
if (target.cmp(network.powLimit) > 0)
target = network.powLimit.clone();
return utils.toCompact(target);
};
@ -832,13 +838,11 @@ ChainBlock.prototype.getMedianTime = function() {
};
ChainBlock.prototype.isOutdated = function(version) {
return this.isSuperMajority(version,
network.block.majorityRejectBlockOutdated);
return this.isSuperMajority(version, network.block.majorityRejectOutdated);
};
ChainBlock.prototype.needsUpgrade = function(version) {
return this.isSuperMajority(version,
network.block.majorityEnforceBlockUpgrade);
ChainBlock.prototype.isUpgraded = function(version) {
return this.isSuperMajority(version, network.block.majorityEnforceUpgrade);
};
ChainBlock.prototype.isSuperMajority = function(version, required) {

View File

@ -100,6 +100,10 @@ Input.prototype.__defineGetter__('addrs', function() {
return this.data.addrs || [];
});
Input.prototype.__defineGetter__('scriptaddr', function() {
return this.data.scriptaddr;
});
Input.prototype.__defineGetter__('m', function() {
return this.data.m || 1;
});

View File

@ -84,6 +84,10 @@ Output.prototype.__defineGetter__('addrs', function() {
return this.data.addrs || [];
});
Output.prototype.__defineGetter__('scriptaddr', function() {
return this.data.scriptaddr;
});
Output.prototype.__defineGetter__('m', function() {
return this.data.m || 1;
});

View File

@ -467,6 +467,10 @@ Pool.prototype._handleBlocks = function _handleBlocks(hashes, peer) {
Pool.prototype._handleInv = function _handleInv(hashes, peer) {
var i, hash;
// Ignore for now if we're still syncing
if (!this.chain.isFull())
return;
for (i = 0; i < hashes.length; i++) {
hash = utils.toHex(hashes[i]);
if (!this.chain.has(hash)) {

View File

@ -156,7 +156,15 @@ exports.block = {
maxSize: 1000000,
maxSigops: 1000000 / 50,
maxOrphanTx: 1000000 / 100,
medianTimeSpan: 11
medianTimeSpan: 11,
bip16time: 1333238400
};
exports.tx = {
maxSize: 100000,
fee: 10000,
dust: 5460,
bareMultisig: true
};
exports.script = {
@ -167,7 +175,9 @@ exports.script = {
maxPubkeysPerMultisig: 20,
maxBlockSigops: exports.block.maxSize / 50,
maxScripthashSigops: 15,
maxTxSigops: exports.block.maxSize / 50 / 5
maxTxSigops: exports.block.maxSize / 50 / 5,
maxOpReturnBytes: 83,
maxOpReturn: 80
};
exports.reject = {

View File

@ -80,6 +80,7 @@ main.checkpoints = main.checkpoints.reduce(function(out, block) {
main.checkpoints.tsLastCheckpoint = 1397080064;
main.checkpoints.txsLastCheckpoint = 36544669;
main.checkpoints.txsPerDay = 60000.0;
main.checkpoints.lastHeight = Object.keys(main.checkpoints).sort().pop();
main.halvingInterval = 210000;
@ -137,12 +138,13 @@ main.powLimit = new bn(
);
main.powTargetTimespan = 14 * 24 * 60 * 60; // two weeks
main.powTargetSpacing = 10 * 60;
main.powDiffInterval = main.powTargetTimespan / main.powTargetSpacing | 0;
main.powAllowMinDifficultyBlocks = false;
main.powNoRetargeting = false;
main.block = {
majorityEnforceBlockUpgrade: 750,
majorityRejectBlockOutdated: 950,
majorityEnforceUpgrade: 750,
majorityRejectOutdated: 950,
majorityWindow: 1000
};
@ -193,6 +195,7 @@ testnet.checkpoints = testnet.checkpoints.reduce(function(out, block) {
testnet.checkpoints.tsLastCheckpoint = 1338180505;
testnet.checkpoints.txsLastCheckpoint = 16341;
testnet.checkpoints.txsPerDay = 300;
testnet.checkpoints.lastHeight = Object.keys(testnet.checkpoints).sort().pop();
testnet.halvingInterval = 210000;
@ -241,11 +244,12 @@ testnet.powLimit = new bn(
);
testnet.powTargetTimespan = 14 * 24 * 60 * 60; // two weeks
testnet.powTargetSpacing = 10 * 60;
testnet.powDiffInterval = testnet.powTargetTimespan / testnet.powTargetSpacing | 0;
testnet.powAllowMinDifficultyBlocks = true;
testnet.powNoRetargeting = false;
testnet.block = {
majorityEnforceBlockUpgrade: 51,
majorityRejectBlockOutdated: 75,
majorityEnforceUpgrade: 51,
majorityRejectOutdated: 75,
majorityWindow: 100
};

View File

@ -232,9 +232,12 @@ script._next = function _next(to, s, pc) {
return -1;
};
script.execute = function execute(s, stack, tx, index, recurse) {
script.execute = function execute(s, stack, tx, index, flags, recurse) {
s = s.slice();
if (!flags)
flags = {};
if (s.length > constants.script.maxOps)
return false;
@ -806,7 +809,7 @@ script.execute = function execute(s, stack, tx, index, recurse) {
}
case 'eval_': {
// OP_EVAL = OP_NOP1
if (!script.allowEval)
if (!flags.allowEval)
break;
recurse = recurse || 0;
@ -828,7 +831,7 @@ script.execute = function execute(s, stack, tx, index, recurse) {
if (res)
return false;
res = script.execute(evalScript, stack, tx, index, recurse);
res = script.execute(evalScript, stack, tx, index, flags, recurse);
if (!res)
return false;
@ -847,13 +850,13 @@ script.execute = function execute(s, stack, tx, index, recurse) {
return true;
};
script.exec = function exec(input, output, tx, i, recurse) {
script.exec = function exec(input, output, tx, i, flags) {
var stack = [];
var res;
script.execute(input, stack, tx, i, recurse);
script.execute(input, stack, tx, i, flags);
res = script.execute(output, stack, tx, i, recurse);
res = script.execute(output, stack, tx, i, flags);
if (!res || stack.length === 0 || new bn(stack.pop()).cmpn(0) === 0)
return false;
@ -888,6 +891,34 @@ script.standard = function standard(s) {
|| null;
};
script.isStandard = function isStandard(s) {
var m, n;
var type = script.standard(s);
if (!type)
return false;
if (type === 'multisig') {
m = new bn(s[0]).toNumber();
n = new bn(s[s.length - 2]).toNumber();
if (n < 1 || n > 3)
return false;
if (m < 1 || m > n)
return false;
} else if (type === 'colored') {
if (script.size(s) > constants.script.maxOpReturnBytes)
return false;
}
return type != null;
};
script.size = function size(s) {
if (s._raw)
return s._raw.length;
return bcoin.script.encode(s).length;
};
script.lockTime = function lockTime(s) {
var lock = s[0];
var res = s.length > 3
@ -1053,7 +1084,7 @@ script.isColored = function isColored(s) {
return s[0] === 'ret'
&& Array.isArray(s[1])
&& s[1].length <= 40;
&& s[1].length <= constants.script.maxOpReturn;
};
script.colored = function colored(s) {
@ -1411,6 +1442,8 @@ script.pushOnly = function pushOnly(s) {
op = s[i];
if (Array.isArray(op) || (op >= 1 && op <= 16))
continue;
if (constants.opcodes[op] == null)
return false;
return false;
}
return true;
@ -1425,6 +1458,8 @@ script.sigops = function sigops(s, accurate) {
op = s[i];
if (Array.isArray(op))
continue;
if (constants.opcodes[op] == null)
return 0;
if (op === 'checksig' || op === 'checksigverify') {
n++;
} else if (op === 'checkmultisig' || op === 'checkmultisigverify') {
@ -1435,14 +1470,12 @@ script.sigops = function sigops(s, accurate) {
}
}
lastOp = op;
if (lastOp === null)
return 0;
}
return n;
};
script.sigopsScripthash = function sigopsScripthash(s, accurate) {
script.sigopsScripthash = function sigopsScripthash(s) {
if (!script.isScripthashInput(s))
return 0;
@ -1451,5 +1484,40 @@ script.sigopsScripthash = function sigopsScripthash(s, accurate) {
s = script.decode(input[input.length - 1]);
return script.sigops(s, accurate);
return script.sigops(s, true);
};
script.args = function args(s) {
var type, pubs, m;
s = bcoin.script.subscript(s);
if (script.lockTime(s))
s = s.slice(3);
type = script.standard(s);
if (type === 'pubkey')
return 1;
if (type === 'pubkeyhash')
return 2;
if (type === 'multisig') {
pubs = bcoin.script.isMultisig(s);
if (!pub)
return -1;
m = new bn(s[0]).toNumber();
if (pubs.length < 1 || m < 1)
return -1;
return m + 1;
}
if (this.type === 'scripthash')
return 1;
if (this.type === 'colored')
return -1;
return -1;
};

View File

@ -67,8 +67,8 @@ function TX(data, block) {
this.ps = this.ts === 0 ? utils.now() : 0;
}
TX.fee = 10000;
TX.dust = 5460;
TX.fee = constants.tx.fee;
TX.dust = constants.tx.dust;
TX.prototype.clone = function clone() {
return new TX(this);
@ -514,7 +514,7 @@ TX.prototype.subscriptHash = function subscriptHash(index, s, type) {
return hash;
};
TX.prototype.verify = function verify(index, force) {
TX.prototype.verify = function verify(index, force, flags) {
// Valid if included in block
if (!force && this.ts !== 0)
return true;
@ -543,8 +543,8 @@ TX.prototype.verify = function verify(index, force) {
return false;
}
bcoin.script.execute(input.script, stack, this, i);
res = bcoin.script.execute(prev, stack, this, i);
bcoin.script.execute(input.script, stack, this, i, flags);
res = bcoin.script.execute(prev, stack, this, i, flags);
if (!res || stack.length === 0 || new bn(stack.pop()).cmpn(0) === 0)
return false;
@ -554,7 +554,7 @@ TX.prototype.verify = function verify(index, force) {
if (!Array.isArray(redeem))
return false;
redeem = bcoin.script.decode(redeem);
res = bcoin.script.execute(redeem, stack, this, i);
res = bcoin.script.execute(redeem, stack, this, i, flags);
if (!res || stack.length === 0 || new bn(stack.pop()).cmpn(0) === 0)
return false;
}
@ -881,19 +881,126 @@ TX.prototype._isFinal = function _isFinal(height, ts) {
return true;
};
TX.prototype.sigops = function sigops(scripthash) {
TX.prototype.sigops = function sigops(scripthash, accurate) {
var n = 0;
this.inputs.forEach(function(input) {
n += bcoin.script.sigops(input.script);
if (scripthash)
n += bcoin.script.sigops(input.script, accurate);
if (scripthash && !this.isCoinbase())
n += bcoin.script.sigopsScripthash(input.script);
});
}, true);
this.outputs.forEach(function(output) {
n += bcoin.script.sigops(output.script);
});
n += bcoin.script.sigops(output.script, accurate);
}, this);
return n;
};
TX.prototype.isStandard = function isStandard() {
var i, input, output, type;
var colored = 0;
if (this.version > constants.tx.version || this.version < 1)
return false;
if (this.size() > constants.tx.maxSize)
return false;
for (i = 0; i < this.inputs.length; i++) {
input = this.inputs[i];
if (script.size(input.script) > 1650)
return false;
if (!bcoin.script.pushOnly(input.script))
return false;
}
for (i = 0; i < this.outputs.length; i++) {
output = this.outputs[i];
type = bcoin.script.standard(output.script);
if (!bcoin.script.isStandard(output.script))
return false;
if (!type)
return false;
if (type === 'colored') {
colored++;
continue;
}
if (type === 'multisig' && !constants.tx.bareMultisig)
return false;
if (output.value.cmpn(constants.tx.dust) < 0)
return false;
}
if (colored > 1)
return false;
return true;
};
TX.prototype.isStandardInputs = function isStandardInputs(flags) {
var i, input, prev, args, stack, res, s, targs;
if (this.isCoinbase())
return true;
for (i = 0; i < this.inputs.length; i++) {
input = this.inputs[i];
if (!input.out.tx)
return false;
prev = input.out.tx[input.out.index];
if (!prev)
return false;
args = bcoin.script.args(prev.script);
if (args < 0)
return false;
stack = [];
res = bcoin.script.execute(input.script, stack, this, i, flags);
if (!res)
return false;
if (bcoin.script.isScripthash(prev.script)) {
if (stack.length === 0)
return false;
s = stack[stack.length - 1];
if (!Array.isArray(s))
return false;
s = bcoin.script.decode(s);
if (bcoin.script.standard(s)) {
targs = bcoin.script.args(s);
if (targs < 0)
return false;
args += targs;
} else {
// Bitcoind returns here whether true
// or false... strange behavior (bug?)
return script.sigops(s, true) <= constants.script.maxScripthashSigops;
}
}
if (stack.length !== args)
return false;
}
return true;
};
TX.prototype.getHeight = function getHeight() {
if (!this.chain)
return -1;

View File

@ -787,6 +787,10 @@ utils.fromCompact = function fromCompact(compact) {
if (negative)
num.ineg();
// var overflow = mantissa !== 0 && ((exponent > 34)
// || (mantissa > 0xff && exponent > 33)
// || (mantissa > 0xffff && exponent > 32));
return num;
};

View File

@ -12,7 +12,7 @@ var pool = bcoin.pool({
redundancy: 1,
parallel: 4000,
loadWindow: 750,
createConnection: function() {
createConnection_: function() {
console.log('connecting...');
return net.connect(8333, addrs[(Math.random() * addrs.length) | 0]);
}