/** * <p>Returns either RuleViolation.NONE if the input is standard, or which rule makes it non-standard if so. * The "IsStandard" rules control whether the default Bitcoin Core client blocks relay of a tx / refuses to mine it, * however, non-standard transactions can still be included in blocks and will be accepted as valid if so.</p> * * <p>This method simply calls <tt>DefaultRiskAnalysis.isInputStandard(this)</tt>.</p> */ public DefaultRiskAnalysis.RuleViolation isStandard() { return DefaultRiskAnalysis.isInputStandard(this); }
@Override public DefaultRiskAnalysis create(Wallet wallet, Transaction tx, List<Transaction> dependencies) { return new DefaultRiskAnalysis(wallet, tx, dependencies); } }
@Override public Result analyze() { checkState(!analyzed); analyzed = true; Result result = analyzeIsFinal(); if (result != null && result != Result.OK) return result; return analyzeIsStandard(); }
for (int i = 0; i < outputs.size(); i++) { TransactionOutput output = outputs.get(i); RuleViolation violation = isOutputStandard(output); if (violation != RuleViolation.NONE) { log.warn("TX considered non-standard due to output {} violating rule {}", i, violation); for (int i = 0; i < inputs.size(); i++) { TransactionInput input = inputs.get(i); RuleViolation violation = isInputStandard(input); if (violation != RuleViolation.NONE) { log.warn("TX considered non-standard due to input {} violating rule {}", i, violation);
private Result analyzeIsStandard() { // The IsStandard rules don't apply on testnet, because they're just a safety mechanism and we don't want to // crush innovation with valueless test coins. if (wallet != null && !wallet.getNetworkParameters().getId().equals(NetworkParameters.ID_MAINNET)) return Result.OK; RuleViolation ruleViolation = isStandard(tx); if (ruleViolation != RuleViolation.NONE) { nonStandard = tx; return Result.NON_STANDARD; } for (Transaction dep : dependencies) { ruleViolation = isStandard(dep); if (ruleViolation != RuleViolation.NONE) { nonStandard = dep; return Result.NON_STANDARD; } } return Result.OK; }
@Test public void canonicalSignatureLowS() { // First, a synthetic test. TransactionSignature sig = TransactionSignature.dummy(); Script scriptHighS = ScriptBuilder .createInputScript(new TransactionSignature(sig.r, ECKey.CURVE.getN().subtract(sig.s))); assertEquals(RuleViolation.SIGNATURE_CANONICAL_ENCODING, DefaultRiskAnalysis.isInputStandard(new TransactionInput(PARAMS, null, scriptHighS.getProgram()))); // This is a real transaction. Its signatures S component is "low". Transaction tx1 = new Transaction(PARAMS, Utils.HEX.decode( "010000000200a2be4376b7f47250ad9ad3a83b6aa5eb6a6d139a1f50771704d77aeb8ce76c010000006a4730440220055723d363cd2d4fe4e887270ebdf5c4b99eaf233a5c09f9404f888ec8b839350220763c3794d310b384ce86decfb05787e5bfa5d31983db612a2dde5ffec7f396ae012102ef47e27e0c4bdd6dc83915f185d972d5eb8515c34d17bad584a9312e59f4e0bcffffffff52239451d37757eeacb86d32864ec1ee6b6e131d1e3fee6f1cff512703b71014030000006b483045022100ea266ac4f893d98a623a6fc0e6a961cd5a3f32696721e87e7570a68851917e75022056d75c3b767419f6f6cb8189a0ad78d45971523908dc4892f7594b75fd43a8d00121038bb455ca101ebbb0ecf7f5c01fa1dcb7d14fbf6b7d7ea52ee56f0148e72a736cffffffff0630b15a00000000001976a9146ae477b690cf85f21c2c01e2c8639a5c18dc884e88ac4f260d00000000001976a91498d08c02ab92a671590adb726dddb719695ee12e88ac65753b00000000001976a9140b2eb4ba6d364c82092f25775f56bc10cd92c8f188ac65753b00000000001976a914d1cb414e22081c6ba3a935635c0f1d837d3c5d9188ac65753b00000000001976a914df9d137a0d279471a2796291874c29759071340b88ac3d753b00000000001976a91459f5aa4815e3aa8e1720e8b82f4ac8e6e904e47d88ac00000000")); assertEquals("2a1c8569b2b01ebac647fb94444d1118d4d00e327456a3c518e40d47d72cd5fe", tx1.getHashAsString()); assertEquals(RuleViolation.NONE, DefaultRiskAnalysis.isStandard(tx1)); // This tx is the same as the above, except for a "high" S component on the signature of input 1. // It was part of the Oct 2015 malleability attack. Transaction tx2 = new Transaction(PARAMS, Utils.HEX.decode( "010000000200a2be4376b7f47250ad9ad3a83b6aa5eb6a6d139a1f50771704d77aeb8ce76c010000006a4730440220055723d363cd2d4fe4e887270ebdf5c4b99eaf233a5c09f9404f888ec8b839350220763c3794d310b384ce86decfb05787e5bfa5d31983db612a2dde5ffec7f396ae012102ef47e27e0c4bdd6dc83915f185d972d5eb8515c34d17bad584a9312e59f4e0bcffffffff52239451d37757eeacb86d32864ec1ee6b6e131d1e3fee6f1cff512703b71014030000006c493046022100ea266ac4f893d98a623a6fc0e6a961cd5a3f32696721e87e7570a68851917e75022100a928a3c4898be60909347e765f52872a613d8aada66c57a8c8791316d2f298710121038bb455ca101ebbb0ecf7f5c01fa1dcb7d14fbf6b7d7ea52ee56f0148e72a736cffffffff0630b15a00000000001976a9146ae477b690cf85f21c2c01e2c8639a5c18dc884e88ac4f260d00000000001976a91498d08c02ab92a671590adb726dddb719695ee12e88ac65753b00000000001976a9140b2eb4ba6d364c82092f25775f56bc10cd92c8f188ac65753b00000000001976a914d1cb414e22081c6ba3a935635c0f1d837d3c5d9188ac65753b00000000001976a914df9d137a0d279471a2796291874c29759071340b88ac3d753b00000000001976a91459f5aa4815e3aa8e1720e8b82f4ac8e6e904e47d88ac00000000")); assertEquals("dbe4147cf89b89fd9fa6c8ce6a3e2adecb234db094ec88301ae09073ca17d61d", tx2.getHashAsString()); assertFalse(ECKey.ECDSASignature .decodeFromDER(new Script(tx2.getInputs().get(1).getScriptBytes()).getChunks().get(0).data) .isCanonical()); assertEquals(RuleViolation.SIGNATURE_CANONICAL_ENCODING, DefaultRiskAnalysis.isStandard(tx2)); }
@Override public void onTransaction(Peer peer, Transaction tx) { Result result = DefaultRiskAnalysis.FACTORY.create(null, tx, NO_DEPS).analyze(); incrementCounter(TOTAL_KEY); log.info("tx {} result {}", tx.getHash(), result); incrementCounter(result.name()); if (result == Result.NON_STANDARD) incrementCounter(Result.NON_STANDARD + "-" + DefaultRiskAnalysis.isStandard(tx)); } });
private Result analyzeIsStandard() { // The IsStandard rules don't apply on testnet, because they're just a safety mechanism and we don't want to // crush innovation with valueless test coins. if (wallet != null && !wallet.getNetworkParameters().getId().equals(NetworkParameters.ID_MAINNET)) return Result.OK; RuleViolation ruleViolation = isStandard(tx); if (ruleViolation != RuleViolation.NONE) { nonStandard = tx; return Result.NON_STANDARD; } long time = wallet.getLastBlockSeenTimeSecs(); final List<TransactionInput> inputs = tx.getInputs(); for (int i = 0; i < inputs.size(); i++) { TransactionInput input = inputs.get(i); RuleViolation violation = isInputSignedWithForkId(input, time > 1501590000); if (violation != RuleViolation.NONE) { log.warn("TX considered non-standard due to input {} violating rule {}", i, violation); return Result.NON_STANDARD; } } for (Transaction dep : dependencies) { ruleViolation = isStandard(dep); if (ruleViolation != RuleViolation.NONE) { nonStandard = dep; return Result.NON_STANDARD; } } return Result.OK; }
@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 public void nonStandardDust() { Transaction standardTx = new Transaction(PARAMS); standardTx.addInput(PARAMS.getGenesisBlock().getTransactions().get(0).getOutput(0)); standardTx.addOutput(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(Coin.SATOSHI, 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(DefaultRiskAnalysis.MIN_ANALYSIS_NONDUST_OUTPUT, key1); // Dust threshold assertEquals(RiskAnalysis.Result.OK, DefaultRiskAnalysis.FACTORY.create(wallet, edgeCaseTx, NO_DEPS).analyze()); }
for (int i = 0; i < outputs.size(); i++) { TransactionOutput output = outputs.get(i); RuleViolation violation = isOutputStandard(output); if (violation != RuleViolation.NONE) { log.warn("TX considered non-standard due to output {} violating rule {}", i, violation); for (int i = 0; i < inputs.size(); i++) { TransactionInput input = inputs.get(i); RuleViolation violation = isInputStandard(input); if (violation != RuleViolation.NONE) { log.warn("TX considered non-standard due to input {} violating rule {}", i, violation);
private Result analyzeIsStandard() { // The IsStandard rules don't apply on testnet, because they're just a safety mechanism and we don't want to // crush innovation with valueless test coins. if (wallet != null && !wallet.getNetworkParameters().getId().equals(NetworkParameters.ID_MAINNET)) return Result.OK; RuleViolation ruleViolation = isStandard(tx); if (ruleViolation != RuleViolation.NONE) { nonStandard = tx; return Result.NON_STANDARD; } for (Transaction dep : dependencies) { ruleViolation = isStandard(dep); if (ruleViolation != RuleViolation.NONE) { nonStandard = dep; return Result.NON_STANDARD; } } return Result.OK; }
@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()); } }
@Test public void selfCreatedAreNotRisky() { Transaction tx = new Transaction(PARAMS); tx.addInput(PARAMS.getGenesisBlock().getTransactions().get(0).getOutput(0)).setSequenceNumber(1); tx.addOutput(COIN, key1); tx.setLockTime(TIMESTAMP + 86400); { // Is risky ... DefaultRiskAnalysis analysis = DefaultRiskAnalysis.FACTORY.create(wallet, tx, NO_DEPS); assertEquals(RiskAnalysis.Result.NON_FINAL, analysis.analyze()); } tx.getConfidence().setSource(TransactionConfidence.Source.SELF); { // Is no longer risky. DefaultRiskAnalysis analysis = DefaultRiskAnalysis.FACTORY.create(wallet, tx, NO_DEPS); assertEquals(RiskAnalysis.Result.OK, analysis.analyze()); } }
for (int i = 0; i < outputs.size(); i++) { TransactionOutput output = outputs.get(i); RuleViolation violation = isOutputStandard(output); if (violation != RuleViolation.NONE) { log.warn("TX considered non-standard due to output {} violating rule {}", i, violation); for (int i = 0; i < inputs.size(); i++) { TransactionInput input = inputs.get(i); RuleViolation violation = isInputStandard(input); if (violation != RuleViolation.NONE) { log.warn("TX considered non-standard due to input {} violating rule {}", i, violation);
@Override public Result analyze() { checkState(!analyzed); analyzed = true; Result result = analyzeIsFinal(); if (result != null && result != Result.OK) return result; return analyzeIsStandard(); }
private Result analyzeIsStandard() { // The IsStandard rules don't apply on testnet, because they're just a safety mechanism and we don't want to // crush innovation with valueless test coins. if (wallet != null && !wallet.getNetworkParameters().getId().equals(NetworkParameters.ID_MAINNET)) return Result.OK; RuleViolation ruleViolation = isStandard(tx); if (ruleViolation != RuleViolation.NONE) { nonStandard = tx; return Result.NON_STANDARD; } for (Transaction dep : dependencies) { ruleViolation = isStandard(dep); if (ruleViolation != RuleViolation.NONE) { nonStandard = dep; return Result.NON_STANDARD; } } return Result.OK; }
/** * <p>Returns either RuleViolation.NONE if the input is standard, or which rule makes it non-standard if so. * The "IsStandard" rules control whether the default Bitcoin Core client blocks relay of a tx / refuses to mine it, * however, non-standard transactions can still be included in blocks and will be accepted as valid if so.</p> * * <p>This method simply calls <tt>DefaultRiskAnalysis.isInputStandard(this)</tt>.</p> */ public DefaultRiskAnalysis.RuleViolation isStandard() { return DefaultRiskAnalysis.isInputStandard(this); }
@Override public DefaultRiskAnalysis create(Wallet wallet, Transaction tx, List<Transaction> dependencies) { return new DefaultRiskAnalysis(wallet, tx, dependencies); } }
@Test 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). Transaction tx = new Transaction(PARAMS); TransactionInput input = tx.addInput(PARAMS.getGenesisBlock().getTransactions().get(0).getOutput(0)); tx.addOutput(COIN, key1); tx.setLockTime(TIMESTAMP + 86400); DefaultRiskAnalysis analysis = DefaultRiskAnalysis.FACTORY.create(wallet, tx, NO_DEPS); assertEquals(RiskAnalysis.Result.OK, analysis.analyze()); assertNull(analysis.getNonFinal()); // Set a sequence number on the input to make it genuinely non-final. Verify it's risky. input.setSequenceNumber(TransactionInput.NO_SEQUENCE - 1); analysis = DefaultRiskAnalysis.FACTORY.create(wallet, tx, NO_DEPS); assertEquals(RiskAnalysis.Result.NON_FINAL, analysis.analyze()); assertEquals(tx, analysis.getNonFinal()); // If the lock time is the current block, it's about to become final and we consider it non-risky. tx.setLockTime(1000); analysis = DefaultRiskAnalysis.FACTORY.create(wallet, tx, NO_DEPS); assertEquals(RiskAnalysis.Result.OK, analysis.analyze()); }