chain: optimize reward calculation.

This commit is contained in:
Christopher Jeffrey 2017-02-14 00:29:20 -08:00
parent 2a69b7ab7a
commit f71f2d954b
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
5 changed files with 52 additions and 125 deletions

View File

@ -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',

View File

@ -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

View File

@ -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;
};
/**

View File

@ -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() {

View File

@ -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() {