diff --git a/core/src/main/java/com/google/bitcoin/wallet/DefaultRiskAnalysis.java b/core/src/main/java/com/google/bitcoin/wallet/DefaultRiskAnalysis.java index 01a812d4..74658b44 100644 --- a/core/src/main/java/com/google/bitcoin/wallet/DefaultRiskAnalysis.java +++ b/core/src/main/java/com/google/bitcoin/wallet/DefaultRiskAnalysis.java @@ -1,5 +1,6 @@ /* * Copyright 2013 Google Inc. + * Copyright 2014 Andreas Schildbach * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,11 +17,14 @@ package com.google.bitcoin.wallet; +import com.google.bitcoin.core.NetworkParameters; import com.google.bitcoin.core.Transaction; import com.google.bitcoin.core.TransactionConfidence; +import com.google.bitcoin.core.TransactionOutput; import com.google.bitcoin.core.Wallet; import javax.annotation.Nullable; + import java.util.List; import static com.google.common.base.Preconditions.checkState; @@ -34,6 +38,7 @@ public class DefaultRiskAnalysis implements RiskAnalysis { protected final List dependencies; protected final Wallet wallet; + private Transaction nonStandard; protected Transaction nonFinal; protected boolean analyzed; @@ -48,6 +53,14 @@ public class DefaultRiskAnalysis implements RiskAnalysis { checkState(!analyzed); analyzed = true; + Result result = analyzeIsFinal(); + if (result != Result.OK) + return result; + + return analyzeIsStandard(); + } + + private Result analyzeIsFinal() { // Transactions we create ourselves are, by definition, not at risk of double spending against us. if (tx.getConfidence().getSource() == TransactionConfidence.Source.SELF) return Result.OK; @@ -71,6 +84,49 @@ public class DefaultRiskAnalysis implements RiskAnalysis { return Result.OK; } + private Result analyzeIsStandard() { + if (!wallet.getNetworkParameters().getId().equals(NetworkParameters.ID_MAINNET)) + return Result.OK; + + nonStandard = isStandard(tx); + if (nonStandard != null) + return Result.NON_STANDARD; + + for (Transaction dep : dependencies) { + nonStandard = isStandard(dep); + if (nonStandard != null) + return Result.NON_STANDARD; + } + + return Result.OK; + } + + /** + *

Checks if a transaction is considered "standard" by the reference client's IsStandardTx and AreInputsStandard + * functions.

+ * + *

Note that this method currently only implements a minimum of checks. More to be added later.

+ * + * @return Either null if the transaction is standard, or the first transaction found which is considered nonstandard + */ + public Transaction isStandard(Transaction tx) { + if (tx.getVersion() > 1 || tx.getVersion() < 1) + return tx; + + for (TransactionOutput output : tx.getOutputs()) { + if (output.getMinNonDustValue().compareTo(output.getValue()) > 0) + return tx; + } + + return null; + } + + /** Returns the transaction that was found to be non-standard, or null. */ + @Nullable + public Transaction getNonStandard() { + return nonStandard; + } + /** Returns the transaction that was found to be non-final, or null. */ @Nullable public Transaction getNonFinal() { @@ -83,6 +139,8 @@ public class DefaultRiskAnalysis implements RiskAnalysis { return "Pending risk analysis for " + tx.getHashAsString(); else if (nonFinal != null) return "Risky due to non-finality of " + nonFinal.getHashAsString(); + else if (nonStandard != null) + return "Risky due to non-standard tx " + nonStandard.getHashAsString(); else return "Non-risky"; } diff --git a/core/src/main/java/com/google/bitcoin/wallet/RiskAnalysis.java b/core/src/main/java/com/google/bitcoin/wallet/RiskAnalysis.java index e0f06d4a..193a9bc8 100644 --- a/core/src/main/java/com/google/bitcoin/wallet/RiskAnalysis.java +++ b/core/src/main/java/com/google/bitcoin/wallet/RiskAnalysis.java @@ -34,7 +34,8 @@ import java.util.List; public interface RiskAnalysis { public enum Result { OK, - NON_FINAL + NON_FINAL, + NON_STANDARD } public Result analyze(); diff --git a/core/src/test/java/com/google/bitcoin/wallet/DefaultRiskAnalysisTest.java b/core/src/test/java/com/google/bitcoin/wallet/DefaultRiskAnalysisTest.java index 53c39043..95731ab6 100644 --- a/core/src/test/java/com/google/bitcoin/wallet/DefaultRiskAnalysisTest.java +++ b/core/src/test/java/com/google/bitcoin/wallet/DefaultRiskAnalysisTest.java @@ -1,5 +1,6 @@ /* * Copyright 2013 Google Inc. + * Copyright 2014 Andreas Schildbach * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +17,10 @@ package com.google.bitcoin.wallet; +import java.math.BigInteger; + import com.google.bitcoin.core.*; -import com.google.bitcoin.params.UnitTestParams; +import com.google.bitcoin.params.MainNetParams; import com.google.common.collect.ImmutableList; import org.junit.Before; import org.junit.Test; @@ -27,7 +30,8 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.fail; public class DefaultRiskAnalysisTest { - private static final NetworkParameters params = UnitTestParams.get(); + // Uses mainnet because isStandard checks are disabled on testnet. + private static final NetworkParameters params = MainNetParams.get(); private Wallet wallet; private final int TIMESTAMP = 1384190189; private ECKey key1; @@ -119,4 +123,22 @@ public class DefaultRiskAnalysisTest { assertEquals(RiskAnalysis.Result.NON_FINAL, analysis.analyze()); assertEquals(tx1, analysis.getNonFinal()); } + + @Test + public void nonStandardDust() { + Transaction standardTx = new Transaction(params); + standardTx.addInput(params.getGenesisBlock().getTransactions().get(0).getOutput(0)); + standardTx.addOutput(Utils.COIN, key1); + assertEquals(RiskAnalysis.Result.OK, DefaultRiskAnalysis.FACTORY.create(wallet, standardTx, NO_DEPS).analyze()); + + Transaction dustTx = new Transaction(params); + dustTx.addInput(params.getGenesisBlock().getTransactions().get(0).getOutput(0)); + dustTx.addOutput(BigInteger.ONE, key1); // 1 Satoshi + assertEquals(RiskAnalysis.Result.NON_STANDARD, DefaultRiskAnalysis.FACTORY.create(wallet, dustTx, NO_DEPS).analyze()); + + Transaction edgeCaseTx = new Transaction(params); + edgeCaseTx.addInput(params.getGenesisBlock().getTransactions().get(0).getOutput(0)); + edgeCaseTx.addOutput(dustTx.getOutput(0).getMinNonDustValue(), key1); // Dust threshold + assertEquals(RiskAnalysis.Result.OK, DefaultRiskAnalysis.FACTORY.create(wallet, edgeCaseTx, NO_DEPS).analyze()); + } }