Policy: Report debug message why inputs are non standard #29060

pull ismaelsadeeq wants to merge 3 commits into bitcoin:master from ismaelsadeeq:11-2023-non-standard-inputs-error-messages changing 10 files +164 −40
  1. ismaelsadeeq commented at 1:16 PM on December 12, 2023: member

    This PR is another attempt at #13525.

    Transactions that fail PreChecks Validation due to non-standard inputs now returns invalid validation stateTxValidationResult::TX_INPUTS_NOT_STANDARD along with a debug error message.

    Previously, the debug error message for non-standard inputs do not specify why the inputs were considered non-standard. Instead, the same error string, bad-txns-nonstandard-inputs, used for all types of non-standard input scriptSigs.

    This PR updates the AreInputsStandard to include the reason why inputs are non-standard in the debug message. This improves the Precheck debug message to be more descriptive.

    Furthermore, I have addressed all remaining comments from #13525 in this PR.

  2. DrahtBot commented at 1:16 PM on December 12, 2023: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    <!--006a51241073e994b41acfe9ec718e94-->

    Code Coverage & Benchmarks

    For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/29060.

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    ACK instagibbs, sedited, achow101
    Concept ACK stickies-v
    Stale ACK sipa

    If your review is incorrectly listed, please copy-paste <code>&lt;!--meta-tag:bot-skip--&gt;</code> into the comment that the bot should ignore.

    <!--174a7506f384e20aa4161008e828411d-->

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #34124 (validation: make CCoinsView a pure virtual interface by l0rinc)
    • #32729 (test,refactor: extract script template helpers & widen sigop count coverage by l0rinc)
    • #32317 (kernel: Separate UTXO set access from validation functions by sedited)

    If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

    <!--5faf32d7da4f0f540f40219e4f7537a3-->

    LLM Linter (✨ experimental)

    Possible places where named args for integral literals may be used (e.g. func(x, /*named_arg=*/0) in C++, and func(x, named_arg=0) in Python):

    • AddCoins(coins, CTransaction(txFrom), 0) in src/test/script_p2sh_tests.cpp
    • AddCoins(coins, CTransaction(tx_create), 0, false) in src/test/script_p2sh_tests.cpp
    • AddCoins(coins, CTransaction(tx_create_p2pk), 0, false) in src/test/script_p2sh_tests.cpp
    • AddCoins(coins, CTransaction(tx_create_segwit), 0, false) in src/test/script_p2sh_tests.cpp
    • AddCoins(coins, CTransaction(tx_create), 0, false) in src/test/transaction_tests.cpp
    • AddCoins(coins, CTransaction(tx_create_p2pk), 0, false) in src/test/transaction_tests.cpp

    <sup>2026-03-02 22:39:28</sup>

  3. DrahtBot added the label TX fees and policy on Dec 12, 2023
  4. ismaelsadeeq force-pushed on Dec 12, 2023
  5. DrahtBot added the label CI failed on Dec 12, 2023
  6. DrahtBot removed the label CI failed on Dec 12, 2023
  7. in src/policy/policy.cpp:178 in 6ffb9569b7 outdated
     174 | @@ -174,38 +175,49 @@ bool IsStandardTx(const CTransaction& tx, const std::optional<unsigned>& max_dat
     175 |   *
     176 |   * Note that only the non-witness portion of the transaction is checked here.
     177 |   */
     178 | -bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
     179 | +std::optional<NonStandardInputsReason> AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
    


    stickies-v commented at 1:42 PM on December 13, 2023:

    A function called AreInputsStandard that returns a falsy value if inputs are standard is counterintuitive. Would suggest renaming to e.g. HasNonStandardInput.

    <details> <summary>git diff on 6ffb9569b7</summary>

    diff --git a/src/bench/ccoins_caching.cpp b/src/bench/ccoins_caching.cpp
    index c677fb1c16..9a31cecd7a 100644
    --- a/src/bench/ccoins_caching.cpp
    +++ b/src/bench/ccoins_caching.cpp
    @@ -44,7 +44,7 @@ static void CCoinsCaching(benchmark::Bench& bench)
         // Benchmark.
         const CTransaction tx_1(t1);
         bench.run([&] {
    -        auto result{AreInputsStandard(tx_1, coins)};
    +        auto result{HasNonStandardInput(tx_1, coins)};
             assert(!result.has_value());
         });
         ECC_Stop();
    diff --git a/src/policy/policy.cpp b/src/policy/policy.cpp
    index c093cb3923..aada94d909 100644
    --- a/src/policy/policy.cpp
    +++ b/src/policy/policy.cpp
    @@ -175,7 +175,7 @@ bool IsStandardTx(const CTransaction& tx, const std::optional<unsigned>& max_dat
      *
      * Note that only the non-witness portion of the transaction is checked here.
      */
    -std::optional<NonStandardInputsReason> AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
    +std::optional<NonStandardInputsReason> HasNonStandardInput(const CTransaction& tx, const CCoinsViewCache& mapInputs)
     {
         if (tx.IsCoinBase()) {
             return std::nullopt; // Coinbases don't use vin normally
    @@ -187,32 +187,27 @@ std::optional<NonStandardInputsReason> AreInputsStandard(const CTransaction& tx,
             std::vector<std::vector<unsigned char> > vSolutions;
             TxoutType whichType = Solver(prev.scriptPubKey, vSolutions);
             if (whichType == TxoutType::NONSTANDARD) {
    -            auto result = NonStandardInputsReason{"bad-txns-input-script-nonstandard", strprintf("input %u", i)};
    -            return result;
    +            return NonStandardInputsReason{"bad-txns-input-script-nonstandard", strprintf("input %u", i)};
             } else if (whichType == TxoutType::WITNESS_UNKNOWN) {
                 // WITNESS_UNKNOWN failures are typically also caught with a policy
                 // flag in the script interpreter, but it can be helpful to catch
                 // this type of NONSTANDARD transaction earlier in transaction
                 // validation.
    -            auto result = NonStandardInputsReason{"bad-txns-input-witness-unknown", strprintf("input %u", i)};
    -            return result;
    +            return NonStandardInputsReason{"bad-txns-input-witness-unknown", strprintf("input %u", i)};
             } else if (whichType == TxoutType::SCRIPTHASH) {
                 std::vector<std::vector<unsigned char> > stack;
                 ScriptError serror;
                 // convert the scriptSig into a stack, so we can inspect the redeemScript
                 if (!EvalScript(stack, tx.vin[i].scriptSig, SCRIPT_VERIFY_NONE, BaseSignatureChecker(), SigVersion::BASE, &serror)) {
    -                auto result = NonStandardInputsReason{"bad-txns-input-scriptsig-failure", strprintf("input %u: %s", i, ScriptErrorString(serror))};
    -                return result;
    +                return NonStandardInputsReason{"bad-txns-input-scriptsig-failure", strprintf("input %u: %s", i, ScriptErrorString(serror))};
                 }
                 if (stack.empty()) {
    -                auto result = NonStandardInputsReason{"bad-txns-input-p2sh-no-redeemscript", strprintf("input %u", i)};
    -                return result;
    +                return NonStandardInputsReason{"bad-txns-input-p2sh-no-redeemscript", strprintf("input %u", i)};
                 }
                 CScript subscript(stack.back().begin(), stack.back().end());
                 unsigned int sigop_count = subscript.GetSigOpCount(true);
                 if (sigop_count > MAX_P2SH_SIGOPS) {
    -                auto result = NonStandardInputsReason{"bad-txns-input-scriptcheck-sigops", strprintf("input %u: %u > %u", i, sigop_count, MAX_P2SH_SIGOPS)};
    -                return result;
    +                return NonStandardInputsReason{"bad-txns-input-scriptcheck-sigops", strprintf("input %u: %u > %u", i, sigop_count, MAX_P2SH_SIGOPS)};
                 }
             }
         }
    diff --git a/src/policy/policy.h b/src/policy/policy.h
    index 3986f7d709..3d7d4b7e5c 100644
    --- a/src/policy/policy.h
    +++ b/src/policy/policy.h
    @@ -149,7 +149,7 @@ bool IsStandardTx(const CTransaction& tx, const std::optional<unsigned>& max_dat
      * [@return](/bitcoin-bitcoin/contributor/return/) std::nullopt if all inputs (scriptSigs) use only standard transaction forms else returns
      * NonStandardInputsReason which states why an input is not standard.
      */
    -std::optional<NonStandardInputsReason> AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs);
    +std::optional<NonStandardInputsReason> HasNonStandardInput(const CTransaction& tx, const CCoinsViewCache& mapInputs);
     /**
     * Check if the transaction is over standard P2WSH resources limit:
     * 3600bytes witnessScript size, 80bytes per witness stack element, 100 witness stack elements
    diff --git a/src/test/fuzz/coins_view.cpp b/src/test/fuzz/coins_view.cpp
    index 8f3e357a84..e210b2a13d 100644
    --- a/src/test/fuzz/coins_view.cpp
    +++ b/src/test/fuzz/coins_view.cpp
    @@ -235,7 +235,7 @@ FUZZ_TARGET(coins_view, .init = initialize_coins_view)
                     assert(expected_code_path);
                 },
                 [&] {
    -                (void)AreInputsStandard(CTransaction{random_mutable_transaction}, coins_view_cache);
    +                (void)HasNonStandardInput(CTransaction{random_mutable_transaction}, coins_view_cache);
                 },
                 [&] {
                     TxValidationState state;
    diff --git a/src/test/fuzz/transaction.cpp b/src/test/fuzz/transaction.cpp
    index 2a043f7458..785c823e37 100644
    --- a/src/test/fuzz/transaction.cpp
    +++ b/src/test/fuzz/transaction.cpp
    @@ -87,7 +87,7 @@ FUZZ_TARGET(transaction, .init = initialize_transaction)
     
         CCoinsView coins_view;
         const CCoinsViewCache coins_view_cache(&coins_view);
    -    (void)AreInputsStandard(tx, coins_view_cache);
    +    (void)HasNonStandardInput(tx, coins_view_cache);
         (void)IsWitnessStandard(tx, coins_view_cache);
     
         if (tx.GetTotalSize() < 250'000) { // Avoid high memory usage (with msan) due to json encoding
    diff --git a/src/test/script_p2sh_tests.cpp b/src/test/script_p2sh_tests.cpp
    index 500ec0bd6b..32907bdd24 100644
    --- a/src/test/script_p2sh_tests.cpp
    +++ b/src/test/script_p2sh_tests.cpp
    @@ -265,7 +265,7 @@ BOOST_AUTO_TEST_CASE(switchover)
         BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_EQUALVERIFY, ScriptErrorString(err));
     }
     
    -BOOST_AUTO_TEST_CASE(AreInputsStandard)
    +BOOST_AUTO_TEST_CASE(GetFirstNonStandardInput)
     {
         CCoinsView coinsDummy;
         CCoinsViewCache coins(&coinsDummy);
    @@ -366,7 +366,7 @@ BOOST_AUTO_TEST_CASE(AreInputsStandard)
         txTo.vin[3].scriptSig << OP_11 << OP_11 << std::vector<unsigned char>(oneAndTwo.begin(), oneAndTwo.end());
         txTo.vin[4].scriptSig << std::vector<unsigned char>(fifteenSigops.begin(), fifteenSigops.end());
     
    -    BOOST_CHECK(!::AreInputsStandard(CTransaction(txTo), coins).has_value());
    +    BOOST_CHECK(!::HasNonStandardInput(CTransaction(txTo), coins).has_value());
         // 22 P2SH sigops for all inputs (1 for vin[0], 6 for vin[3], 15 for vin[4]
         BOOST_CHECK_EQUAL(GetP2SHSigOpCount(CTransaction(txTo), coins), 22U);
     
    @@ -380,7 +380,7 @@ BOOST_AUTO_TEST_CASE(AreInputsStandard)
         txToNonStd1.vin[0].prevout.hash = txFrom.GetHash();
         txToNonStd1.vin[0].scriptSig << std::vector<unsigned char>(sixteenSigops.begin(), sixteenSigops.end());
     
    -    const auto txToNonStd1_res = ::AreInputsStandard(CTransaction(txToNonStd1), coins);
    +    const auto txToNonStd1_res = ::HasNonStandardInput(CTransaction(txToNonStd1), coins);
         BOOST_CHECK(txToNonStd1_res.has_value());
         BOOST_CHECK_EQUAL(txToNonStd1_res->reason, "bad-txns-input-scriptcheck-sigops");
         BOOST_CHECK_EQUAL(txToNonStd1_res->debug, "input 0: 16 > 15");
    @@ -398,7 +398,7 @@ BOOST_AUTO_TEST_CASE(AreInputsStandard)
     
         std::vector<std::vector<unsigned char>> vSolutions;
         BOOST_CHECK_EQUAL(Solver(txFrom.vout[6].scriptPubKey, vSolutions), TxoutType::SCRIPTHASH);
    -    const auto txToNonStd2_res = ::AreInputsStandard(CTransaction(txToNonStd2), coins);
    +    const auto txToNonStd2_res = ::HasNonStandardInput(CTransaction(txToNonStd2), coins);
         BOOST_CHECK(txToNonStd2_res.has_value());
         BOOST_CHECK_EQUAL(txToNonStd2_res->reason, "bad-txns-input-scriptcheck-sigops");
         BOOST_CHECK_EQUAL(txToNonStd2_res->debug, "input 0: 20 > 15");
    @@ -413,7 +413,7 @@ BOOST_AUTO_TEST_CASE(AreInputsStandard)
         txToNonStd2_no_scriptSig.vin[0].prevout.hash = txFrom.GetHash();
     
         BOOST_CHECK_EQUAL(Solver(txFrom.vout[6].scriptPubKey, vSolutions), TxoutType::SCRIPTHASH);
    -    const auto txToNonStd2_no_scriptSig_res = ::AreInputsStandard(CTransaction(txToNonStd2_no_scriptSig), coins);
    +    const auto txToNonStd2_no_scriptSig_res = ::HasNonStandardInput(CTransaction(txToNonStd2_no_scriptSig), coins);
         BOOST_CHECK(txToNonStd2_no_scriptSig_res.has_value());
         BOOST_CHECK_EQUAL(txToNonStd2_no_scriptSig_res->reason, "bad-txns-input-p2sh-no-redeemscript");
         BOOST_CHECK_EQUAL(txToNonStd2_no_scriptSig_res->debug, "input 0");
    @@ -429,7 +429,7 @@ BOOST_AUTO_TEST_CASE(AreInputsStandard)
         txToNonStd3.vin[0].prevout.hash = txFrom.GetHash();
     
         BOOST_CHECK_EQUAL(Solver(txFrom.vout[7].scriptPubKey, vSolutions), TxoutType::NONSTANDARD);
    -    const auto txToNonStd3_res = ::AreInputsStandard(CTransaction(txToNonStd3), coins);
    +    const auto txToNonStd3_res = ::HasNonStandardInput(CTransaction(txToNonStd3), coins);
         BOOST_CHECK(txToNonStd3_res.has_value());
         BOOST_CHECK_EQUAL(txToNonStd3_res->reason, "bad-txns-input-script-nonstandard");
         BOOST_CHECK_EQUAL(txToNonStd3_res->debug, "input 0");
    @@ -451,7 +451,7 @@ BOOST_AUTO_TEST_CASE(AreInputsStandard)
         BOOST_CHECK_EQUAL(Solver(txFrom.vout[8].scriptPubKey, vSolutions), TxoutType::SCRIPTHASH);
         BOOST_CHECK(!EvalScript(stack, txToNonStd4.vin[0].scriptSig, SCRIPT_VERIFY_NONE, BaseSignatureChecker(), SigVersion::BASE, &serror));
         BOOST_CHECK_EQUAL(serror, SCRIPT_ERR_OP_RETURN);
    -    const auto txToNonStd4_res = ::AreInputsStandard(CTransaction(txToNonStd4), coins);
    +    const auto txToNonStd4_res = ::HasNonStandardInput(CTransaction(txToNonStd4), coins);
         BOOST_CHECK(txToNonStd4_res.has_value());
         BOOST_CHECK_EQUAL(txToNonStd4_res->reason, "bad-txns-input-scriptsig-failure");
         BOOST_CHECK_EQUAL(txToNonStd4_res->debug, "input 0: OP_RETURN was encountered");
    @@ -465,7 +465,7 @@ BOOST_AUTO_TEST_CASE(AreInputsStandard)
         txWitnessUnknown.vin[0].prevout.n = 9;
         txWitnessUnknown.vin[0].prevout.hash = txFrom.GetHash();
         BOOST_CHECK_EQUAL(Solver(txFrom.vout[9].scriptPubKey, vSolutions), TxoutType::WITNESS_UNKNOWN);
    -    const auto txWitnessUnknown_res = ::AreInputsStandard(CTransaction(txWitnessUnknown), coins);
    +    const auto txWitnessUnknown_res = ::HasNonStandardInput(CTransaction(txWitnessUnknown), coins);
         BOOST_CHECK(txWitnessUnknown_res.has_value());
         BOOST_CHECK_EQUAL(txWitnessUnknown_res->reason, "bad-txns-input-witness-unknown");
         BOOST_CHECK_EQUAL(txWitnessUnknown_res->debug, "input 0");
    diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp
    index e2443a2fce..05074eb631 100644
    --- a/src/test/transaction_tests.cpp
    +++ b/src/test/transaction_tests.cpp
    @@ -406,7 +406,7 @@ BOOST_AUTO_TEST_CASE(test_Get)
         t1.vout[0].nValue = 90*CENT;
         t1.vout[0].scriptPubKey << OP_1;
     
    -    BOOST_CHECK(!AreInputsStandard(CTransaction(t1), coins).has_value());
    +    BOOST_CHECK(!HasNonStandardInput(CTransaction(t1), coins).has_value());
     }
     
     static void CreateCreditAndSpend(const FillableSigningProvider& keystore, const CScript& outscript, CTransactionRef& output, CMutableTransaction& input, bool success = true)
    diff --git a/src/validation.cpp b/src/validation.cpp
    index 96204b2ab4..cdcf56860f 100644
    --- a/src/validation.cpp
    +++ b/src/validation.cpp
    @@ -822,7 +822,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
             return false; // state filled in by CheckTxInputs
         }
     
    -    const auto inputs_standardness_result = AreInputsStandard(tx, m_view);
    +    const auto inputs_standardness_result = HasNonStandardInput(tx, m_view);
         if (m_pool.m_require_standard && inputs_standardness_result.has_value()) {
             return state.Invalid(TxValidationResult::TX_INPUTS_NOT_STANDARD, inputs_standardness_result.value().reason, inputs_standardness_result.value().debug);
         }
    
    

    </details>

    Alternatively, could use util::Result failure values after #25665 is merged.


    stickies-v commented at 3:09 PM on December 13, 2023:

    nit: requires #include <optional> (in both files)


    ismaelsadeeq commented at 11:45 AM on December 14, 2023:

    Thank you, HasNonStandardInput makes more sense, rename to HasNonStandardInput in 94cd47f3460


    ismaelsadeeq commented at 11:47 AM on December 14, 2023:

    Fixed


    ismaelsadeeq commented at 12:34 PM on December 25, 2023:

    Alternatively, could use util::Result failure values after #25665 is merged.

    will resolve this since I am now using TxValidationState

  8. in src/policy/policy.cpp:191 in 6ffb9569b7 outdated
     189 |          std::vector<std::vector<unsigned char> > vSolutions;
     190 |          TxoutType whichType = Solver(prev.scriptPubKey, vSolutions);
     191 | -        if (whichType == TxoutType::NONSTANDARD || whichType == TxoutType::WITNESS_UNKNOWN) {
     192 | +        if (whichType == TxoutType::NONSTANDARD) {
     193 | +            auto result = NonStandardInputsReason{"bad-txns-input-script-nonstandard", strprintf("input %u", i)};
     194 | +            return result;
    


    stickies-v commented at 1:43 PM on December 13, 2023:

    nit: No need for the intermediary var, here and in a bunch of other places

                return NonStandardInputsReason{"bad-txns-input-script-nonstandard", strprintf("input %u", i)};
    

    ismaelsadeeq commented at 11:45 AM on December 14, 2023:

    Yes, fixed now

  9. in src/test/script_p2sh_tests.cpp:369 in 6ffb9569b7 outdated
     365 | @@ -350,10 +366,11 @@ BOOST_AUTO_TEST_CASE(AreInputsStandard)
     366 |      txTo.vin[3].scriptSig << OP_11 << OP_11 << std::vector<unsigned char>(oneAndTwo.begin(), oneAndTwo.end());
     367 |      txTo.vin[4].scriptSig << std::vector<unsigned char>(fifteenSigops.begin(), fifteenSigops.end());
     368 |  
     369 | -    BOOST_CHECK(::AreInputsStandard(CTransaction(txTo), coins));
     370 | +    BOOST_CHECK(!::AreInputsStandard(CTransaction(txTo), coins).has_value());
    


    stickies-v commented at 1:44 PM on December 13, 2023:

    nit: no need for .has_value(), idiomatic enough as is imo (here and in a bunch of other places)

        BOOST_CHECK(!::AreInputsStandard(CTransaction(txTo), coins));
    

    ismaelsadeeq commented at 11:45 AM on December 14, 2023:

    Fixed in latest push

  10. in src/validation.cpp:827 in 6ffb9569b7 outdated
     821 | @@ -822,8 +822,9 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
     822 |          return false; // state filled in by CheckTxInputs
     823 |      }
     824 |  
     825 | -    if (m_pool.m_require_standard && !AreInputsStandard(tx, m_view)) {
     826 | -        return state.Invalid(TxValidationResult::TX_INPUTS_NOT_STANDARD, "bad-txns-nonstandard-inputs");
     827 | +    const auto inputs_standardness_result = AreInputsStandard(tx, m_view);
     828 | +    if (m_pool.m_require_standard && inputs_standardness_result.has_value()) {
     829 | +        return state.Invalid(TxValidationResult::TX_INPUTS_NOT_STANDARD, inputs_standardness_result.value().reason, inputs_standardness_result.value().debug);
    


    stickies-v commented at 1:45 PM on December 13, 2023:

    Since we've validated has_value() just above, can just derefence safely for conciseness:

            return state.Invalid(TxValidationResult::TX_INPUTS_NOT_STANDARD, inputs_standardness_result->reason, inputs_standardness_result->debug);
    

    ismaelsadeeq commented at 11:45 AM on December 14, 2023:

    Fixed

  11. in src/bench/ccoins_caching.cpp:48 in 6ffb9569b7 outdated
      43 | @@ -44,8 +44,8 @@ static void CCoinsCaching(benchmark::Bench& bench)
      44 |      // Benchmark.
      45 |      const CTransaction tx_1(t1);
      46 |      bench.run([&] {
      47 | -        bool success{AreInputsStandard(tx_1, coins)};
      48 | -        assert(success);
      49 | +        auto result{AreInputsStandard(tx_1, coins)};
      50 | +        assert(!result.has_value());
    


    stickies-v commented at 1:46 PM on December 13, 2023:

    nit: more concise, easier to read (+ in other places)

            assert(!AreInputsStandard(tx_1, coins));
    

    ismaelsadeeq commented at 11:46 AM on December 14, 2023:

    Taken

  12. in src/policy/policy.h:124 in 6ffb9569b7 outdated
     120 | @@ -121,6 +121,11 @@ static constexpr unsigned int STANDARD_NOT_MANDATORY_VERIFY_FLAGS{STANDARD_SCRIP
     121 |  /** Used as the flags parameter to sequence and nLocktime checks in non-consensus code. */
     122 |  static constexpr unsigned int STANDARD_LOCKTIME_VERIFY_FLAGS{LOCKTIME_VERIFY_SEQUENCE};
     123 |  
     124 | +struct NonStandardInputsReason {
    


    stickies-v commented at 2:25 PM on December 13, 2023:

    nit: this is the reason for a single input

    struct NonStandardInputReason {
    

    ismaelsadeeq commented at 11:46 AM on December 14, 2023:

    Yes, taken

  13. in src/validation.cpp:825 in 6ffb9569b7 outdated
     821 | @@ -822,8 +822,9 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
     822 |          return false; // state filled in by CheckTxInputs
     823 |      }
     824 |  
     825 | -    if (m_pool.m_require_standard && !AreInputsStandard(tx, m_view)) {
     826 | -        return state.Invalid(TxValidationResult::TX_INPUTS_NOT_STANDARD, "bad-txns-nonstandard-inputs");
     827 | +    const auto inputs_standardness_result = AreInputsStandard(tx, m_view);
    


    stickies-v commented at 3:07 PM on December 13, 2023:

    This causes unnecessary input validation if !m_require_standard, would suggest changing to e.g.

    <details> <summary>git diff on 6ffb9569b7</summary>

    diff --git a/src/validation.cpp b/src/validation.cpp
    index 96204b2ab4..bf1c578384 100644
    --- a/src/validation.cpp
    +++ b/src/validation.cpp
    @@ -822,9 +822,10 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
             return false; // state filled in by CheckTxInputs
         }
     
    -    const auto inputs_standardness_result = AreInputsStandard(tx, m_view);
    -    if (m_pool.m_require_standard && inputs_standardness_result.has_value()) {
    -        return state.Invalid(TxValidationResult::TX_INPUTS_NOT_STANDARD, inputs_standardness_result.value().reason, inputs_standardness_result.value().debug);
    +    if (m_pool.m_require_standard) {
    +        if (const auto result{AreInputsStandard(tx, m_view)}) {
    +            return state.Invalid(TxValidationResult::TX_INPUTS_NOT_STANDARD, result->reason, result->debug);
    +        }
         }
     
         // Check for non-standard witnesses.
    
    

    </details>


    ismaelsadeeq commented at 11:47 AM on December 14, 2023:

    Thank you fixed in 94cd47f3460

  14. stickies-v commented at 3:15 PM on December 13, 2023: contributor

    Concept ACK

  15. ismaelsadeeq force-pushed on Dec 14, 2023
  16. ismaelsadeeq commented at 11:49 AM on December 14, 2023: member
  17. in src/policy/policy.cpp:191 in e032462c44 outdated
     189 |  
     190 |          std::vector<std::vector<unsigned char> > vSolutions;
     191 |          TxoutType whichType = Solver(prev.scriptPubKey, vSolutions);
     192 | -        if (whichType == TxoutType::NONSTANDARD || whichType == TxoutType::WITNESS_UNKNOWN) {
     193 | +        if (whichType == TxoutType::NONSTANDARD) {
     194 | +            return NonStandardInputReason{"bad-txns-input-script-nonstandard", strprintf("input %u", i)};
    


    luke-jr commented at 6:09 PM on December 17, 2023:
                return NonStandardInputReason{"bad-txns-input-script-unknown", strprintf("input %u", i)};
    

    ismaelsadeeq commented at 2:54 PM on December 18, 2023:

    Fixed thanks

  18. in src/policy/policy.cpp:206 in e032462c44 outdated
     209 | -                return false;
     210 | +            if (!EvalScript(stack, tx.vin[i].scriptSig, SCRIPT_VERIFY_NONE, BaseSignatureChecker(), SigVersion::BASE, &serror)) {
     211 | +                return NonStandardInputReason{"bad-txns-input-scriptsig-failure", strprintf("input %u: %s", i, ScriptErrorString(serror))};
     212 | +            }
     213 | +            if (stack.empty()) {
     214 | +                return NonStandardInputReason{"bad-txns-input-p2sh-no-redeemscript", strprintf("input %u", i)};
    


    luke-jr commented at 6:11 PM on December 17, 2023:
                    return NonStandardInputReason{"bad-txns-input-scriptcheck-missing", strprintf("input %u", i)};
    

    ismaelsadeeq commented at 2:54 PM on December 18, 2023:

    fixed

  19. luke-jr changes_requested
  20. ismaelsadeeq force-pushed on Dec 18, 2023
  21. ismaelsadeeq commented at 3:00 PM on December 18, 2023: member

    Thanks for reviewing @luke-jr Force pushed from https://github.com/bitcoin/bitcoin/commit/e032462c44dba9b6961c1e18bd597d63c6afac02 to https://github.com/bitcoin/bitcoin/pull/29060/commits/0a39b0708ec72292ab3e1dde643dedfbd5d20674

    Compare diff

    The changes in latest push are:

    • Rebased on master to fix CI
    • Updated the non-standardness reason to address @luke-jr comments
    • bad-txns-input-script-nonstandard --> bad-txns-input-script-unknown
    • bad-txns-input-scriptsig-failure --> bad-txns-input-p2sh-scriptsig-malformed
    • bad-txns-input-p2sh-no-redeemscript --> bad-txns-input-scriptcheck-missing
    • bad-txns-input-scriptcheck-sigops --> bad-txns-input-p2sh-redeemscript-sigops
  22. ismaelsadeeq force-pushed on Dec 18, 2023
  23. in src/policy/policy.h:128 in 0a39b0708e outdated
     121 | @@ -121,6 +122,11 @@ static constexpr unsigned int STANDARD_NOT_MANDATORY_VERIFY_FLAGS{STANDARD_SCRIP
     122 |  /** Used as the flags parameter to sequence and nLocktime checks in non-consensus code. */
     123 |  static constexpr unsigned int STANDARD_LOCKTIME_VERIFY_FLAGS{LOCKTIME_VERIFY_SEQUENCE};
     124 |  
     125 | +struct NonStandardInputReason {
     126 | +    std::string reason;
     127 | +    std::string debug;
     128 | +};
    


    glozow commented at 11:06 AM on December 20, 2023:

    Maybe just use TxValidationState?


    ismaelsadeeq commented at 4:09 PM on December 24, 2023:

    Thanks updated

  24. ismaelsadeeq force-pushed on Dec 24, 2023
  25. ismaelsadeeq force-pushed on Dec 24, 2023
  26. DrahtBot added the label CI failed on Jan 16, 2024
  27. ismaelsadeeq force-pushed on Jan 25, 2024
  28. DrahtBot removed the label CI failed on Jan 26, 2024
  29. ismaelsadeeq commented at 12:36 PM on January 30, 2024: member

    Rebased on master for green CI.

  30. achow101 requested review from luke-jr on Apr 9, 2024
  31. achow101 requested review from glozow on Apr 9, 2024
  32. ismaelsadeeq requested review from stickies-v on Apr 24, 2024
  33. DrahtBot added the label Needs rebase on May 15, 2024
  34. ismaelsadeeq force-pushed on May 17, 2024
  35. DrahtBot removed the label Needs rebase on May 17, 2024
  36. in src/validation.cpp:852 in 101a30833b outdated
     847 | @@ -848,8 +848,11 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
     848 |          return false; // state filled in by CheckTxInputs
     849 |      }
     850 |  
     851 | -    if (m_pool.m_opts.require_standard && !AreInputsStandard(tx, m_view)) {
     852 | -        return state.Invalid(TxValidationResult::TX_INPUTS_NOT_STANDARD, "bad-txns-nonstandard-inputs");
     853 | +    if (m_pool.m_opts.require_standard) {
     854 | +        state = HasNonStandardInput(tx, m_view);
    


    luke-jr commented at 5:32 PM on May 31, 2024:

    Seems like it'd be nicer to just pass state into the function rather than creating a new one and copying it.


    ismaelsadeeq commented at 6:42 PM on May 31, 2024:

    Seems like it'd be nicer to just pass state into the function

    I am taking this review comment as a non-blocking (nit) suggestion based on the tone.

    I will update this when there are blocking comments that indicate a need to retouch the diffs.

    Also, I would like to know whether this PR is conceptually acknowledged, @luke-jr. Thanks for your review.


    ismaelsadeeq commented at 9:45 PM on November 1, 2024:

    In this case, a new state is indeed created in HasNonStandardInput, but either RVO or an implicit move is performed not a copy?

    I think, in general, returning a value is preferred over using out parameters? same was also recommended here https://github.com/bitcoin/bitcoin/blob/master/doc/developer-notes.md#coding-style-c-functions-and-methods

  37. DrahtBot added the label CI failed on Sep 11, 2024
  38. ismaelsadeeq force-pushed on Sep 14, 2024
  39. DrahtBot removed the label CI failed on Sep 14, 2024
  40. achow101 requested review from willcl-ark on Oct 15, 2024
  41. achow101 commented at 3:58 PM on October 15, 2024: member

    cc @tdb3

  42. in src/policy/policy.h:146 in 38a84db797 outdated
     146 | -*/
     147 | -bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs);
     148 | + * Check for standard transaction types
     149 | + * @param[in] mapInputs       Map of previous transactions that have outputs we're spending
     150 | + * @returns valid TxValidationState if all inputs (scriptSigs) use only standard transaction forms else returns
     151 | + * invalid TxValidationState which states why an input is not standard.
    


    tdb3 commented at 12:36 AM on October 31, 2024:

    Could also clarify that invalid TxValidationState returned describes the first nonstandard input encountered.

    For example:

    - * invalid TxValidationState which states why an input is not standard
    + * invalid TxValidationState which states why the first invalid input is not standard
    

    ismaelsadeeq commented at 8:07 PM on November 1, 2024:

    Done thanks

  43. in src/test/script_p2sh_tests.cpp:345 in 3ef7ac6f47 outdated
     341 | +    txFrom.vout[7].scriptPubKey = no_sigops;
     342 | +    txFrom.vout[7].nValue = 1000;
     343 | +
     344 | +    // vout [8] is non-standard because it contains OP_RETURN in its scriptSig.
     345 | +    static const unsigned char op_return[] = {OP_RETURN};
     346 | +    txFrom.vout[8].scriptPubKey = GetScriptForDestination(ScriptHash(CScript(op_return, op_return + sizeof(op_return))));
    


    tdb3 commented at 1:26 AM on October 31, 2024:

    Instantiating the CScript here could allow re-use later (in txToNonStd4.vin[0].scriptSig = CScript(op_return, op_return + sizeof(op_return));)


    ismaelsadeeq commented at 8:08 PM on November 1, 2024:

    Good idea, done

  44. tdb3 commented at 1:33 AM on October 31, 2024: contributor

    I'm a fan of the more descriptive errors.

    However, would some of these more descriptive errors flow down to RPC responses and cause issues for users? (e.g. testmempoolaccept).

    #30212 (comment)

    Does bitcoind consider this to be a breaking change? The new error message may be "more accurate", but for clients that were matching on this error to figure out why a transaction was rejected, this breaks old behavior.

  45. ismaelsadeeq force-pushed on Nov 1, 2024
  46. ismaelsadeeq force-pushed on Nov 1, 2024
  47. DrahtBot commented at 8:07 PM on November 1, 2024: contributor

    <!--85328a0da195eb286784d51f73fa0af9-->

    🚧 At least one of the CI tasks failed. <sub>Debug: https://github.com/bitcoin/bitcoin/runs/32405132725</sub>

    <details><summary>Hints</summary>

    Try to run the tests locally, according to the documentation. However, a CI failure may still happen due to a number of reasons, for example:

    • Possibly due to a silent merge conflict (the changes in this pull request being incompatible with the current code in the target branch). If so, make sure to rebase on the latest commit of the target branch.

    • A sanitizer issue, which can only be found by compiling with the sanitizer and running the affected test.

    • An intermittent issue.

    Leave a comment here, if you need help tracking down a confusing failure.

    </details>

  48. DrahtBot added the label CI failed on Nov 1, 2024
  49. ismaelsadeeq commented at 8:08 PM on November 1, 2024: member

    Does bitcoind consider this to be a breaking change? The new error message may be "more accurate", but for clients that were matching on this error to figure out why a transaction was rejected, this breaks old behavior.

    I've added a release notes for this

  50. ismaelsadeeq force-pushed on Nov 1, 2024
  51. DrahtBot removed the label CI failed on Nov 1, 2024
  52. tdb3 commented at 6:26 PM on November 2, 2024: contributor

    Does bitcoind consider this to be a breaking change? The new error message may be "more accurate", but for clients that were matching on this error to figure out why a transaction was rejected, this breaks old behavior.

    I've added a release notes for this

    The release note helps a lot. To err on the side of caution, it seems appropriate to include a -deprecatedrpc= option, to enable a period of deprecation for users.

  53. ismaelsadeeq commented at 8:49 PM on November 12, 2024: member

    The release note helps a lot. To err on the side of caution, it seems appropriate to include a -deprecatedrpc= option, to enable a period of deprecation for users.

    Thanks for your review @tdb3 will address this comment soon.

  54. ismaelsadeeq commented at 4:33 PM on November 15, 2024: member

    The release note helps a lot. To err on the side of caution, it seems appropriate to include a -deprecatedrpc= option, to enable a period of deprecation for users.

    I attempted this, but it's a bit non trivial with some if-else branching. I also had to add a new TransactionError enum type to handle sendrawtransaction case.

    <details> <summary> see **untested** diff </summary>

    diff --git a/src/common/messages.cpp b/src/common/messages.cpp
    index 5fe3e9e4d86..3142ca07b4c 100644
    --- a/src/common/messages.cpp
    +++ b/src/common/messages.cpp
    @@ -132,6 +132,8 @@ bilingual_str TransactionErrorString(const TransactionError err)
                 return Untranslated("Transaction outputs already in utxo set");
             case TransactionError::MEMPOOL_REJECTED:
                 return Untranslated("Transaction rejected by mempool");
    +        case TransactionError::MEMPOOL_REJECTED_NON_STANDARD_INPUT:
    +            return Untranslated("Transaction rejected by mempool");
             case TransactionError::MEMPOOL_ERROR:
                 return Untranslated("Mempool internal error");
             case TransactionError::MAX_FEE_EXCEEDED:
    diff --git a/src/node/transaction.cpp b/src/node/transaction.cpp
    index 0f45da45dbd..c35bee4ee61 100644
    --- a/src/node/transaction.cpp
    +++ b/src/node/transaction.cpp
    @@ -25,6 +25,9 @@ static TransactionError HandleATMPError(const TxValidationState& state, std::str
             if (state.GetResult() == TxValidationResult::TX_MISSING_INPUTS) {
                 return TransactionError::MISSING_INPUTS;
             }
    +        if (state.GetResult() == TxValidationResult::TX_INPUTS_NOT_STANDARD) {
    +            return TransactionError::MEMPOOL_REJECTED_NON_STANDARD_INPUT;
    +        }
             return TransactionError::MEMPOOL_REJECTED;
         } else {
             return TransactionError::MEMPOOL_ERROR;
    diff --git a/src/node/types.h b/src/node/types.h
    index 1302f1b127f..5cdb696204f 100644
    --- a/src/node/types.h
    +++ b/src/node/types.h
    @@ -21,6 +21,7 @@ enum class TransactionError {
         MISSING_INPUTS,
         ALREADY_IN_UTXO_SET,
         MEMPOOL_REJECTED,
    +    MEMPOOL_REJECTED_NON_STANDARD_INPUT,
         MEMPOOL_ERROR,
         MAX_FEE_EXCEEDED,
         MAX_BURN_EXCEEDED,
    diff --git a/src/rpc/mempool.cpp b/src/rpc/mempool.cpp
    index d61898260bf..a9f434fa605 100644
    --- a/src/rpc/mempool.cpp
    +++ b/src/rpc/mempool.cpp
    @@ -95,6 +95,9 @@ static RPCHelpMan sendrawtransaction()
                 NodeContext& node = EnsureAnyNodeContext(request.context);
                 const TransactionError err = BroadcastTransaction(node, tx, err_string, max_raw_tx_fee, /*relay=*/true, /*wait_callback=*/true);
                 if (TransactionError::OK != err) {
    +                if (IsDeprecatedRPCEnabled("nonstandard-inputs-err") && err == TransactionError::MEMPOOL_REJECTED_NON_STANDARD_INPUT) {
    +                    throw JSONRPCTransactionError(err, "bad-txns-nonstandard-inputs");
    +                }
                     throw JSONRPCTransactionError(err, err_string);
                 }
     
    @@ -241,7 +244,11 @@ static RPCHelpMan testmempoolaccept()
                         if (state.GetResult() == TxValidationResult::TX_MISSING_INPUTS) {
                             result_inner.pushKV("reject-reason", "missing-inputs");
                         } else {
    -                        result_inner.pushKV("reject-reason", state.GetRejectReason());
    +                        if (IsDeprecatedRPCEnabled("nonstandard-inputs-err") && state.GetResult() == TxValidationResult::TX_INPUTS_NOT_STANDA
    RD) {
    +                            result_inner.pushKV("reject-reason", "bad-txns-nonstandard-inputs");
    +                        } else {
    +                            result_inner.pushKV("reject-reason", state.GetRejectReason());
    +                        }
                         }
                     }
                     rpc_result.push_back(std::move(result_inner));
    @@ -961,7 +968,6 @@ static RPCHelpMan submitpackage()
                 }
     
                 UniValue rpc_result{UniValue::VOBJ};
    -            rpc_result.pushKV("package_msg", package_msg);
                 UniValue tx_result_map{UniValue::VOBJ};
                 std::set<uint256> replaced_txids;
                 for (const auto& tx : txns) {
    @@ -979,7 +985,11 @@ static RPCHelpMan submitpackage()
                         result_inner.pushKV("other-wtxid", it->second.m_other_wtxid.value().GetHex());
                         break;
                     case MempoolAcceptResult::ResultType::INVALID:
    -                    result_inner.pushKV("error", it->second.m_state.ToString());
    +                    if (IsDeprecatedRPCEnabled("nonstandard-inputs-err") && it->second.m_state.GetResult() == TxValidationResult::TX_INPUTS_N
    OT_STANDARD){
    +                        result_inner.pushKV("error", "bad-txns-nonstandard-inputs");
    +                    } else {
    +                        result_inner.pushKV("error", it->second.m_state.ToString());
    +                    }
                         break;
                     case MempoolAcceptResult::ResultType::VALID:
                     case MempoolAcceptResult::ResultType::MEMPOOL_ENTRY:
    diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp
    index 678bac7a185..3653421dcae 100644
    --- a/src/rpc/util.cpp
    +++ b/src/rpc/util.cpp
    @@ -400,6 +400,8 @@ RPCErrorCode RPCErrorFromTransactionError(TransactionError terr)
         switch (terr) {
             case TransactionError::MEMPOOL_REJECTED:
                 return RPC_TRANSACTION_REJECTED;
    +        case TransactionError::MEMPOOL_REJECTED_NON_STANDARD_INPUT:
    +            return RPC_TRANSACTION_REJECTED;
             case TransactionError::ALREADY_IN_UTXO_SET:
                 return RPC_VERIFY_ALREADY_IN_UTXO_SET;
             default: break;
    diff --git a/src/test/fuzz/kitchen_sink.cpp b/src/test/fuzz/kitchen_sink.cpp
    index 62b49106cdd..4df4a2c4372 100644
    --- a/src/test/fuzz/kitchen_sink.cpp
    +++ b/src/test/fuzz/kitchen_sink.cpp
    @@ -25,6 +25,7 @@ constexpr TransactionError ALL_TRANSACTION_ERROR[] = {
         TransactionError::MISSING_INPUTS,
         TransactionError::ALREADY_IN_UTXO_SET,
         TransactionError::MEMPOOL_REJECTED,
    +    TransactionError::MEMPOOL_REJECTED_NON_STANDARD_INPUT,
         TransactionError::MEMPOOL_ERROR,
         TransactionError::MAX_FEE_EXCEEDED,
     };
    

    </details>

    However, do you think it would be better to make the prefix of the message bad-txns-nonstandard-inputs and append the additional information after it? This approach ensures that clients relying solely on pattern matching remain unaffected, as the error code remains unchanged.

    The only clients that affected after the above suggestion are those performing an exact match.

    Let me know what you think about the two approaches.

  55. fanquake referenced this in commit b43cfa20fd on Mar 21, 2025
  56. DrahtBot added the label Needs rebase on Jul 3, 2025
  57. ismaelsadeeq force-pushed on Jul 3, 2025
  58. DrahtBot removed the label Needs rebase on Jul 3, 2025
  59. DrahtBot added the label CI failed on Jul 3, 2025
  60. DrahtBot commented at 7:43 PM on July 3, 2025: contributor

    <!--85328a0da195eb286784d51f73fa0af9-->

    🚧 At least one of the CI tasks failed. <sub>Task lint: https://github.com/bitcoin/bitcoin/runs/45300633777</sub> <sub>LLM reason (✨ experimental): The CI failure is caused by a lint error related to missing a trailing newline.</sub>

    <details><summary>Hints</summary>

    Try to run the tests locally, according to the documentation. However, a CI failure may still happen due to a number of reasons, for example:

    • Possibly due to a silent merge conflict (the changes in this pull request being incompatible with the current code in the target branch). If so, make sure to rebase on the latest commit of the target branch.

    • A sanitizer issue, which can only be found by compiling with the sanitizer and running the affected test.

    • An intermittent issue.

    Leave a comment here, if you need help tracking down a confusing failure.

    </details>

  61. in doc/release-notes-29060.md:3 in b487edcffa outdated
       0 | @@ -0,0 +1,12 @@
       1 | +- Logging and RPC
       2 | +
       3 | +  - Bitcoin Core now provide the specific reason and index of the first
    


    DrahtBot commented at 7:47 AM on July 4, 2025:

    Possible typos and grammar issues:

    “Bitcoin Core now provide” -> “Bitcoin Core now provides” [subject–verb agreement]
    remove extra “will” in “bad-txns-nonstandard-inputs will message will now be:” -> “bad-txns-nonstandard-inputs message will now be:” [duplicate word]

    ismaelsadeeq commented at 4:37 PM on July 7, 2025:

    Fixed.

  62. ismaelsadeeq force-pushed on Jul 7, 2025
  63. ismaelsadeeq force-pushed on Jul 7, 2025
  64. ismaelsadeeq commented at 4:57 PM on July 7, 2025: member

    Rebased and addressed comment from @DrahtBot see diff

  65. DrahtBot removed the label CI failed on Jul 7, 2025
  66. DrahtBot added the label CI failed on Jul 19, 2025
  67. DrahtBot commented at 5:05 PM on July 19, 2025: contributor

    <!--85328a0da195eb286784d51f73fa0af9-->

    🚧 At least one of the CI tasks failed. <sub>Task tidy: https://github.com/bitcoin/bitcoin/runs/45492221044</sub> <sub>LLM reason (✨ experimental): The CI failure is caused by a compilation error in src/policy/policy.cpp where returning false from a function with return type TxValidationState causes a type mismatch.</sub>

    <details><summary>Hints</summary>

    Try to run the tests locally, according to the documentation. However, a CI failure may still happen due to a number of reasons, for example:

    • Possibly due to a silent merge conflict (the changes in this pull request being incompatible with the current code in the target branch). If so, make sure to rebase on the latest commit of the target branch.

    • A sanitizer issue, which can only be found by compiling with the sanitizer and running the affected test.

    • An intermittent issue.

    Leave a comment here, if you need help tracking down a confusing failure.

    </details>

  68. ismaelsadeeq force-pushed on Jul 21, 2025
  69. ismaelsadeeq force-pushed on Jul 21, 2025
  70. ismaelsadeeq commented at 11:09 AM on July 21, 2025: member

    Rebased after the merge of #32521 to pick up recent changes to AreInputsStandard.

    A new verbose message will be returned when the BIP54 legacy sigops limit is violated:

    • bad-txns-non-witness-sigops-exceeds-bip54-limit: This occurs when the total number of non-witness sigops across the entire transaction exceeds MAX_TX_LEGACY_SIGOPS. Additionally, a debug message is also returned with the actual number of sigops and the limit, for example: sigops 2501 > limit 2500.

    3cde7f185255b1bc07a90e95708665e0b6d5a46d..85c6c97b609bcbcd2b2f13638d858d5a31a903

  71. DrahtBot removed the label CI failed on Jul 21, 2025
  72. in src/policy/policy.cpp:214 in 3cd21ae2d4 outdated
     210 | @@ -210,42 +211,56 @@ static bool CheckSigopsBIP54(const CTransaction& tx, const CCoinsViewCache& inpu
     211 |   *
     212 |   * We also check the total number of non-witness sigops across the whole transaction, as per BIP54.
     213 |   */
     214 | -bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
     215 | +TxValidationState HasNonStandardInput(const CTransaction& tx, const CCoinsViewCache& mapInputs)
    


    sedited commented at 1:58 PM on October 11, 2025:

    Since we're renaming, I would prefer ValidateInputsStandardness here. The Has prefix implies a bool return value to me.


    ismaelsadeeq commented at 12:20 PM on October 13, 2025:

    Done thanks

  73. in src/test/script_p2sh_tests.cpp:417 in 9108490071 outdated
     412 | @@ -391,8 +413,79 @@ BOOST_AUTO_TEST_CASE(HasNonStandardInput)
     413 |      txToNonStd2.vin[0].prevout.hash = txFrom.GetHash();
     414 |      txToNonStd2.vin[0].scriptSig << std::vector<unsigned char>(twentySigops.begin(), twentySigops.end());
     415 |  
     416 | -    BOOST_CHECK(::HasNonStandardInput(CTransaction(txToNonStd2), coins).IsInvalid());
     417 | +    std::vector<std::vector<unsigned char>> vSolutions;
     418 | +    BOOST_CHECK_EQUAL(Solver(txFrom.vout[6].scriptPubKey, vSolutions), TxoutType::SCRIPTHASH);
    


    sedited commented at 2:21 PM on October 11, 2025:

    I don't quite follow why we are invoking the solver here. What is this supposed to test?


    ismaelsadeeq commented at 12:21 PM on October 13, 2025:

    This test is redundant removed.

  74. in src/test/script_p2sh_tests.cpp:470 in 9108490071 outdated
     466 | +    std::vector<std::vector<unsigned char>> stack;
     467 | +    ScriptError serror;
     468 | +
     469 | +    BOOST_CHECK_EQUAL(Solver(txFrom.vout[8].scriptPubKey, vSolutions), TxoutType::SCRIPTHASH);
     470 | +    BOOST_CHECK(!EvalScript(stack, txToNonStd4.vin[0].scriptSig, SCRIPT_VERIFY_NONE, BaseSignatureChecker(), SigVersion::BASE, &serror));
     471 | +    BOOST_CHECK_EQUAL(serror, SCRIPT_ERR_OP_RETURN);
    


    sedited commented at 2:27 PM on October 11, 2025:

    Why is this tested here?


    ismaelsadeeq commented at 12:21 PM on October 13, 2025:

    This test is also redundant removed.

  75. in src/test/script_p2sh_tests.cpp:292 in 9108490071 outdated
     291 | @@ -292,7 +292,7 @@ BOOST_AUTO_TEST_CASE(HasNonStandardInput)
     292 |          keys.push_back(key[i].GetPubKey());
    


    sedited commented at 2:28 PM on October 11, 2025:

    In commit 910849007151cb8868f703ed759ee31f630eaea7:

    Nit: Typo in the commit message s/resin/reason/.


    ismaelsadeeq commented at 12:21 PM on October 13, 2025:

    fixed

  76. sedited commented at 2:29 PM on October 11, 2025: contributor

    Concept ACK

    There are also some drahtbot llm linter suggestions.

  77. ismaelsadeeq force-pushed on Oct 13, 2025
  78. sipa commented at 1:50 PM on October 14, 2025: member

    Concept ACK on more descriptive error messages.

    However, I'm not convinced about changing the error code string from "bad-txns-nonstandard-inputs" to be more granular. My thinking is that the error code is for software to make automated decisions based on, and the more detailed message is for humans to help debugging. In this case, it seems to me just changing the debugging suffices, or do we expect applications to deal differently with different forms of non-standardness.

    Somewhat related question: are there places where only the error code is reported, and not the more detailed message?

  79. ismaelsadeeq force-pushed on Oct 14, 2025
  80. ismaelsadeeq commented at 5:10 PM on October 14, 2025: member

    Forced push from db5c7ed388a8eb72247dff015bbf47654f27faf1 to 9eea72d3f3647197c24329b412c7fc71895e3ea2 Compare diff 4f27faf1..9eea72d

    I addressed @sipa recent #29060 (comment) by maintained the error code "bad-txns-nonstandard-inputs" and add the additional information in the debug message.

    Somewhat related question: are there places where only the error code is reported, and not the more detailed message?

    After a grep of GetRejectReason It seems the trace point only reports the reject reason in AcceptToMemoryPool.

  81. ismaelsadeeq renamed this:
    Policy: Report reason inputs are non standard
    Policy: Report debug message why inputs are non standard
    on Oct 14, 2025
  82. in src/policy/policy.cpp:252 in 0c38c58662 outdated
     257 | +            if (!EvalScript(stack, tx.vin[i].scriptSig, SCRIPT_VERIFY_NONE, BaseSignatureChecker(), SigVersion::BASE, &serror)) {
     258 | +                state.Invalid(TxValidationResult::TX_INPUTS_NOT_STANDARD, "bad-txns-nonstandard-inputs", strprintf("p2sh scriptsig malformed (input %u: %s)", i, ScriptErrorString(serror)));
     259 | +                return state;
     260 | +            }
     261 | +            if (stack.empty()) {
     262 | +                state.Invalid(TxValidationResult::TX_INPUTS_NOT_STANDARD, "bad-txns-nonstandard-inputs", strprintf("input %u scriptcheck missing", i));
    


    sipa commented at 1:32 PM on October 15, 2025:

    Nit: this seems hard to understand

    How about "input %u P2SH redeemscript missing"?


    ismaelsadeeq commented at 1:00 PM on November 28, 2025:

    Fixed, thanks.

  83. sipa commented at 1:35 PM on October 15, 2025: member

    utACK 9eea72d3f3647197c24329b412c7fc71895e3ea2

  84. DrahtBot requested review from sedited on Oct 15, 2025
  85. sedited approved
  86. sedited commented at 1:12 PM on November 27, 2025: contributor

    ACK 9eea72d3f3647197c24329b412c7fc71895e3ea2

  87. in src/policy/policy.cpp:223 in 0c38c58662 outdated
     222 |  
     223 | -    if (!CheckSigopsBIP54(tx, mapInputs)) {
     224 | -        return false;
     225 | +    auto sigops_check_result = CheckSigopsBIP54(tx, mapInputs);
     226 | +    if (!sigops_check_result.first) {
     227 | +        state.Invalid(TxValidationResult::TX_INPUTS_NOT_STANDARD, "bad-txns-nonstandard-inputs", strprintf("non-witness sigops exceed bip54 limit (sigops %u > limit %u)",
    


    maflcko commented at 2:33 PM on November 27, 2025:

    i don't think this is correct. the sigops loop breaks as soon as the limit is overshot. However, the real sigops number can be anything larger than that. I don't think there is any value in reporting a wrong value.


    ismaelsadeeq commented at 1:00 PM on November 28, 2025:

    You are right, this is fixed.

  88. ismaelsadeeq force-pushed on Nov 28, 2025
  89. ismaelsadeeq force-pushed on Nov 28, 2025
  90. DrahtBot added the label CI failed on Nov 28, 2025
  91. ismaelsadeeq force-pushed on Nov 28, 2025
  92. ismaelsadeeq commented at 1:15 PM on November 28, 2025: member

    Forced push from 9eea72d3f3647197c24329b412c7fc71895e3ea2 to 65d45acf283e7ee5ef75785b6ceaf81bac000791 9eea72d3f3...65d45acf283

    • Fixed a nit see #29060 (review)
    • Removed incomplete accounting of the sigop limit because we bailout immediately the limit is reached #29060 (review)
  93. DrahtBot removed the label CI failed on Nov 28, 2025
  94. in src/policy/policy.cpp:221 in 7fe698da3f outdated
     220 | +        return state; // Coinbases don't use vin normally
     221 |      }
     222 |  
     223 | -    if (!CheckSigopsBIP54(tx, mapInputs)) {
     224 | -        return false;
     225 | +    auto sigops_check_result = CheckSigopsBIP54(tx, mapInputs);
    


    sedited commented at 11:04 AM on December 29, 2025:

    Looks like this could just be in the if statement now.


    ismaelsadeeq commented at 1:41 PM on December 30, 2025:

    Yes, fixed.

  95. ismaelsadeeq force-pushed on Dec 30, 2025
  96. sedited approved
  97. sedited commented at 4:12 PM on December 30, 2025: contributor

    Re-ACK b4db7f3dbaffab93cbb0e43da7b482fc194a4a74

  98. DrahtBot requested review from sipa on Dec 30, 2025
  99. ismaelsadeeq requested review from maflcko on Jan 8, 2026
  100. sedited requested review from instagibbs on Feb 19, 2026
  101. in src/policy/policy.cpp:239 in e6afe3ec06
     239 |              // WITNESS_UNKNOWN failures are typically also caught with a policy
     240 |              // flag in the script interpreter, but it can be helpful to catch
     241 |              // this type of NONSTANDARD transaction earlier in transaction
     242 |              // validation.
     243 | -            return false;
     244 | +            state.Invalid(TxValidationResult::TX_INPUTS_NOT_STANDARD, "bad-txns-nonstandard-inputs", strprintf("input %u witness unknown", i));
    


    instagibbs commented at 2:36 PM on March 2, 2026:

    e6afe3ec06bd33a0f2f043d894958398ea97b7f3

    "witness unknown" might be a bit weird to someone debugging and not familiar with codbase innards. Sounds like witness data is messed up or something. How about "undefined witness program"

  102. in src/validation.cpp:898 in e6afe3ec06 outdated
     893 | @@ -897,8 +894,11 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
     894 |          return false; // state filled in by CheckTxInputs
     895 |      }
     896 |  
     897 | -    if (m_pool.m_opts.require_standard && !AreInputsStandard(tx, m_view)) {
     898 | -        return state.Invalid(TxValidationResult::TX_INPUTS_NOT_STANDARD, "bad-txns-nonstandard-inputs");
     899 | +    if (m_pool.m_opts.require_standard) {
     900 | +        state = ValidateInputsStandardness(tx, m_view);
    


    instagibbs commented at 2:41 PM on March 2, 2026:

    e6afe3ec06bd33a0f2f043d894958398ea97b7f3

    not arguing against this, but is this a new pattern vs having the function set validation state. Just noting.

  103. in src/test/script_p2sh_tests.cpp:344 in 9b19b417dc
     340 | +    // vout[7] is non-standard because it lacks sigops
     341 | +    CScript no_sigops;
     342 | +    txFrom.vout[7].scriptPubKey = no_sigops;
     343 | +    txFrom.vout[7].nValue = 1000;
     344 | +
     345 | +    // vout [8] is non-standard because it contains OP_RETURN in its scriptSig.
    


    instagibbs commented at 2:43 PM on March 2, 2026:

    9b19b417dc36b564ce77b9376b501debfd4ab7b4

    s/scriptSig/redeemScript/

  104. in doc/release-notes-29060.md:6 in b4db7f3dba
       0 | @@ -0,0 +1,9 @@
       1 | +- Logging and RPC
       2 | +
       3 | +  - Bitcoin Core now reports a debug message explaining why transaction inputs are non-standard.
       4 | +
       5 | +  - This information is now returned in the responses of the transaction-sending RPCs `submitpackage`,
       6 | +   `sendrawtransaction`, and `testmempoolaccept`, and is also logged to `debug.log` when such transactions
    


    instagibbs commented at 2:44 PM on March 2, 2026:

    b4db7f3dbaffab93cbb0e43da7b482fc194a4a74

    when mempoolrej is set*

  105. instagibbs commented at 2:45 PM on March 2, 2026: member

    concept ACK

  106. policy: update `AreInputsStandard` to return error string
    This commit renames AreInputsStandard to ValidateInputsStandardness.
    
    ValidateInputsStandardness now returns valid TxValidationState if all inputs
    (scriptSigs) use only standard transaction forms else returns invalid
    TxValidationState which states why an input is not standard.
    d2716e9e5b
  107. test: ensure `ValidateInputsStandardness` optionally returns debug string
    - Test the various input scriptSig non standardness, and ensure ValidateInputsStandardness
      Returns the reason why the input is nonstandard.
    
    Co-authored-by: Ben Woosley <ben.woosley@gmail.com>
    248c175e3d
  108. doc: add release notes d8f4e7caf0
  109. ismaelsadeeq force-pushed on Mar 2, 2026
  110. ismaelsadeeq commented at 10:41 PM on March 2, 2026: member

    Forced pushed from b4db7f3dbaffab93cbb0e43da7b482fc194a4a74 to d8f4e7caf0d25aaf4b3978d6ba698659b46473b4 compare diff

  111. DrahtBot requested review from sedited on Mar 3, 2026
  112. sedited approved
  113. sedited commented at 3:34 PM on March 3, 2026: contributor

    Re-ACK d8f4e7caf0d25aaf4b3978d6ba698659b46473b4

  114. fanquake added this to the milestone 32.0 on Mar 3, 2026
  115. sedited commented at 10:24 AM on March 11, 2026: contributor

    Can you take another look here @sipa?

  116. achow101 commented at 10:01 PM on March 19, 2026: member

    ACK d8f4e7caf0d25aaf4b3978d6ba698659b46473b4

  117. achow101 merged this on Mar 19, 2026
  118. achow101 closed this on Mar 19, 2026

  119. ismaelsadeeq deleted the branch on Mar 21, 2026

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-04-28 06:13 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me