mirror of
https://github.com/bitcoinj/bitcoinj.git
synced 2025-03-10 09:20:04 +01:00
Check transaction fees and block subsidies in conenctTransactions.
After this commit, bitcoinj implements all non-script-executing checks at block connect time that the reference implementation does.
This commit is contained in:
parent
ef6e1b89ca
commit
90f5ab7e47
1 changed files with 45 additions and 1 deletions
|
@ -19,6 +19,7 @@ package com.google.bitcoin.core;
|
||||||
import com.google.bitcoin.store.BlockStoreException;
|
import com.google.bitcoin.store.BlockStoreException;
|
||||||
import com.google.bitcoin.store.FullPrunedBlockStore;
|
import com.google.bitcoin.store.FullPrunedBlockStore;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -121,8 +122,12 @@ public class FullPrunedBlockChain extends AbstractBlockChain {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
BigInteger totalFees = BigInteger.ZERO;
|
||||||
|
BigInteger coinbaseValue = null;
|
||||||
for (Transaction tx : block.transactions) {
|
for (Transaction tx : block.transactions) {
|
||||||
boolean isCoinBase = tx.isCoinBase();
|
boolean isCoinBase = tx.isCoinBase();
|
||||||
|
BigInteger valueIn = BigInteger.ZERO;
|
||||||
|
BigInteger valueOut = BigInteger.ZERO;
|
||||||
if (!isCoinBase) {
|
if (!isCoinBase) {
|
||||||
// For each input of the transaction remove the corresponding output from the set of unspent
|
// For each input of the transaction remove the corresponding output from the set of unspent
|
||||||
// outputs.
|
// outputs.
|
||||||
|
@ -137,6 +142,7 @@ public class FullPrunedBlockChain extends AbstractBlockChain {
|
||||||
if (height - prevOut.getHeight() < params.getSpendableCoinbaseDepth())
|
if (height - prevOut.getHeight() < params.getSpendableCoinbaseDepth())
|
||||||
throw new VerificationException("Tried to spend coinbase at depth " + (height - prevOut.getHeight()));
|
throw new VerificationException("Tried to spend coinbase at depth " + (height - prevOut.getHeight()));
|
||||||
// TODO: Check we're not spending the genesis transaction here. Satoshis code won't allow it.
|
// TODO: Check we're not spending the genesis transaction here. Satoshis code won't allow it.
|
||||||
|
valueIn = valueIn.add(prevOut.getValue());
|
||||||
if (enforceBIP16) {
|
if (enforceBIP16) {
|
||||||
try {
|
try {
|
||||||
if (new Script(params, prevOut.getScriptBytes(), 0, prevOut.getScriptBytes().length).isPayToScriptHash())
|
if (new Script(params, prevOut.getScriptBytes(), 0, prevOut.getScriptBytes().length).isPayToScriptHash())
|
||||||
|
@ -154,13 +160,27 @@ public class FullPrunedBlockChain extends AbstractBlockChain {
|
||||||
}
|
}
|
||||||
Sha256Hash hash = tx.getHash();
|
Sha256Hash hash = tx.getHash();
|
||||||
for (TransactionOutput out : tx.getOutputs()) {
|
for (TransactionOutput out : tx.getOutputs()) {
|
||||||
|
valueOut = valueOut.add(out.getValue());
|
||||||
// For each output, add it to the set of unspent outputs so it can be consumed in future.
|
// For each output, add it to the set of unspent outputs so it can be consumed in future.
|
||||||
StoredTransactionOutput newOut = new StoredTransactionOutput(hash, out.getIndex(), out.getValue(),
|
StoredTransactionOutput newOut = new StoredTransactionOutput(hash, out.getIndex(), out.getValue(),
|
||||||
height, isCoinBase, out.getScriptBytes());
|
height, isCoinBase, out.getScriptBytes());
|
||||||
blockStore.addUnspentTransactionOutput(newOut);
|
blockStore.addUnspentTransactionOutput(newOut);
|
||||||
txOutsCreated.add(newOut);
|
txOutsCreated.add(newOut);
|
||||||
}
|
}
|
||||||
|
// All values were already checked for being non-negative (as it is verified in Transaction.verify())
|
||||||
|
// but we check again here just for defence in depth. Transactions with zero output value are OK.
|
||||||
|
if (valueOut.compareTo(BigInteger.ZERO) < 0 || valueOut.compareTo(params.MAX_MONEY) > 0)
|
||||||
|
throw new VerificationException("Transaction output value out of rage");
|
||||||
|
if (isCoinBase) {
|
||||||
|
coinbaseValue = valueOut;
|
||||||
|
} else {
|
||||||
|
if (valueIn.compareTo(valueOut) < 0 || valueIn.compareTo(params.MAX_MONEY) > 0)
|
||||||
|
throw new VerificationException("Transaction input value out of range");
|
||||||
|
totalFees = totalFees.add(valueIn.subtract(valueOut));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if (totalFees.compareTo(params.MAX_MONEY) > 0 || getBlockInflation(height).add(totalFees).compareTo(coinbaseValue) < 0)
|
||||||
|
throw new VerificationException("Transaction fees out of range");
|
||||||
} catch (VerificationException e) {
|
} catch (VerificationException e) {
|
||||||
blockStore.abortDatabaseBatchWrite();
|
blockStore.abortDatabaseBatchWrite();
|
||||||
throw e;
|
throw e;
|
||||||
|
@ -171,6 +191,10 @@ public class FullPrunedBlockChain extends AbstractBlockChain {
|
||||||
return new TransactionOutputChanges(txOutsCreated, txOutsSpent);
|
return new TransactionOutputChanges(txOutsCreated, txOutsSpent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private BigInteger getBlockInflation(int height) {
|
||||||
|
return Utils.toNanoCoins(50, 0).shiftRight(height / 210000);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
/**
|
/**
|
||||||
* Used during reorgs to connect a block previously on a fork
|
* Used during reorgs to connect a block previously on a fork
|
||||||
|
@ -202,8 +226,12 @@ public class FullPrunedBlockChain extends AbstractBlockChain {
|
||||||
throw new VerificationException("Block failed BIP30 test!");
|
throw new VerificationException("Block failed BIP30 test!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (StoredTransaction tx : transactions) {
|
BigInteger totalFees = BigInteger.ZERO;
|
||||||
|
BigInteger coinbaseValue = null;
|
||||||
|
for(StoredTransaction tx : transactions) {
|
||||||
boolean isCoinBase = tx.isCoinBase();
|
boolean isCoinBase = tx.isCoinBase();
|
||||||
|
BigInteger valueIn = BigInteger.ZERO;
|
||||||
|
BigInteger valueOut = BigInteger.ZERO;
|
||||||
if (!isCoinBase)
|
if (!isCoinBase)
|
||||||
for(TransactionInput in : tx.getInputs()) {
|
for(TransactionInput in : tx.getInputs()) {
|
||||||
StoredTransactionOutput prevOut = blockStore.getTransactionOutput(in.getOutpoint().getHash(),
|
StoredTransactionOutput prevOut = blockStore.getTransactionOutput(in.getOutpoint().getHash(),
|
||||||
|
@ -212,6 +240,7 @@ public class FullPrunedBlockChain extends AbstractBlockChain {
|
||||||
throw new VerificationException("Attempted spend of a non-existent or already spent output!");
|
throw new VerificationException("Attempted spend of a non-existent or already spent output!");
|
||||||
if (newBlock.getHeight() - prevOut.getHeight() < params.getSpendableCoinbaseDepth())
|
if (newBlock.getHeight() - prevOut.getHeight() < params.getSpendableCoinbaseDepth())
|
||||||
throw new VerificationException("Tried to spend coinbase at depth " + (newBlock.getHeight() - prevOut.getHeight()));
|
throw new VerificationException("Tried to spend coinbase at depth " + (newBlock.getHeight() - prevOut.getHeight()));
|
||||||
|
valueIn = valueIn.add(prevOut.getValue());
|
||||||
if (enforcePayToScriptHash) {
|
if (enforcePayToScriptHash) {
|
||||||
try {
|
try {
|
||||||
Script script = new Script(params, prevOut.getScriptBytes(), 0, prevOut.getScriptBytes().length);
|
Script script = new Script(params, prevOut.getScriptBytes(), 0, prevOut.getScriptBytes().length);
|
||||||
|
@ -229,13 +258,28 @@ public class FullPrunedBlockChain extends AbstractBlockChain {
|
||||||
}
|
}
|
||||||
Sha256Hash hash = tx.getHash();
|
Sha256Hash hash = tx.getHash();
|
||||||
for (StoredTransactionOutput out : tx.getOutputs()) {
|
for (StoredTransactionOutput out : tx.getOutputs()) {
|
||||||
|
valueOut = valueOut.add(out.getValue());
|
||||||
StoredTransactionOutput newOut = new StoredTransactionOutput(hash, out.getIndex(), out.getValue(),
|
StoredTransactionOutput newOut = new StoredTransactionOutput(hash, out.getIndex(), out.getValue(),
|
||||||
newBlock.getHeight(), isCoinBase,
|
newBlock.getHeight(), isCoinBase,
|
||||||
out.getScriptBytes());
|
out.getScriptBytes());
|
||||||
blockStore.addUnspentTransactionOutput(newOut);
|
blockStore.addUnspentTransactionOutput(newOut);
|
||||||
txOutsCreated.add(newOut);
|
txOutsCreated.add(newOut);
|
||||||
}
|
}
|
||||||
|
// All values were already checked for being non-negative (as it is verified in Transaction.verify())
|
||||||
|
// but we check again here just for defence in depth. Transactions with zero output value are OK.
|
||||||
|
if (valueOut.compareTo(BigInteger.ZERO) < 0 || valueOut.compareTo(params.MAX_MONEY) > 0)
|
||||||
|
throw new VerificationException("Transaction output value out of rage");
|
||||||
|
if (isCoinBase) {
|
||||||
|
coinbaseValue = valueOut;
|
||||||
|
} else {
|
||||||
|
if (valueIn.compareTo(valueOut) < 0 || valueIn.compareTo(params.MAX_MONEY) > 0)
|
||||||
|
throw new VerificationException("Transaction input value out of range");
|
||||||
|
totalFees = totalFees.add(valueIn.subtract(valueOut));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if (totalFees.compareTo(params.MAX_MONEY) > 0 ||
|
||||||
|
getBlockInflation(newBlock.getHeight()).add(totalFees).compareTo(coinbaseValue) < 0)
|
||||||
|
throw new VerificationException("Transaction fees out of range");
|
||||||
txOutChanges = new TransactionOutputChanges(txOutsCreated, txOutsSpent);
|
txOutChanges = new TransactionOutputChanges(txOutsCreated, txOutsSpent);
|
||||||
} else {
|
} else {
|
||||||
// Use the undo data.
|
// Use the undo data.
|
||||||
|
|
Loading…
Add table
Reference in a new issue