diff --git a/lib/bcoin/block.js b/lib/bcoin/block.js index f086ca47..620186b1 100644 --- a/lib/bcoin/block.js +++ b/lib/bcoin/block.js @@ -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; diff --git a/lib/bcoin/chain.js b/lib/bcoin/chain.js index 806b0307..5f8e3319 100644 --- a/lib/bcoin/chain.js +++ b/lib/bcoin/chain.js @@ -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) { diff --git a/lib/bcoin/input.js b/lib/bcoin/input.js index b43fef99..1e958146 100644 --- a/lib/bcoin/input.js +++ b/lib/bcoin/input.js @@ -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; }); diff --git a/lib/bcoin/output.js b/lib/bcoin/output.js index 5c415a81..1c3a5123 100644 --- a/lib/bcoin/output.js +++ b/lib/bcoin/output.js @@ -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; }); diff --git a/lib/bcoin/pool.js b/lib/bcoin/pool.js index 6de95dc0..841e0b03 100644 --- a/lib/bcoin/pool.js +++ b/lib/bcoin/pool.js @@ -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)) { diff --git a/lib/bcoin/protocol/constants.js b/lib/bcoin/protocol/constants.js index f74594ce..0ad589a7 100644 --- a/lib/bcoin/protocol/constants.js +++ b/lib/bcoin/protocol/constants.js @@ -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 = { diff --git a/lib/bcoin/protocol/network.js b/lib/bcoin/protocol/network.js index 81246dc8..ca602177 100644 --- a/lib/bcoin/protocol/network.js +++ b/lib/bcoin/protocol/network.js @@ -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 }; diff --git a/lib/bcoin/script.js b/lib/bcoin/script.js index dd95cb2e..59b24225 100644 --- a/lib/bcoin/script.js +++ b/lib/bcoin/script.js @@ -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; }; diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index d42666f5..ebc52b45 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -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; diff --git a/lib/bcoin/utils.js b/lib/bcoin/utils.js index bdeaef15..7250ad5d 100644 --- a/lib/bcoin/utils.js +++ b/lib/bcoin/utils.js @@ -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; }; diff --git a/scripts/update.js b/scripts/update.js index c0a21522..37e3ecca 100644 --- a/scripts/update.js +++ b/scripts/update.js @@ -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]); }