mirror of
https://github.com/bitcoinj/bitcoinj.git
synced 2025-03-10 17:26:28 +01:00
Transaction: make verify()
a static helper method
This commit is contained in:
parent
3ad4879e40
commit
cf8715eb4b
7 changed files with 71 additions and 70 deletions
|
@ -684,8 +684,8 @@ public class Block extends Message {
|
||||||
checkTransactions(height, flags);
|
checkTransactions(height, flags);
|
||||||
checkMerkleRoot();
|
checkMerkleRoot();
|
||||||
checkSigOps();
|
checkSigOps();
|
||||||
for (Transaction transaction : transactions)
|
for (Transaction tx : transactions)
|
||||||
transaction.verify();
|
Transaction.verify(params, tx);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -713,7 +713,7 @@ public class Peer extends PeerSocketHandler {
|
||||||
|
|
||||||
protected void processTransaction(final Transaction tx) throws VerificationException {
|
protected void processTransaction(final Transaction tx) throws VerificationException {
|
||||||
// Check a few basic syntax issues to ensure the received TX isn't nonsense.
|
// Check a few basic syntax issues to ensure the received TX isn't nonsense.
|
||||||
tx.verify();
|
tx.verify(params, tx);
|
||||||
lock.lock();
|
lock.lock();
|
||||||
try {
|
try {
|
||||||
if (log.isDebugEnabled())
|
if (log.isDebugEnabled())
|
||||||
|
|
|
@ -1727,59 +1727,6 @@ public class Transaction extends Message {
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>Checks the transaction contents for sanity, in ways that can be done in a standalone manner.
|
|
||||||
* Does <b>not</b> perform all checks on a transaction such as whether the inputs are already spent.
|
|
||||||
* Specifically this method verifies:</p>
|
|
||||||
*
|
|
||||||
* <ul>
|
|
||||||
* <li>That there is at least one input and output.</li>
|
|
||||||
* <li>That the serialized size is not larger than the max block size.</li>
|
|
||||||
* <li>That no outputs have negative value.</li>
|
|
||||||
* <li>That the outputs do not sum to larger than the max allowed quantity of coin in the system.</li>
|
|
||||||
* <li>If the tx is a coinbase tx, the coinbase scriptSig size is within range. Otherwise that there are no
|
|
||||||
* coinbase inputs in the tx.</li>
|
|
||||||
* </ul>
|
|
||||||
*
|
|
||||||
* @throws VerificationException
|
|
||||||
*/
|
|
||||||
public void verify() throws VerificationException {
|
|
||||||
if (inputs.size() == 0 || outputs.size() == 0)
|
|
||||||
throw new VerificationException.EmptyInputsOrOutputs();
|
|
||||||
if (this.getMessageSize() > Block.MAX_BLOCK_SIZE)
|
|
||||||
throw new VerificationException.LargerThanMaxBlockSize();
|
|
||||||
|
|
||||||
HashSet<TransactionOutPoint> outpoints = new HashSet<>();
|
|
||||||
for (TransactionInput input : inputs) {
|
|
||||||
if (outpoints.contains(input.getOutpoint()))
|
|
||||||
throw new VerificationException.DuplicatedOutPoint();
|
|
||||||
outpoints.add(input.getOutpoint());
|
|
||||||
}
|
|
||||||
|
|
||||||
Coin valueOut = Coin.ZERO;
|
|
||||||
for (TransactionOutput output : outputs) {
|
|
||||||
Coin value = output.getValue();
|
|
||||||
if (value.signum() < 0)
|
|
||||||
throw new VerificationException.NegativeValueOutput();
|
|
||||||
try {
|
|
||||||
valueOut = valueOut.add(value);
|
|
||||||
} catch (ArithmeticException e) {
|
|
||||||
throw new VerificationException.ExcessiveValue();
|
|
||||||
}
|
|
||||||
if (params.network().exceedsMaxMoney(valueOut))
|
|
||||||
throw new VerificationException.ExcessiveValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isCoinBase()) {
|
|
||||||
if (inputs.get(0).getScriptBytes().length < 2 || inputs.get(0).getScriptBytes().length > 100)
|
|
||||||
throw new VerificationException.CoinbaseScriptSizeOutOfRange();
|
|
||||||
} else {
|
|
||||||
for (TransactionInput input : inputs)
|
|
||||||
if (input.isCoinBase())
|
|
||||||
throw new VerificationException.UnexpectedCoinbaseInput();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>A transaction is time-locked if at least one of its inputs is non-final and it has a lock time. A transaction can
|
* <p>A transaction is time-locked if at least one of its inputs is non-final and it has a lock time. A transaction can
|
||||||
* also have a relative lock time which this method doesn't tell. Use {@link #hasRelativeLockTime()} to find out.</p>
|
* also have a relative lock time which this method doesn't tell. Use {@link #hasRelativeLockTime()} to find out.</p>
|
||||||
|
@ -1899,4 +1846,59 @@ public class Transaction extends Message {
|
||||||
public void setMemo(String memo) {
|
public void setMemo(String memo) {
|
||||||
this.memo = memo;
|
this.memo = memo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Checks the transaction contents for sanity, in ways that can be done in a standalone manner.
|
||||||
|
* Does <b>not</b> perform all checks on a transaction such as whether the inputs are already spent.
|
||||||
|
* Specifically this method verifies:</p>
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>That there is at least one input and output.</li>
|
||||||
|
* <li>That the serialized size is not larger than the max block size.</li>
|
||||||
|
* <li>That no outputs have negative value.</li>
|
||||||
|
* <li>That the outputs do not sum to larger than the max allowed quantity of coin in the system.</li>
|
||||||
|
* <li>If the tx is a coinbase tx, the coinbase scriptSig size is within range. Otherwise that there are no
|
||||||
|
* coinbase inputs in the tx.</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @param params parameters for the verification rules
|
||||||
|
* @param tx transaction to verify
|
||||||
|
* @throws VerificationException if at least one of the rules is violated
|
||||||
|
*/
|
||||||
|
public static void verify(NetworkParameters params, Transaction tx) throws VerificationException {
|
||||||
|
if (tx.inputs.size() == 0 || tx.outputs.size() == 0)
|
||||||
|
throw new VerificationException.EmptyInputsOrOutputs();
|
||||||
|
if (tx.getMessageSize() > Block.MAX_BLOCK_SIZE)
|
||||||
|
throw new VerificationException.LargerThanMaxBlockSize();
|
||||||
|
|
||||||
|
HashSet<TransactionOutPoint> outpoints = new HashSet<>();
|
||||||
|
for (TransactionInput input : tx.inputs) {
|
||||||
|
if (outpoints.contains(input.getOutpoint()))
|
||||||
|
throw new VerificationException.DuplicatedOutPoint();
|
||||||
|
outpoints.add(input.getOutpoint());
|
||||||
|
}
|
||||||
|
|
||||||
|
Coin valueOut = Coin.ZERO;
|
||||||
|
for (TransactionOutput output : tx.outputs) {
|
||||||
|
Coin value = output.getValue();
|
||||||
|
if (value.signum() < 0)
|
||||||
|
throw new VerificationException.NegativeValueOutput();
|
||||||
|
try {
|
||||||
|
valueOut = valueOut.add(value);
|
||||||
|
} catch (ArithmeticException e) {
|
||||||
|
throw new VerificationException.ExcessiveValue();
|
||||||
|
}
|
||||||
|
if (params.network().exceedsMaxMoney(valueOut))
|
||||||
|
throw new VerificationException.ExcessiveValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tx.isCoinBase()) {
|
||||||
|
if (tx.inputs.get(0).getScriptBytes().length < 2 || tx.inputs.get(0).getScriptBytes().length > 100)
|
||||||
|
throw new VerificationException.CoinbaseScriptSizeOutOfRange();
|
||||||
|
} else {
|
||||||
|
for (TransactionInput input : tx.inputs)
|
||||||
|
if (input.isCoinBase())
|
||||||
|
throw new VerificationException.UnexpectedCoinbaseInput();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -345,7 +345,6 @@ public class PaymentProtocol {
|
||||||
@Nullable List<Protos.Output> refundOutputs, @Nullable String memo, @Nullable byte[] merchantData) {
|
@Nullable List<Protos.Output> refundOutputs, @Nullable String memo, @Nullable byte[] merchantData) {
|
||||||
Protos.Payment.Builder builder = Protos.Payment.newBuilder();
|
Protos.Payment.Builder builder = Protos.Payment.newBuilder();
|
||||||
for (Transaction transaction : transactions) {
|
for (Transaction transaction : transactions) {
|
||||||
transaction.verify();
|
|
||||||
builder.addTransactions(ByteString.copyFrom(transaction.bitcoinSerialize()));
|
builder.addTransactions(ByteString.copyFrom(transaction.bitcoinSerialize()));
|
||||||
}
|
}
|
||||||
if (refundOutputs != null) {
|
if (refundOutputs != null) {
|
||||||
|
|
|
@ -2002,7 +2002,7 @@ public class Wallet extends BaseTaggableObject
|
||||||
// spend against one of our other pending transactions.
|
// spend against one of our other pending transactions.
|
||||||
lock.lock();
|
lock.lock();
|
||||||
try {
|
try {
|
||||||
tx.verify();
|
Transaction.verify(params, tx);
|
||||||
// Ignore it if we already know about this transaction. Receiving a pending transaction never moves it
|
// Ignore it if we already know about this transaction. Receiving a pending transaction never moves it
|
||||||
// between pools.
|
// between pools.
|
||||||
EnumSet<Pool> containingPools = getContainingPools(tx);
|
EnumSet<Pool> containingPools = getContainingPools(tx);
|
||||||
|
@ -2782,7 +2782,7 @@ public class Wallet extends BaseTaggableObject
|
||||||
* @throws VerificationException If transaction fails to verify
|
* @throws VerificationException If transaction fails to verify
|
||||||
*/
|
*/
|
||||||
public boolean maybeCommitTx(Transaction tx) throws VerificationException {
|
public boolean maybeCommitTx(Transaction tx) throws VerificationException {
|
||||||
tx.verify();
|
Transaction.verify(params, tx);
|
||||||
lock.lock();
|
lock.lock();
|
||||||
try {
|
try {
|
||||||
if (pending.containsKey(tx.getTxId()))
|
if (pending.containsKey(tx.getTxId()))
|
||||||
|
|
|
@ -87,21 +87,21 @@ public class TransactionTest {
|
||||||
public void emptyOutputs() {
|
public void emptyOutputs() {
|
||||||
Transaction tx = FakeTxBuilder.createFakeTx(TESTNET);
|
Transaction tx = FakeTxBuilder.createFakeTx(TESTNET);
|
||||||
tx.clearOutputs();
|
tx.clearOutputs();
|
||||||
tx.verify();
|
Transaction.verify(TESTNET, tx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = VerificationException.EmptyInputsOrOutputs.class)
|
@Test(expected = VerificationException.EmptyInputsOrOutputs.class)
|
||||||
public void emptyInputs() {
|
public void emptyInputs() {
|
||||||
Transaction tx = FakeTxBuilder.createFakeTx(TESTNET);
|
Transaction tx = FakeTxBuilder.createFakeTx(TESTNET);
|
||||||
tx.clearInputs();
|
tx.clearInputs();
|
||||||
tx.verify();
|
Transaction.verify(TESTNET, tx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = VerificationException.LargerThanMaxBlockSize.class)
|
@Test(expected = VerificationException.LargerThanMaxBlockSize.class)
|
||||||
public void tooHuge() {
|
public void tooHuge() {
|
||||||
Transaction tx = FakeTxBuilder.createFakeTx(TESTNET);
|
Transaction tx = FakeTxBuilder.createFakeTx(TESTNET);
|
||||||
tx.getInput(0).setScriptBytes(new byte[Block.MAX_BLOCK_SIZE]);
|
tx.getInput(0).setScriptBytes(new byte[Block.MAX_BLOCK_SIZE]);
|
||||||
tx.verify();
|
Transaction.verify(TESTNET, tx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = VerificationException.DuplicatedOutPoint.class)
|
@Test(expected = VerificationException.DuplicatedOutPoint.class)
|
||||||
|
@ -110,14 +110,14 @@ public class TransactionTest {
|
||||||
TransactionInput input = tx.getInput(0);
|
TransactionInput input = tx.getInput(0);
|
||||||
input.setScriptBytes(new byte[1]);
|
input.setScriptBytes(new byte[1]);
|
||||||
tx.addInput(input);
|
tx.addInput(input);
|
||||||
tx.verify();
|
Transaction.verify(TESTNET, tx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = VerificationException.NegativeValueOutput.class)
|
@Test(expected = VerificationException.NegativeValueOutput.class)
|
||||||
public void negativeOutput() {
|
public void negativeOutput() {
|
||||||
Transaction tx = FakeTxBuilder.createFakeTx(TESTNET);
|
Transaction tx = FakeTxBuilder.createFakeTx(TESTNET);
|
||||||
tx.getOutput(0).setValue(Coin.NEGATIVE_SATOSHI);
|
tx.getOutput(0).setValue(Coin.NEGATIVE_SATOSHI);
|
||||||
tx.verify();
|
Transaction.verify(TESTNET, tx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = VerificationException.ExcessiveValue.class)
|
@Test(expected = VerificationException.ExcessiveValue.class)
|
||||||
|
@ -126,14 +126,14 @@ public class TransactionTest {
|
||||||
Coin half = BitcoinNetwork.MAX_MONEY.divide(2).add(Coin.SATOSHI);
|
Coin half = BitcoinNetwork.MAX_MONEY.divide(2).add(Coin.SATOSHI);
|
||||||
tx.getOutput(0).setValue(half);
|
tx.getOutput(0).setValue(half);
|
||||||
tx.addOutput(half, ADDRESS);
|
tx.addOutput(half, ADDRESS);
|
||||||
tx.verify();
|
Transaction.verify(TESTNET, tx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = VerificationException.UnexpectedCoinbaseInput.class)
|
@Test(expected = VerificationException.UnexpectedCoinbaseInput.class)
|
||||||
public void coinbaseInputInNonCoinbaseTX() {
|
public void coinbaseInputInNonCoinbaseTX() {
|
||||||
Transaction tx = FakeTxBuilder.createFakeTx(TESTNET);
|
Transaction tx = FakeTxBuilder.createFakeTx(TESTNET);
|
||||||
tx.addInput(Sha256Hash.ZERO_HASH, 0xFFFFFFFFL, new ScriptBuilder().data(new byte[10]).build());
|
tx.addInput(Sha256Hash.ZERO_HASH, 0xFFFFFFFFL, new ScriptBuilder().data(new byte[10]).build());
|
||||||
tx.verify();
|
Transaction.verify(TESTNET, tx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = VerificationException.CoinbaseScriptSizeOutOfRange.class)
|
@Test(expected = VerificationException.CoinbaseScriptSizeOutOfRange.class)
|
||||||
|
@ -141,7 +141,7 @@ public class TransactionTest {
|
||||||
Transaction tx = FakeTxBuilder.createFakeTx(TESTNET);
|
Transaction tx = FakeTxBuilder.createFakeTx(TESTNET);
|
||||||
tx.clearInputs();
|
tx.clearInputs();
|
||||||
tx.addInput(Sha256Hash.ZERO_HASH, 0xFFFFFFFFL, new ScriptBuilder().build());
|
tx.addInput(Sha256Hash.ZERO_HASH, 0xFFFFFFFFL, new ScriptBuilder().build());
|
||||||
tx.verify();
|
Transaction.verify(TESTNET, tx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = VerificationException.CoinbaseScriptSizeOutOfRange.class)
|
@Test(expected = VerificationException.CoinbaseScriptSizeOutOfRange.class)
|
||||||
|
@ -150,7 +150,7 @@ public class TransactionTest {
|
||||||
tx.clearInputs();
|
tx.clearInputs();
|
||||||
TransactionInput input = tx.addInput(Sha256Hash.ZERO_HASH, 0xFFFFFFFFL, new ScriptBuilder().data(new byte[99]).build());
|
TransactionInput input = tx.addInput(Sha256Hash.ZERO_HASH, 0xFFFFFFFFL, new ScriptBuilder().data(new byte[99]).build());
|
||||||
assertEquals(101, input.getScriptBytes().length);
|
assertEquals(101, input.getScriptBytes().length);
|
||||||
tx.verify();
|
Transaction.verify(TESTNET, tx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -392,7 +392,7 @@ public class ScriptTest {
|
||||||
try {
|
try {
|
||||||
Map<TransactionOutPoint, Script> scriptPubKeys = parseScriptPubKeys(test.get(0));
|
Map<TransactionOutPoint, Script> scriptPubKeys = parseScriptPubKeys(test.get(0));
|
||||||
transaction = TESTNET.getDefaultSerializer().makeTransaction(ByteBuffer.wrap(ByteUtils.parseHex(test.get(1).asText().toLowerCase())));
|
transaction = TESTNET.getDefaultSerializer().makeTransaction(ByteBuffer.wrap(ByteUtils.parseHex(test.get(1).asText().toLowerCase())));
|
||||||
transaction.verify();
|
Transaction.verify(TESTNET, transaction);
|
||||||
Set<VerifyFlag> verifyFlags = parseVerifyFlags(test.get(2).asText());
|
Set<VerifyFlag> verifyFlags = parseVerifyFlags(test.get(2).asText());
|
||||||
|
|
||||||
for (int i = 0; i < transaction.getInputs().size(); i++) {
|
for (int i = 0; i < transaction.getInputs().size(); i++) {
|
||||||
|
@ -433,7 +433,7 @@ public class ScriptTest {
|
||||||
|
|
||||||
boolean valid = true;
|
boolean valid = true;
|
||||||
try {
|
try {
|
||||||
transaction.verify();
|
Transaction.verify(TESTNET, transaction);
|
||||||
} catch (VerificationException e) {
|
} catch (VerificationException e) {
|
||||||
valid = false;
|
valid = false;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue