From f71f2d954b6e62b45c38bafa08f77ffacb3b73c2 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 14 Feb 2017 00:29:20 -0800 Subject: [PATCH] chain: optimize reward calculation. --- lib/blockchain/chain.js | 21 ++++++++++++--- lib/primitives/block.js | 37 ------------------------- lib/primitives/tx.js | 60 +++++++++++++++++++---------------------- test/block-test.js | 8 ++++-- test/tx-test.js | 51 ----------------------------------- 5 files changed, 52 insertions(+), 125 deletions(-) diff --git a/lib/blockchain/chain.js b/lib/blockchain/chain.js index 2eae7eec..79d87785 100644 --- a/lib/blockchain/chain.js +++ b/lib/blockchain/chain.js @@ -571,13 +571,15 @@ Chain.prototype.verifyDuplicates = co(function* verifyDuplicates(block, prev, st */ Chain.prototype.verifyInputs = co(function* verifyInputs(block, prev, state) { + var interval = this.network.halvingInterval; var ret = new VerifyResult(); var view = new CoinView(); var height = prev.height + 1; var historical = prev.isHistorical(); var sigops = 0; + var reward = 0; var jobs = []; - var i, tx, valid; + var i, tx, valid, fee; if (this.options.spv) return view; @@ -628,13 +630,24 @@ Chain.prototype.verifyInputs = co(function* verifyInputs(block, prev, state) { // Contextual sanity checks. if (i > 0) { - if (!tx.checkInputs(view, height, ret)) { + fee = tx.checkContext(view, height, ret); + + if (fee === -1) { throw new VerifyError(block, 'invalid', ret.reason, ret.score); } + reward += fee; + + if (reward > consensus.MAX_MONEY) { + throw new VerifyError(block, + 'invalid', + 'bad-cb-amount', + 100); + } + // Push onto verification queue. jobs.push(tx.verifyAsync(view, state.flags)); } @@ -647,7 +660,9 @@ Chain.prototype.verifyInputs = co(function* verifyInputs(block, prev, state) { return view; // Make sure the miner isn't trying to conjure more coins. - if (block.getClaimed() > block.getReward(view, height, this.network)) { + reward += consensus.getReward(height, interval); + + if (block.getClaimed() > reward) { throw new VerifyError(block, 'invalid', 'bad-cb-amount', diff --git a/lib/primitives/block.js b/lib/primitives/block.js index 6b76f096..60aea44b 100644 --- a/lib/primitives/block.js +++ b/lib/primitives/block.js @@ -538,43 +538,6 @@ Block.prototype.getCoinbaseHeight = function getCoinbaseHeight() { return height; }; -/** - * Calculate the block reward. - * @returns {Amount} reward - */ - -Block.prototype.getReward = function getReward(view, height, network) { - var i, tx, reward, fee; - - assert(typeof height === 'number'); - - network = Network.get(network); - reward = consensus.getReward(height, network.halvingInterval); - - for (i = 1; i < this.txs.length; i++) { - tx = this.txs[i]; - - fee = tx.getFee(view); - - if (fee < 0 || fee > consensus.MAX_MONEY) - return -1; - - reward += fee; - - // We don't want to go above 53 bits. - // This is to make the getClaimed check - // fail if the miner mined an evil block. - // Note that this check ONLY works because - // MAX_MONEY is 51 bits. The result of - // (51 bits + 51 bits) is _never_ greater - // than 52 bits. - if (reward < 0 || reward > consensus.MAX_MONEY) - return -1; - } - - return reward; -}; - /** * Get the "claimed" reward by the coinbase. * @returns {Amount} claimed diff --git a/lib/primitives/tx.js b/lib/primitives/tx.js index 95265d31..53cc8973 100644 --- a/lib/primitives/tx.js +++ b/lib/primitives/tx.js @@ -71,8 +71,6 @@ function TX(options) { this._size = -1; this._witness = -1; - this._outputValue = -1; - this._inputValue = -1; this._hashPrevouts = null; this._hashSequence = null; this._hashOutputs = null; @@ -183,9 +181,6 @@ TX.prototype.refresh = function refresh() { this._size = -1; this._witness = -1; - this._outputValue = -1; - this._inputValue = -1; - this._hashPrevouts = null; this._hashSequence = null; this._hashOutputs = null; @@ -891,9 +886,6 @@ TX.prototype.getInputValue = function getInputValue(view) { var total = 0; var i, input, coin; - if (this._inputValue !== -1) - return this._inputValue; - for (i = 0; i < this.inputs.length; i++) { input = this.inputs[i]; coin = view.getOutput(input); @@ -904,9 +896,6 @@ TX.prototype.getInputValue = function getInputValue(view) { total += coin.value; } - if (!this.mutable) - this._inputValue = total; - return total; }; @@ -919,17 +908,11 @@ TX.prototype.getOutputValue = function getOutputValue() { var total = 0; var i, output; - if (this._outputValue !== -1) - return this._outputValue; - for (i = 0; i < this.outputs.length; i++) { output = this.outputs[i]; total += output.value; } - if (!this.mutable) - this._outputValue = total; - return total; }; @@ -1447,9 +1430,6 @@ TX.prototype.isSane = function isSane(ret) { } } - if (!this.mutable) - this._outputValue = total; - return true; }; @@ -1757,6 +1737,25 @@ TX.prototype.getWitnessStandard = function getWitnessStandard(view) { */ TX.prototype.checkInputs = function checkInputs(view, height, ret) { + return this.checkContext(view, height, ret) !== -1; +}; + +/** + * Perform contextual checks to verify input, output, + * and fee values, as well as coinbase spend maturity + * (coinbases can only be spent 100 blocks or more + * after they're created). Note that this function is + * consensus critical. + * @param {CoinView} view + * @param {Number} height - Height at which the + * transaction is being spent. In the mempool this is + * the chain height plus one at the time it entered the pool. + * @param {Object?} ret - Return object, may be + * set with properties `reason` and `score`. + * @returns {Amount} + */ + +TX.prototype.checkContext = function checkContext(view, height, ret) { var total = 0; var i, input, coins, coin, fee, value; @@ -1772,14 +1771,14 @@ TX.prototype.checkInputs = function checkInputs(view, height, ret) { if (!coins) { ret.reason = 'bad-txns-inputs-missingorspent'; ret.score = 0; - return false; + return -1; } if (coins.coinbase) { if (height - coins.height < consensus.COINBASE_MATURITY) { ret.reason = 'bad-txns-premature-spend-of-coinbase'; ret.score = 0; - return false; + return -1; } } @@ -1788,13 +1787,13 @@ TX.prototype.checkInputs = function checkInputs(view, height, ret) { if (!coin) { ret.reason = 'bad-txns-inputs-missingorspent'; ret.score = 0; - return false; + return -1; } if (coin.value < 0 || coin.value > consensus.MAX_MONEY) { ret.reason = 'bad-txns-inputvalues-outofrange'; ret.score = 100; - return false; + return -1; } total += coin.value; @@ -1802,7 +1801,7 @@ TX.prototype.checkInputs = function checkInputs(view, height, ret) { if (total < 0 || total > consensus.MAX_MONEY) { ret.reason = 'bad-txns-inputvalues-outofrange'; ret.score = 100; - return false; + return -1; } } @@ -1812,7 +1811,7 @@ TX.prototype.checkInputs = function checkInputs(view, height, ret) { if (total < value) { ret.reason = 'bad-txns-in-belowout'; ret.score = 100; - return false; + return -1; } fee = total - value; @@ -1820,19 +1819,16 @@ TX.prototype.checkInputs = function checkInputs(view, height, ret) { if (fee < 0) { ret.reason = 'bad-txns-fee-negative'; ret.score = 100; - return false; + return -1; } if (fee > consensus.MAX_MONEY) { ret.reason = 'bad-txns-fee-outofrange'; ret.score = 100; - return false; + return -1; } - if (!this.mutable) - this._inputValue = total; - - return true; + return fee; }; /** diff --git a/test/block-test.js b/test/block-test.js index d5a31f72..e656a499 100644 --- a/test/block-test.js +++ b/test/block-test.js @@ -189,6 +189,7 @@ describe('Block', function() { var view = new CoinView(); var height = block300025.height; var sigops = 0; + var reward = 0; var i, j, tx, input, coin, flags; for (i = 1; i < block300025.txs.length; i++) { @@ -216,11 +217,14 @@ describe('Block', function() { assert(!tx.hasWitness()); sigops += tx.getSigopsCost(view, flags); view.addTX(tx, height); + reward += tx.getFee(view); } + reward += consensus.getReward(height, 210000); + assert.equal(sigops, 5280); - assert.equal(block.getReward(view, height), 2507773345); - assert.equal(block.getReward(view, height), block.txs[0].outputs[0].value); + assert.equal(reward, 2507773345); + assert.equal(reward, block.txs[0].outputs[0].value); }); it('should fail with a bad merkle root', function() { diff --git a/test/tx-test.js b/test/tx-test.js index d34a9264..0967d3bd 100644 --- a/test/tx-test.js +++ b/test/tx-test.js @@ -513,32 +513,6 @@ describe('TX', function() { assert.ok(!tx.checkInputs(view, 0)); }); - it('should fail on >51 bit fees from multiple txs', function() { - var view = new CoinView(); - var genesis = Network.get().genesis; - var block = new Block(genesis); - var i, tx; - - for (i = 0; i < 3; i++) { - tx = new TX({ - version: 1, - flag: 1, - inputs: [ - createInput(Math.floor(consensus.MAX_MONEY / 2), view) - ], - outputs: [{ - script: [], - value: 0 - }], - locktime: 0 - }); - - block.txs.push(tx); - } - - assert.equal(block.getReward(view, 0), -1); - }); - it('should fail to parse >53 bit values', function() { var view = new CoinView(); var tx, raw; @@ -689,31 +663,6 @@ describe('TX', function() { assert.ok(tx.isSane()); assert.ok(!tx.checkInputs(view, 0)); }); - - it('should fail on >53 bit fees from multiple txs', function() { - var view = new CoinView(); - var genesis = Network.get().genesis; - var block = new Block(genesis); - var i, tx; - - for (i = 0; i < 3; i++) { - tx = new TX({ - version: 1, - flag: 1, - inputs: [ - createInput(MAX, view) - ], - outputs: [{ - script: [], - value: 0 - }], - locktime: 0 - }); - block.txs.push(tx); - } - - assert.equal(block.getReward(view, 0), -1); - }); }); it('should count sigops for multisig', function() {