diff --git a/core/src/main/java/org/bitcoinj/core/Block.java b/core/src/main/java/org/bitcoinj/core/Block.java index be1719acf..622234339 100644 --- a/core/src/main/java/org/bitcoinj/core/Block.java +++ b/core/src/main/java/org/bitcoinj/core/Block.java @@ -684,8 +684,8 @@ public class Block extends Message { checkTransactions(height, flags); checkMerkleRoot(); checkSigOps(); - for (Transaction transaction : transactions) - transaction.verify(); + for (Transaction tx : transactions) + Transaction.verify(params, tx); } /** diff --git a/core/src/main/java/org/bitcoinj/core/Peer.java b/core/src/main/java/org/bitcoinj/core/Peer.java index de516bccf..8eb954334 100644 --- a/core/src/main/java/org/bitcoinj/core/Peer.java +++ b/core/src/main/java/org/bitcoinj/core/Peer.java @@ -713,7 +713,7 @@ public class Peer extends PeerSocketHandler { protected void processTransaction(final Transaction tx) throws VerificationException { // Check a few basic syntax issues to ensure the received TX isn't nonsense. - tx.verify(); + tx.verify(params, tx); lock.lock(); try { if (log.isDebugEnabled()) diff --git a/core/src/main/java/org/bitcoinj/core/Transaction.java b/core/src/main/java/org/bitcoinj/core/Transaction.java index 9c3c08de1..65d3ff0cb 100644 --- a/core/src/main/java/org/bitcoinj/core/Transaction.java +++ b/core/src/main/java/org/bitcoinj/core/Transaction.java @@ -1727,59 +1727,6 @@ public class Transaction extends Message { .orElse(null); } - /** - *

Checks the transaction contents for sanity, in ways that can be done in a standalone manner. - * Does not perform all checks on a transaction such as whether the inputs are already spent. - * Specifically this method verifies:

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

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.

@@ -1899,4 +1846,59 @@ public class Transaction extends Message { public void setMemo(String memo) { this.memo = memo; } + + /** + *

Checks the transaction contents for sanity, in ways that can be done in a standalone manner. + * Does not perform all checks on a transaction such as whether the inputs are already spent. + * Specifically this method verifies:

+ * + * + * + * @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 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(); + } + } } diff --git a/core/src/main/java/org/bitcoinj/protocols/payments/PaymentProtocol.java b/core/src/main/java/org/bitcoinj/protocols/payments/PaymentProtocol.java index b0a96659f..876789503 100644 --- a/core/src/main/java/org/bitcoinj/protocols/payments/PaymentProtocol.java +++ b/core/src/main/java/org/bitcoinj/protocols/payments/PaymentProtocol.java @@ -345,7 +345,6 @@ public class PaymentProtocol { @Nullable List refundOutputs, @Nullable String memo, @Nullable byte[] merchantData) { Protos.Payment.Builder builder = Protos.Payment.newBuilder(); for (Transaction transaction : transactions) { - transaction.verify(); builder.addTransactions(ByteString.copyFrom(transaction.bitcoinSerialize())); } if (refundOutputs != null) { diff --git a/core/src/main/java/org/bitcoinj/wallet/Wallet.java b/core/src/main/java/org/bitcoinj/wallet/Wallet.java index cba23ab89..82a3ece23 100644 --- a/core/src/main/java/org/bitcoinj/wallet/Wallet.java +++ b/core/src/main/java/org/bitcoinj/wallet/Wallet.java @@ -2002,7 +2002,7 @@ public class Wallet extends BaseTaggableObject // spend against one of our other pending transactions. lock.lock(); try { - tx.verify(); + Transaction.verify(params, tx); // Ignore it if we already know about this transaction. Receiving a pending transaction never moves it // between pools. EnumSet containingPools = getContainingPools(tx); @@ -2782,7 +2782,7 @@ public class Wallet extends BaseTaggableObject * @throws VerificationException If transaction fails to verify */ public boolean maybeCommitTx(Transaction tx) throws VerificationException { - tx.verify(); + Transaction.verify(params, tx); lock.lock(); try { if (pending.containsKey(tx.getTxId())) diff --git a/core/src/test/java/org/bitcoinj/core/TransactionTest.java b/core/src/test/java/org/bitcoinj/core/TransactionTest.java index 58717fa72..09b65713a 100644 --- a/core/src/test/java/org/bitcoinj/core/TransactionTest.java +++ b/core/src/test/java/org/bitcoinj/core/TransactionTest.java @@ -87,21 +87,21 @@ public class TransactionTest { public void emptyOutputs() { Transaction tx = FakeTxBuilder.createFakeTx(TESTNET); tx.clearOutputs(); - tx.verify(); + Transaction.verify(TESTNET, tx); } @Test(expected = VerificationException.EmptyInputsOrOutputs.class) public void emptyInputs() { Transaction tx = FakeTxBuilder.createFakeTx(TESTNET); tx.clearInputs(); - tx.verify(); + Transaction.verify(TESTNET, tx); } @Test(expected = VerificationException.LargerThanMaxBlockSize.class) public void tooHuge() { Transaction tx = FakeTxBuilder.createFakeTx(TESTNET); tx.getInput(0).setScriptBytes(new byte[Block.MAX_BLOCK_SIZE]); - tx.verify(); + Transaction.verify(TESTNET, tx); } @Test(expected = VerificationException.DuplicatedOutPoint.class) @@ -110,14 +110,14 @@ public class TransactionTest { TransactionInput input = tx.getInput(0); input.setScriptBytes(new byte[1]); tx.addInput(input); - tx.verify(); + Transaction.verify(TESTNET, tx); } @Test(expected = VerificationException.NegativeValueOutput.class) public void negativeOutput() { Transaction tx = FakeTxBuilder.createFakeTx(TESTNET); tx.getOutput(0).setValue(Coin.NEGATIVE_SATOSHI); - tx.verify(); + Transaction.verify(TESTNET, tx); } @Test(expected = VerificationException.ExcessiveValue.class) @@ -126,14 +126,14 @@ public class TransactionTest { Coin half = BitcoinNetwork.MAX_MONEY.divide(2).add(Coin.SATOSHI); tx.getOutput(0).setValue(half); tx.addOutput(half, ADDRESS); - tx.verify(); + Transaction.verify(TESTNET, tx); } @Test(expected = VerificationException.UnexpectedCoinbaseInput.class) public void coinbaseInputInNonCoinbaseTX() { Transaction tx = FakeTxBuilder.createFakeTx(TESTNET); tx.addInput(Sha256Hash.ZERO_HASH, 0xFFFFFFFFL, new ScriptBuilder().data(new byte[10]).build()); - tx.verify(); + Transaction.verify(TESTNET, tx); } @Test(expected = VerificationException.CoinbaseScriptSizeOutOfRange.class) @@ -141,7 +141,7 @@ public class TransactionTest { Transaction tx = FakeTxBuilder.createFakeTx(TESTNET); tx.clearInputs(); tx.addInput(Sha256Hash.ZERO_HASH, 0xFFFFFFFFL, new ScriptBuilder().build()); - tx.verify(); + Transaction.verify(TESTNET, tx); } @Test(expected = VerificationException.CoinbaseScriptSizeOutOfRange.class) @@ -150,7 +150,7 @@ public class TransactionTest { tx.clearInputs(); TransactionInput input = tx.addInput(Sha256Hash.ZERO_HASH, 0xFFFFFFFFL, new ScriptBuilder().data(new byte[99]).build()); assertEquals(101, input.getScriptBytes().length); - tx.verify(); + Transaction.verify(TESTNET, tx); } @Test diff --git a/core/src/test/java/org/bitcoinj/script/ScriptTest.java b/core/src/test/java/org/bitcoinj/script/ScriptTest.java index cf2d8726e..56582b043 100644 --- a/core/src/test/java/org/bitcoinj/script/ScriptTest.java +++ b/core/src/test/java/org/bitcoinj/script/ScriptTest.java @@ -392,7 +392,7 @@ public class ScriptTest { try { Map scriptPubKeys = parseScriptPubKeys(test.get(0)); transaction = TESTNET.getDefaultSerializer().makeTransaction(ByteBuffer.wrap(ByteUtils.parseHex(test.get(1).asText().toLowerCase()))); - transaction.verify(); + Transaction.verify(TESTNET, transaction); Set verifyFlags = parseVerifyFlags(test.get(2).asText()); for (int i = 0; i < transaction.getInputs().size(); i++) { @@ -433,7 +433,7 @@ public class ScriptTest { boolean valid = true; try { - transaction.verify(); + Transaction.verify(TESTNET, transaction); } catch (VerificationException e) { valid = false; }