mirror of
https://github.com/bitcoinj/bitcoinj.git
synced 2025-02-25 07:07:39 +01:00
DefaultRiskAnalysis: Consider transactions that opt into replace-by-fee at risk for double spending.
This commit is contained in:
parent
786a11187e
commit
54780491fc
2 changed files with 36 additions and 21 deletions
|
@ -84,6 +84,12 @@ public class DefaultRiskAnalysis implements RiskAnalysis {
|
||||||
if (tx.getConfidence().getSource() == TransactionConfidence.Source.SELF)
|
if (tx.getConfidence().getSource() == TransactionConfidence.Source.SELF)
|
||||||
return Result.OK;
|
return Result.OK;
|
||||||
|
|
||||||
|
// We consider transactions that opt into replace-by-fee at risk of double spending.
|
||||||
|
if (tx.isOptInFullRBF()) {
|
||||||
|
nonFinal = tx;
|
||||||
|
return Result.NON_FINAL;
|
||||||
|
}
|
||||||
|
|
||||||
if (wallet == null)
|
if (wallet == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
@ -103,6 +109,7 @@ public class DefaultRiskAnalysis implements RiskAnalysis {
|
||||||
return Result.NON_FINAL;
|
return Result.NON_FINAL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result.OK;
|
return Result.OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@ import org.bitcoinj.core.*;
|
||||||
import org.bitcoinj.crypto.*;
|
import org.bitcoinj.crypto.*;
|
||||||
import org.bitcoinj.params.*;
|
import org.bitcoinj.params.*;
|
||||||
import org.bitcoinj.script.*;
|
import org.bitcoinj.script.*;
|
||||||
|
import org.bitcoinj.testing.FakeTxBuilder;
|
||||||
import org.bitcoinj.wallet.DefaultRiskAnalysis.*;
|
import org.bitcoinj.wallet.DefaultRiskAnalysis.*;
|
||||||
import org.junit.*;
|
import org.junit.*;
|
||||||
|
|
||||||
|
@ -54,6 +55,16 @@ public class DefaultRiskAnalysisTest {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalStateException.class)
|
||||||
|
public void analysisCantBeUsedTwice() {
|
||||||
|
Transaction tx = new Transaction(params);
|
||||||
|
DefaultRiskAnalysis analysis = DefaultRiskAnalysis.FACTORY.create(wallet, tx, NO_DEPS);
|
||||||
|
assertEquals(RiskAnalysis.Result.OK, analysis.analyze());
|
||||||
|
assertNull(analysis.getNonFinal());
|
||||||
|
// Verify we can't re-use a used up risk analysis.
|
||||||
|
analysis.analyze();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void nonFinal() throws Exception {
|
public void nonFinal() throws Exception {
|
||||||
// Verify that just having a lock time in the future is not enough to be considered risky (it's still final).
|
// Verify that just having a lock time in the future is not enough to be considered risky (it's still final).
|
||||||
|
@ -61,32 +72,20 @@ public class DefaultRiskAnalysisTest {
|
||||||
TransactionInput input = tx.addInput(params.getGenesisBlock().getTransactions().get(0).getOutput(0));
|
TransactionInput input = tx.addInput(params.getGenesisBlock().getTransactions().get(0).getOutput(0));
|
||||||
tx.addOutput(COIN, key1);
|
tx.addOutput(COIN, key1);
|
||||||
tx.setLockTime(TIMESTAMP + 86400);
|
tx.setLockTime(TIMESTAMP + 86400);
|
||||||
|
DefaultRiskAnalysis analysis = DefaultRiskAnalysis.FACTORY.create(wallet, tx, NO_DEPS);
|
||||||
{
|
assertEquals(RiskAnalysis.Result.OK, analysis.analyze());
|
||||||
DefaultRiskAnalysis analysis = DefaultRiskAnalysis.FACTORY.create(wallet, tx, NO_DEPS);
|
assertNull(analysis.getNonFinal());
|
||||||
assertEquals(RiskAnalysis.Result.OK, analysis.analyze());
|
|
||||||
assertNull(analysis.getNonFinal());
|
|
||||||
// Verify we can't re-use a used up risk analysis.
|
|
||||||
try {
|
|
||||||
analysis.analyze();
|
|
||||||
fail();
|
|
||||||
} catch (IllegalStateException e) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set a sequence number on the input to make it genuinely non-final. Verify it's risky.
|
// Set a sequence number on the input to make it genuinely non-final. Verify it's risky.
|
||||||
input.setSequenceNumber(1);
|
input.setSequenceNumber(TransactionInput.NO_SEQUENCE - 1);
|
||||||
{
|
analysis = DefaultRiskAnalysis.FACTORY.create(wallet, tx, NO_DEPS);
|
||||||
DefaultRiskAnalysis analysis = DefaultRiskAnalysis.FACTORY.create(wallet, tx, NO_DEPS);
|
assertEquals(RiskAnalysis.Result.NON_FINAL, analysis.analyze());
|
||||||
assertEquals(RiskAnalysis.Result.NON_FINAL, analysis.analyze());
|
assertEquals(tx, analysis.getNonFinal());
|
||||||
assertEquals(tx, analysis.getNonFinal());
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the lock time is the current block, it's about to become final and we consider it non-risky.
|
// If the lock time is the current block, it's about to become final and we consider it non-risky.
|
||||||
tx.setLockTime(1000);
|
tx.setLockTime(1000);
|
||||||
{
|
analysis = DefaultRiskAnalysis.FACTORY.create(wallet, tx, NO_DEPS);
|
||||||
DefaultRiskAnalysis analysis = DefaultRiskAnalysis.FACTORY.create(wallet, tx, NO_DEPS);
|
assertEquals(RiskAnalysis.Result.OK, analysis.analyze());
|
||||||
assertEquals(RiskAnalysis.Result.OK, analysis.analyze());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -223,4 +222,13 @@ public class DefaultRiskAnalysisTest {
|
||||||
tx.addOutput(Coin.CENT, ScriptBuilder.createOpReturnScript("hi there".getBytes()));
|
tx.addOutput(Coin.CENT, ScriptBuilder.createOpReturnScript("hi there".getBytes()));
|
||||||
assertEquals(RiskAnalysis.Result.OK, DefaultRiskAnalysis.FACTORY.create(wallet, tx, NO_DEPS).analyze());
|
assertEquals(RiskAnalysis.Result.OK, DefaultRiskAnalysis.FACTORY.create(wallet, tx, NO_DEPS).analyze());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void optInFullRBF() throws Exception {
|
||||||
|
Transaction tx = FakeTxBuilder.createFakeTx(params);
|
||||||
|
tx.getInput(0).setSequenceNumber(TransactionInput.NO_SEQUENCE - 2);
|
||||||
|
DefaultRiskAnalysis analysis = DefaultRiskAnalysis.FACTORY.create(wallet, tx, NO_DEPS);
|
||||||
|
assertEquals(RiskAnalysis.Result.NON_FINAL, analysis.analyze());
|
||||||
|
assertEquals(tx, analysis.getNonFinal());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue