{
 "number": 29843,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/29843",
 "title": "policy: Allow non-standard scripts with -acceptnonstdtxn=1 (test nets only)",
 "author": "ajtowns",
 "author_association": "MEMBER",
 "created_at": "2024-04-10T09:08:35Z",
 "updated_at": "2026-09-17T08:50:58Z",
 "age_days": 890,
 "draft": false,
 "labels": [
  "TX fees and policy"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "2bf3c6421f9efea9261922b6b5943d42688361d2",
 "head_ref": "202303-acceptnonstdscript",
 "head_repo": "ajtowns/bitcoin",
 "head_history": [
  {
   "t": "2024-05-16T15:53:05Z",
   "sha": "affee04f7a651345db66d6592131eb997bc1dea7"
  },
  {
   "t": "2025-03-24T06:59:19Z",
   "sha": "69416ee29ea91b3b5d69c49dead4cd763cde2127"
  },
  {
   "t": "2025-08-13T03:08:31Z",
   "sha": "0ab8d4a5ae4599e8062ff6871ad8b719d8b451d8"
  },
  {
   "t": "2025-08-28T15:44:12Z",
   "sha": "bc95fa731f0e451d118c2f0a1bfe814181d79687"
  },
  {
   "t": "2025-11-18T08:45:33Z",
   "sha": "dfe9687efa9e494b2ff7bf36237a36727225ebc2"
  },
  {
   "t": "2026-04-07T12:54:25Z",
   "sha": "dfe4cc6cc2003b077f95e30e50c47f1fa28fb2e2"
  },
  {
   "t": "2026-07-22T07:18:28Z",
   "sha": "d6eb6c97b6d29f066558bb2429476b37fd7fbbd0"
  },
  {
   "t": "2026-07-22T07:25:37Z",
   "sha": "1f5447d74a8b735be03bb684a3f354c927294c14"
  },
  {
   "t": "2026-07-22T09:23:33Z",
   "sha": "2bf3c6421f9efea9261922b6b5943d42688361d2"
  }
 ],
 "additions": 149,
 "deletions": 55,
 "changed_files": 6,
 "commit_count": 5,
 "size_bucket": "M",
 "mergeable_state": "clean",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "concept_ack": [
     {
      "login": "benthecarman",
      "url": "https://github.com/bitcoin/bitcoin/pull/29843#issuecomment-2052415425"
     },
     {
      "login": "glozow",
      "url": "https://github.com/bitcoin/bitcoin/pull/29843#issuecomment-2069138726"
     },
     {
      "login": "fjahr",
      "url": "https://github.com/bitcoin/bitcoin/pull/29843#issuecomment-2748654660"
     },
     {
      "login": "1440000bytes",
      "url": "https://github.com/bitcoin/bitcoin/pull/29843#issuecomment-2052349369"
     },
     {
      "login": "ismaelsadeeq",
      "url": "https://github.com/bitcoin/bitcoin/pull/29843#pullrequestreview-4026831915"
     }
    ],
    "approach_ack": [
     {
      "login": "sedited",
      "url": "https://github.com/bitcoin/bitcoin/pull/29843#pullrequestreview-4145739744"
     }
    ]
   },
   "conflicts": [
    {
     "number": 35662,
     "title": "script: prevent stale sighash caches across transactions",
     "author": "l0rinc"
    },
    {
     "number": 35569,
     "title": "Encapsulation for CTransaction",
     "author": "purpleKarrot"
    },
    {
     "number": 32575,
     "title": "consensus: Remove special treatment for single threaded script checking",
     "author": "fjahr"
    },
    {
     "number": 30342,
     "title": "kernel, logging: Pass Logger instances to kernel objects",
     "author": "ryanofsky"
    },
    {
     "number": 29491,
     "title": "[EXPERIMENTAL] Schnorr batch verification for blocks",
     "author": "fjahr"
    }
   ]
  }
 },
 "acks_parsed": {
  "1440000bytes": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2024-04-12T19:02:34Z",
   "stale": false
  },
  "benthecarman": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2024-04-12T19:36:07Z",
   "stale": false
  },
  "glozow": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2024-04-22T11:20:37Z",
   "stale": false
  },
  "fjahr": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2025-03-24T16:06:37Z",
   "stale": false
  },
  "ismaelsadeeq": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-03-29T14:24:05Z",
   "stale": false
  },
  "sedited": {
   "kind": "approach_ack",
   "hash": null,
   "t": "2026-05-11T19:57:34Z",
   "stale": false
  }
 },
 "acks_tally": {
  "ack": 0,
  "stale_ack": 0,
  "concept_ack": 5,
  "approach_ack": 1,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 0,
  "changes_requested": 0,
  "distinct_reviewers": [
   "1440000bytes",
   "benthecarman",
   "fjahr",
   "glozow",
   "instagibbs",
   "ismaelsadeeq",
   "luke-jr",
   "sedited",
   "twosatsmaxi"
  ]
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": false,
  "mergeable_state": "clean",
  "last_author_activity": "2026-07-22T09:32:45Z",
  "last_reviewer_activity": "2026-09-17T08:50:58Z",
  "last_reviewer": "sedited",
  "author_silent_days": 57,
  "waiting_on_author_days": 0,
  "days_since_update": 0
 },
 "refs": {
  "mentioned": [
   28334,
   29086,
   31823,
   33050
  ],
  "depends_on": [],
  "fixes": [],
  "linked_issues": [],
  "references": [
   {
    "number": 28334,
    "type": "pull",
    "state": "closed",
    "merged": false,
    "merged_at": null,
    "title": "policy: Allow non-standard scripts with -acceptnonstdtxn=1"
   },
   {
    "number": 29086,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2024-05-15",
    "title": "refactor: Simply include CTxMemPool::Options in CTxMemPool directly rather than duplicating definition"
   },
   {
    "number": 31823,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2025-12-09",
    "title": "tests: Add witness commitment if we have a witness transaction in `FullBlockTest.update_block()`"
   },
   {
    "number": 33050,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2025-08-12",
    "title": "net, validation: don't punish peers for consensus-invalid txs"
   }
  ],
  "conflicts": [
   35662,
   35569,
   32575,
   30342,
   29491
  ]
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [
  "src/test/txvalidationcache_tests.cpp",
  "src/validation.cpp",
  "test/functional/p2p_invalid_tx.py",
  "test/functional/p2p_segwit.py"
 ],
 "body": "This PR changes `-acceptnonstdtxn=1` so that it also skips the non-mandatory script checks, allowing txs that (eg) use OP_NOPx or OP_SUCCESS into the mempool. This remains only available on test nets.",
 "commits": [
  {
   "sha": "9b6342e5d295e337791984af2d42681d9c8e08bb",
   "date": "2026-07-22T08:12:37Z",
   "message": "tests: in p2p_segwit, check non-mandatory errors with -acceptnonstdtxn=0 node\n\nPrepare for updating -acceptnonstdtxn to allow txns that violate\nSTANDARD_SCRIPT_VERIFY_FLAGS but not MANDATORY_SCRIPT_VERIFY_FLAGS by\nchecking the non-mandatory flags with node that enforces standardness."
  },
  {
   "sha": "4c9a00f565e9b9da4c1fc29f90704cf2761216c2",
   "date": "2026-07-22T09:17:56Z",
   "message": "tests: test tx rejection for both -acceptnonstdtxn=0 and 1"
  },
  {
   "sha": "1eb2d8828f1b9a7407d3379edb656f280b6a3096",
   "date": "2026-07-22T09:17:59Z",
   "message": "validation: Be explicit about whether CheckInputScripts is for block/mempool\n\nAlso moves the declaration for CheckInputScripts into the header to\navoid redeclaring it."
  },
  {
   "sha": "daea759c1893dec8de24ef53264263e80b6a3f93",
   "date": "2026-07-22T09:17:59Z",
   "message": "validation: Check only (potential) block script flags when -acceptnonstdtxn is set"
  },
  {
   "sha": "2bf3c6421f9efea9261922b6b5943d42688361d2",
   "date": "2026-07-22T09:17:59Z",
   "message": "validation: Ensure STANDARD_SCRIPT_VERIFY_FLAGS is a superset of GetBlockScriptFlags"
  }
 ],
 "timeline": [
  {
   "t": "2024-04-10T09:15:36Z",
   "kind": "comment",
   "who": "ajtowns",
   "assoc": "MEMBER",
   "text": "Previous discussion in #28334.\n\nIf you enable this feature as a non-mining node, you risk having txs pinned in your mempool that conflict or double-spend standard txs in the normal mempool, which may cause problems with applications or layer-2 protocols.\n\nIf you enable this feature as a mining node, you have a small risk of ending up with invalid txs remaining in your mempool if a non-standard tx is accepted in your mempool and remains in there during a soft-fork activation that renders the tx invalid. At that point if that tx is included in blocks you generate, they will be invalid. (EDIT: Restarting your node after activation occurs will dump the mempool to disk and reload it under the new consensus rules, which will correct such problems, however)\n\nFor these reasons this feature continues to only be available on test nets, not mainnet.\n\nHaving this be available in mainline bitcoin may make it easier to observe test transactions using proposed new soft-fork features (eg OP_CAT or SIGHASH_APO is enabled via the inquisition client which considers those txs standard, but the mainline client will consider them non-standard and has no option to accept them). [discussion link](https://twitter.com/glozow/status/1633134573356740608)\n\nEDIT 2025/03/25: There are more or less three reasons why things are marked non-standard:\n\n * some are upgrade hooks (future witness versions, future taproot versions, future tapscript pubkey types, annex usage, NOP and OP_SUCCESS opcodes, tx versions greater than 3, currently unused scriptPubKey types)\n  * others are DoS mitigations (p2sh sigop limit, dangerous scriptPubKey types, ...)\n  * others are just to make things nicer for users/miners (tx weight/sigop limit to make packing blocks easier, various things to reduce malleability, limit wasteful utxos, etc...)\n\nPrior to this PR, `-acceptnonstdtxn` enables txs that include features from each of these categories (eg for upgrades, txs with annex data via IsWitnessStandard being disabled, higher tx versions via IsStandardTx being disabled; for DoS, excessive p2sh sigops via IsStandardTx being disabled; for user friendliness, creation of ephemeral dust due to ephemeral checks being disabled). This PR likewise enables features from each category (NOPS, OP_SUCCESS, tapscript pubkey type, new taproot versions are upgrade features; CONST_SCRIPTCODE is a DoS mitigation; LOW_S, MINIMALIF, CLEANSTACK etc are user friendliness).\n\nSeparating these into different categories that can be controlled separately might be worthwhile, but doesn't seem necessary for this PR."
  },
  {
   "t": "2024-04-12T19:02:34Z",
   "kind": "comment",
   "who": "1440000bytes",
   "assoc": "CONTRIBUTOR",
   "text": "Concept ACK"
  },
  {
   "t": "2024-04-12T19:36:07Z",
   "kind": "comment",
   "who": "benthecarman",
   "assoc": "CONTRIBUTOR",
   "text": "Concept ACK this would be nice for testing soft forks on test networks"
  },
  {
   "t": "2024-04-21T01:44:21Z",
   "kind": "comment",
   "who": "luke-jr",
   "assoc": "CONTRIBUTOR",
   "text": "Seems like this goes beyond the expected behaviour. Maybe make it `=2` at least?"
  },
  {
   "t": "2024-04-22T11:20:37Z",
   "kind": "comment",
   "who": "glozow",
   "assoc": "CONTRIBUTOR",
   "text": "concept ACK fwiw"
  },
  {
   "t": "2024-04-22T13:53:53Z",
   "kind": "comment",
   "who": "instagibbs",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nSince these are test-network-only changes, are we required to preserve current behavior?"
  },
  {
   "t": "2024-04-25T15:54:54Z",
   "kind": "comment",
   "who": "twosatsmaxi",
   "assoc": "NONE",
   "text": "Following."
  },
  {
   "t": "2024-05-16T15:53:05Z",
   "kind": "force_push",
   "who": "ajtowns",
   "commit": "affee04f7a651345db66d6592131eb997bc1dea7"
  },
  {
   "t": "2024-05-16T15:54:34Z",
   "kind": "comment",
   "who": "ajtowns",
   "assoc": "MEMBER",
   "text": "Bumped past #29086"
  },
  {
   "t": "2024-06-08T01:43:39Z",
   "kind": "comment",
   "who": "luke-jr",
   "assoc": "CONTRIBUTOR",
   "text": "[quoted text omitted]\n\nWhether you are required to or not, it is the correct thing to do. This option originated in Knots where it has always been available on mainnet, and expected to not enable upgradable opcodes/etc."
  },
  {
   "t": "2025-03-24T06:59:19Z",
   "kind": "force_push",
   "who": "ajtowns",
   "commit": "69416ee29ea91b3b5d69c49dead4cd763cde2127"
  },
  {
   "t": "2025-03-24T16:06:37Z",
   "kind": "comment",
   "who": "fjahr",
   "assoc": "MEMBER",
   "text": "Concept ACK"
  },
  {
   "t": "2025-08-13T03:08:31Z",
   "kind": "force_push",
   "who": "ajtowns",
   "commit": "0ab8d4a5ae4599e8062ff6871ad8b719d8b451d8"
  },
  {
   "t": "2025-08-28T15:44:12Z",
   "kind": "force_push",
   "who": "ajtowns",
   "commit": "bc95fa731f0e451d118c2f0a1bfe814181d79687"
  },
  {
   "t": "2025-11-18T08:45:33Z",
   "kind": "force_push",
   "who": "ajtowns",
   "commit": "dfe9687efa9e494b2ff7bf36237a36727225ebc2"
  },
  {
   "t": "2026-03-19T12:07:36Z",
   "kind": "comment",
   "who": "sedited",
   "assoc": "MEMBER",
   "text": "@instagibbs, @ismaelsadeeq can you take a look here?"
  },
  {
   "t": "2026-03-29T14:24:05Z",
   "kind": "review",
   "who": "ismaelsadeeq",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "dfe9687efa9e494b2ff7bf36237a36727225ebc2",
   "text": "Concept ACK for the advantages stated.\n\n1. I think this also makes it clear that a transaction input script must violate the mandatory script verify flags to be a consensus failure; other flags failure are just standardness.\n\nI think the second commit adds some brittleness to the `CheckInputScripts` function; it now needs to know whether we are in block validation or individual transaction validation.\nI think that should not be the case; the function should be as generic as possible, and each caller should know that context and hence use it as necessary.\nSo `CheckInputScripts` should just return a validation state indicating whether consensus or standardness is violated; callers should add the detail of whether it is block validation or mempool acceptance validation that triggered the failure.\n\nThis makes the testing more straightforward, I think, and is better than the current smoke test.\n\nSee diff below.\n\n2. I think after this PR, in non-block input script validation path, the result enum after a consensus input script failure is `TX_NOT_STANDARD`, which is incorrect; it should be `TX_CONSENSUS`.\n\nThe diff below should fix this as well.\n\ndiff\n\n```diff\ndiff --git a/src/test/txvalidationcache_tests.cpp b/src/test/txvalidationcache_tests.cpp\nindex c8cfbadfa9..9edad1d020 100644\n--- a/src/test/txvalidationcache_tests.cpp\n+++ b/src/test/txvalidationcache_tests.cpp\n@@ -20,8 +20,8 @@ struct Dersig100Setup : public TestChain100Setup {\n         : TestChain100Setup{ChainType::REGTEST, {.extra_args = {\"-testactivationheight=dersig@102\"}}} {}\n };\n\n-bool CheckInputScripts(const CTransaction& tx, TxValidationState& state,\n-                       const CCoinsViewCache& inputs, script_verify_flags flags, bool check_for_block,\n+TxValidationState CheckInputScripts(const CTransaction& tx,\n+                       const CCoinsViewCache& inputs, script_verify_flags flags,\n                        bool cacheSigStore,\n                        bool cacheFullScriptStore, PrecomputedTransactionData& txdata,\n                        ValidationCache& validation_cache,\n@@ -110,6 +110,21 @@ BOOST_FIXTURE_TEST_CASE(tx_mempool_block_doublespend, Dersig100Setup)\n     BOOST_CHECK_EQUAL(m_node.mempool->size(), 0U);\n }\n\n+void ValidTxValidationState(const TxValidationState& ret)\n+{\n+    BOOST_CHECK_EQUAL(ret.GetResult(), TxValidationResult::TX_RESULT_UNSET);\n+    BOOST_CHECK(ret.GetRejectReason().empty());\n+    BOOST_CHECK(ret.GetDebugMessage().empty());\n+}\n+\n+void InvalidTxValidationState(const TxValidationState& ret, TxValidationResult result, const std::string& reject_reason, const std::string& debug_message)\n+{\n+    BOOST_CHECK(!ret.IsValid());\n+    BOOST_CHECK_EQUAL(ret.GetResult(), result);\n+    BOOST_CHECK_EQUAL(ret.GetRejectReason(), reject_reason);\n+    BOOST_CHECK_EQUAL(ret.GetDebugMessage(), debug_message);\n+}\n+\n // Run CheckInputScripts (using CoinsTip()) on the given transaction, for all script\n // flags.  Test that CheckInputScripts passes for all flags that don't overlap with\n // the failing_flags argument, but otherwise fails.\n@@ -128,8 +143,6 @@ static void ValidateCheckInputsForAllFlags(const CTransaction &tx, script_verify\n     FastRandomContext insecure_rand(true);\n\n     for (int count = 0; count < 10000; ++count) {\n-        TxValidationState state;\n-\n         // Randomly selects flag combinations\n         script_verify_flags test_flags = script_verify_flags::from_int(insecure_rand.randrange(MAX_SCRIPT_VERIFY_FLAGS));\n\n@@ -143,23 +156,35 @@ static void ValidateCheckInputsForAllFlags(const CTransaction &tx, script_verify\n             // WITNESS requires P2SH\n             test_flags |= SCRIPT_VERIFY_P2SH;\n         }\n-        bool ret = CheckInputScripts(tx, state, &active_coins_tip, test_flags, /*check_for_block=*/false, true, add_to_cache, txdata, validation_cache, nullptr);\n+        auto ret = CheckInputScripts(tx, &active_coins_tip, test_flags, true, add_to_cache, txdata, validation_cache, nullptr);\n         // CheckInputScripts should succeed iff test_flags doesn't intersect with\n         // failing_flags\n         bool expected_return_value = !(test_flags & failing_flags);\n-        BOOST_CHECK_EQUAL(ret, expected_return_value);\n+        BOOST_CHECK_EQUAL(ret.IsValid(), expected_return_value);\n+        if (!ret.IsValid()) {\n+            if (test_flags & STANDARD_NOT_MANDATORY_VERIFY_FLAGS) {\n+                BOOST_CHECK_EQUAL(ret.GetResult(), TxValidationResult::TX_NOT_STANDARD);\n+            } else {\n+                BOOST_CHECK_EQUAL(ret.GetResult(), TxValidationResult::TX_CONSENSUS);\n+            }\n+            BOOST_CHECK(!ret.GetRejectReason().empty());\n+            BOOST_CHECK(!ret.GetDebugMessage().empty());\n+        } else {\n+           ValidTxValidationState(ret);\n+        }\n\n         // Test the caching\n-        if (ret && add_to_cache) {\n+        if (ret.IsValid() && add_to_cache) {\n             // Check that we get a cache hit if the tx was valid\n             std::vector<CScriptCheck> scriptchecks;\n-            BOOST_CHECK(CheckInputScripts(tx, state, &active_coins_tip, test_flags, /*check_for_block=*/false, true, add_to_cache, txdata, validation_cache, &scriptchecks));\n+            BOOST_CHECK(CheckInputScripts(tx, &active_coins_tip, test_flags, true, add_to_cache, txdata, validation_cache, &scriptchecks).IsValid());\n             BOOST_CHECK(scriptchecks.empty());\n         } else {\n             // Check that we get script executions to check, if the transaction\n             // was invalid, or we didn't add to cache.\n             std::vector<CScriptCheck> scriptchecks;\n-            BOOST_CHECK(CheckInputScripts(tx, state, &active_coins_tip, test_flags, /*check_for_block=*/false, true, add_to_cache, txdata, validation_cache, &scriptchecks));\n+            auto cached_ret{CheckInputScripts(tx, &active_coins_tip, test_flags, true, add_to_cache, txdata, validation_cache, &scriptchecks)};\n+            ValidTxValidationState(cached_ret);\n             BOOST_CHECK_EQUAL(scriptchecks.size(), tx.vin.size());\n         }\n     }\n@@ -214,16 +239,16 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, Dersig100Setup)\n     {\n         LOCK(cs_main);\n\n-        TxValidationState state;\n         PrecomputedTransactionData ptd_spend_tx;\n-\n-        BOOST_CHECK(!CheckInputScripts(CTransaction(spend_tx), state, &m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_DERSIG, /*check_for_block=*/false, true, true, ptd_spend_tx, m_node.chainman->m_validation_cache, nullptr));\n+        auto ret = CheckInputScripts(CTransaction(spend_tx), &m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_DERSIG, true, true, ptd_spend_tx, m_node.chainman->m_validation_cache, nullptr);\n+        InvalidTxValidationState(ret, TxValidationResult::TX_CONSENSUS, \"Non-canonical DER signature\", strprintf(\"input 0 of %s (wtxid %s), spending %s:0\", spend_tx.GetHash().ToString(), CTransaction(spend_tx).GetWitnessHash().ToString(), m_coinbase_txns[0]->GetHash().ToString()));\n\n         // If we call again asking for scriptchecks (as happens in\n         // ConnectBlock), we should add a script check object for this -- we're\n         // not caching invalidity (if that changes, delete this test case).\n         std::vector<CScriptCheck> scriptchecks;\n-        BOOST_CHECK(CheckInputScripts(CTransaction(spend_tx), state, &m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_DERSIG, /*check_for_block=*/false, true, true, ptd_spend_tx, m_node.chainman->m_validation_cache, &scriptchecks));\n+        auto non_cached_ret{CheckInputScripts(CTransaction(spend_tx), &m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_DERSIG, true, true, ptd_spend_tx, m_node.chainman->m_validation_cache, &scriptchecks)};\n+        ValidTxValidationState(non_cached_ret);\n         BOOST_CHECK_EQUAL(scriptchecks.size(), 1U);\n\n         // Test that CheckInputScripts returns true iff DERSIG-enforcing flags are\n@@ -283,9 +308,9 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, Dersig100Setup)\n\n         // Make it valid, and check again\n         invalid_with_cltv_tx.vin[0].scriptSig = CScript() << vchSig << 100;\n-        TxValidationState state;\n         PrecomputedTransactionData txdata;\n-        BOOST_CHECK(CheckInputScripts(CTransaction(invalid_with_cltv_tx), state, m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY, /*check_for_block=*/false, true, true, txdata, m_node.chainman->m_validation_cache, nullptr));\n+        auto valid_ret{CheckInputScripts(CTransaction(invalid_with_cltv_tx), m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY, true, true, txdata, m_node.chainman->m_validation_cache, nullptr)};\n+        ValidTxValidationState(valid_ret);\n     }\n\n     // TEST CHECKSEQUENCEVERIFY\n@@ -311,9 +336,9 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, Dersig100Setup)\n\n         // Make it valid, and check again\n         invalid_with_csv_tx.vin[0].scriptSig = CScript() << vchSig << 100;\n-        TxValidationState state;\n         PrecomputedTransactionData txdata;\n-        BOOST_CHECK(CheckInputScripts(CTransaction(invalid_with_csv_tx), state, &m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_CHECKSEQUENCEVERIFY, /*check_for_block=*/false, true, true, txdata, m_node.chainman->m_validation_cache, nullptr));\n+        auto valid_ret{CheckInputScripts(CTransaction(invalid_with_csv_tx), &m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_CHECKSEQUENCEVERIFY, true, true, txdata, m_node.chainman->m_validation_cache, nullptr)};\n+        ValidTxValidationState(valid_ret);\n     }\n\n     // TODO: add tests for remaining script flags\n@@ -372,15 +397,16 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, Dersig100Setup)\n         // Invalidate vin[1]\n         tx.vin[1].scriptWitness.SetNull();\n\n-        TxValidationState state;\n         PrecomputedTransactionData txdata;\n         // This transaction is now invalid under segwit, because of the second input.\n-        BOOST_CHECK(!CheckInputScripts(CTransaction(tx), state, &m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, /*check_for_block=*/false, true, true, txdata, m_node.chainman->m_validation_cache, nullptr));\n+        auto ret = CheckInputScripts(CTransaction(tx), &m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, true, true, txdata, m_node.chainman->m_validation_cache, nullptr);\n+        InvalidTxValidationState(ret, TxValidationResult::TX_CONSENSUS, \"Witness program hash mismatch\", strprintf(\"input 1 of %s (wtxid %s), spending %s:1\", tx.GetHash().ToString(), CTransaction(tx).GetWitnessHash().ToString(), spend_tx.GetHash().ToString()));\n\n         std::vector<CScriptCheck> scriptchecks;\n         // Make sure this transaction was not cached (ie because the first\n         // input was valid)\n-        BOOST_CHECK(CheckInputScripts(CTransaction(tx), state, &m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, /*check_for_block=*/false, true, true, txdata, m_node.chainman->m_validation_cache, &scriptchecks));\n+        auto valid_ret{CheckInputScripts(CTransaction(tx), &m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, true, true, txdata, m_node.chainman->m_validation_cache, &scriptchecks)};\n+        ValidTxValidationState(valid_ret);\n         // Should get 2 script checks back -- caching is on a whole-transaction basis.\n         BOOST_CHECK_EQUAL(scriptchecks.size(), 2U);\n     }\ndiff --git a/src/validation.cpp b/src/validation.cpp\nindex 5d95edf4db..6c3644f9a5 100644\n--- a/src/validation.cpp\n+++ b/src/validation.cpp\n@@ -138,8 +138,8 @@ const CBlockIndex* Chainstate::FindForkInGlobalIndex(const CBlockLocator& locato\n     return m_chain.Genesis();\n }\n\n-bool CheckInputScripts(const CTransaction& tx, TxValidationState& state,\n-                       const CCoinsViewCache& inputs, script_verify_flags flags, bool check_for_block,\n+TxValidationState CheckInputScripts(const CTransaction& tx,\n+                       const CCoinsViewCache& inputs, script_verify_flags flags,\n                        bool cacheSigStore,\n                        bool cacheFullScriptStore, PrecomputedTransactionData& txdata,\n                        ValidationCache& validation_cache,\n@@ -429,7 +429,11 @@ static bool CheckInputsFromMempoolAndCache(const CTransaction& tx, TxValidationS\n     }\n\n     // Call CheckInputScripts() to cache signature and script validity against current tip consensus rules.\n-    return CheckInputScripts(tx, state, view, flags, /*check_for_block=*/false, /* cacheSigStore= */ true, /* cacheFullScriptStore= */ true, txdata, validation_cache);\n+    auto check_state = CheckInputScripts(tx, view, flags, /* cacheSigStore= */ true, /* cacheFullScriptStore= */ true, txdata, validation_cache);\n+    if (!check_state.IsValid()) {\n+        return state.Invalid(check_state.GetResult(), strprintf(\"mempool-script-verify-flag-failed (%s)\", check_state.GetRejectReason()), check_state.GetDebugMessage());\n+    }\n+    return true;\n }\n\n namespace {\n@@ -1253,13 +1257,15 @@ bool MemPoolAccept::PolicyScriptChecks(const ATMPArgs& args, Workspace& ws)\n\n     // Check input scripts and signatures.\n     // This is done last to help prevent CPU exhaustion denial-of-service attacks.\n-    if (!CheckInputScripts(tx, state, m_view, scriptVerifyFlags, /*check_for_block=*/false, true, false, ws.m_precomputed_txdata, GetValidationCache())) {\n+    TxValidationState check_state = CheckInputScripts(tx, m_view, scriptVerifyFlags, true, false, ws.m_precomputed_txdata, GetValidationCache());\n+    if (!check_state.IsValid()) {\n+        state.Invalid(check_state.GetResult(), strprintf(\"mempool-script-verify-flag-failed (%s)\", check_state.GetRejectReason()), check_state.GetDebugMessage());\n         // Detect a failure due to a missing witness so that p2p code can handle rejection caching appropriately.\n         if (!tx.HasWitness() && SpendsNonAnchorWitnessProg(tx, m_view)) {\n             state.Invalid(TxValidationResult::TX_WITNESS_STRIPPED,\n                     state.GetRejectReason(), state.GetDebugMessage());\n         }\n-        return false; // state filled in by CheckInputScripts\n+        return false;\n     }\n\n     return true;\n@@ -2139,18 +2145,15 @@ ValidationCache::ValidationCache(const size_t script_execution_cache_bytes, cons\n  *\n  * Non-static (and redeclared) in src/test/txvalidationcache_tests.cpp\n  */\n-bool CheckInputScripts(const CTransaction& tx, TxValidationState& state,\n-                       const CCoinsViewCache& inputs, script_verify_flags flags, bool check_for_block,\n+TxValidationState CheckInputScripts(const CTransaction& tx,\n+                       const CCoinsViewCache& inputs, script_verify_flags flags,\n                        bool cacheSigStore,\n                        bool cacheFullScriptStore, PrecomputedTransactionData& txdata,\n                        ValidationCache& validation_cache,\n                        std::vector<CScriptCheck>* pvChecks)\n {\n-    if (tx.IsCoinBase()) return true;\n-\n-    if (flags & STANDARD_NOT_MANDATORY_VERIFY_FLAGS) {\n-        assert(!check_for_block);\n-    }\n+    TxValidationState state;\n+    if (tx.IsCoinBase()) return state;\n\n     if (pvChecks) {\n         pvChecks->reserve(tx.vin.size());\n@@ -2166,7 +2169,7 @@ bool CheckInputScripts(const CTransaction& tx, TxValidationState& state,\n     hasher.Write(UCharCast(tx.GetWitnessHash().begin()), 32).Write((unsigned char*)&flags, sizeof(flags)).Finalize(hashCacheEntry.begin());\n     AssertLockHeld(cs_main); //TODO: Remove this requirement by making CuckooCache not require external locks\n     if (validation_cache.m_script_execution_cache.contains(hashCacheEntry, !cacheFullScriptStore)) {\n-        return true;\n+        return state;\n     }\n\n     if (!txdata.m_spent_outputs_ready) {\n@@ -2202,11 +2205,9 @@ bool CheckInputScripts(const CTransaction& tx, TxValidationState& state,\n             // non-standard DER encodings or non-null dummy\n             // arguments) or due to new consensus rules introduced in\n             // soft forks.\n-            if (!check_for_block) {\n-                return state.Invalid(TxValidationResult::TX_NOT_STANDARD, strprintf(\"mempool-script-verify-flag-failed (%s)\", ScriptErrorString(result->first)), result->second);\n-            } else {\n-                return state.Invalid(TxValidationResult::TX_CONSENSUS, strprintf(\"block-script-verify-flag-failed (%s)\", ScriptErrorString(result->first)), result->second);\n-            }\n+            auto validation_result = flags & STANDARD_NOT_MANDATORY_VERIFY_FLAGS ? TxValidationResult::TX_NOT_STANDARD : TxValidationResult::TX_CONSENSUS;\n+            state.Invalid(validation_result, ScriptErrorString(result->first), result->second);\n+            return state;\n         }\n     }\n\n@@ -2216,7 +2217,7 @@ bool CheckInputScripts(const CTransaction& tx, TxValidationState& state,\n         validation_cache.m_script_execution_cache.insert(hashCacheEntry);\n     }\n\n-    return true;\n+    return state;\n }\n\n bool FatalError(Notifications& notifications, BlockValidationState& state, const bilingual_str& message)\n@@ -2663,21 +2664,20 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state,\n         if (!tx.IsCoinBase() && fScriptChecks)\n         {\n             bool fCacheResults = fJustCheck; /* Don't cache results if we're actually connecting blocks (still consult the cache, though) */\n-            bool tx_ok;\n             TxValidationState tx_state;\n             // If CheckInputScripts is called with a pointer to a checks vector, the resulting checks are appended to it. In that case\n             // they need to be added to control which runs them asynchronously. Otherwise, CheckInputScripts runs the checks before returning.\n             if (control) {\n                 std::vector<CScriptCheck> vChecks;\n-                tx_ok = CheckInputScripts(tx, tx_state, view, flags, /*check_for_block=*/true, fCacheResults, fCacheResults, txsdata[i], m_chainman.m_validation_cache, &vChecks);\n-                if (tx_ok) control->Add(std::move(vChecks));\n+                tx_state = CheckInputScripts(tx, view, flags, fCacheResults, fCacheResults, txsdata[i], m_chainman.m_validation_cache, &vChecks);\n+                if (tx_state.IsValid()) control->Add(std::move(vChecks));\n             } else {\n-                tx_ok = CheckInputScripts(tx, tx_state, view, flags, /*check_for_block=*/true, fCacheResults, fCacheResults, txsdata[i], m_chainman.m_validation_cache);\n+                tx_state = CheckInputScripts(tx, view, flags, fCacheResults, fCacheResults, txsdata[i], m_chainman.m_validation_cache);\n             }\n-            if (!tx_ok) {\n+            if (!tx_state.IsValid()) {\n                 // Any transaction validation failure in ConnectBlock is a block consensus failure\n                 state.Invalid(BlockValidationResult::BLOCK_CONSENSUS,\n-                              tx_state.GetRejectReason(), tx_state.GetDebugMessage());\n+                              strprintf(\"block-script-verify-flag-failed (%s)\", tx_state.GetRejectReason()), tx_state.GetDebugMessage());\n                 break;\n             }\n         }\n\n```"
  },
  {
   "t": "2026-03-30T05:34:18Z",
   "kind": "comment",
   "who": "ajtowns",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nThis form of automated check isn't compatible with soft-forks: we don't update `MANDATORY_SCRIPT_VERIFY_FLAGS` until a soft-fork has been active for some time, but do update `STANDARD_SCRIPT_VERIFY_FLAGS` so we don't have invalid txs per the soft-fork included in the mempool. If/when the soft-fork does activate it will be included in the block flags, and at that point all block checks will be incorrectly reported as standardness failures rather consensus failures. Putting all potential soft forks into the `MANDATORY_SCRIPT_VERIFY_FLAGS` would be an option, but that would make this feature less useful -- you couldn't use it to opt-out of rules introduced in supported-but-not-yet-activated soft-forks.\n\nI think it's a lot more-straightforward and less fragile for the caller to indicate whether they're trying to check for standardness or consensus; that's at least easily reviewed at the call-site."
  },
  {
   "t": "2026-03-30T05:40:48Z",
   "kind": "comment",
   "who": "ajtowns",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nWe'll already return `TX_NOT_STANDARD` for some consensus invalid txs (when we find a policy violation prior to finding the consensus violation), and since #33050 won't rerun the checks to conclusively decide whether it's violating consensus or just policy. So I don't think this is a meaningful change, though perhaps it would help to change the description in consensus/validation.h or similar?"
  },
  {
   "t": "2026-03-30T14:17:49Z",
   "kind": "comment",
   "who": "ismaelsadeeq",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nThis is a good point, and I have not seen this before. Thanks for pointing it out.\n\n[quoted text omitted]\nI won't suggest this either; it seems like a hack to me.\n\n[quoted text omitted]\nHmm. I would prefer to not have to review that `check_for_block` is set correctly at every call site at all.\nMy question is: why does `CheckInputScripts` need to set the validation state? It doesn't seem necessary to me.\n\nI attempted to simply forward the raw `ScriptError` to callers and let them construct the state themselves.\nThis approach fixes the underlying issue, I think? Block validation failures are always consensus failures, so we should unconditionally set that. The mempool validation path  should owns the `TX_NOT_STANDARD` vs `TX_CONSENSUS` decision where it actually belongs, and we no longer need to document the behavior where `CheckInputScripts` can return `TX_NOT_STANDARD` for what is actually a consensus failure and block validation needs to update that.\n\nWdyt?\n\ndiff\n\n```diff\n\ndiff --git a/src/test/txvalidationcache_tests.cpp b/src/test/txvalidationcache_tests.cpp\nindex c8cfbadfa9..7ccc8ee661 100644\n--- a/src/test/txvalidationcache_tests.cpp\n+++ b/src/test/txvalidationcache_tests.cpp\n@@ -20,10 +20,9 @@ struct Dersig100Setup : public TestChain100Setup {\n         : TestChain100Setup{ChainType::REGTEST, {.extra_args = {\"-testactivationheight=dersig@102\"}}} {}\n };\n\n-bool CheckInputScripts(const CTransaction& tx, TxValidationState& state,\n-                       const CCoinsViewCache& inputs, script_verify_flags flags, bool check_for_block,\n-                       bool cacheSigStore,\n-                       bool cacheFullScriptStore, PrecomputedTransactionData& txdata,\n+std::optional<ScriptCheckError> CheckInputScripts(const CTransaction& tx,\n+                       const CCoinsViewCache& inputs, script_verify_flags flags,\n+                       bool cacheSigStore, bool cacheFullScriptStore, PrecomputedTransactionData& txdata,\n                        ValidationCache& validation_cache,\n                        std::vector<CScriptCheck>* pvChecks) EXCLUSIVE_LOCKS_REQUIRED(cs_main);\n\n@@ -128,8 +127,6 @@ static void ValidateCheckInputsForAllFlags(const CTransaction &tx, script_verify\n     FastRandomContext insecure_rand(true);\n\n     for (int count = 0; count < 10000; ++count) {\n-        TxValidationState state;\n-\n         // Randomly selects flag combinations\n         script_verify_flags test_flags = script_verify_flags::from_int(insecure_rand.randrange(MAX_SCRIPT_VERIFY_FLAGS));\n\n@@ -143,23 +140,26 @@ static void ValidateCheckInputsForAllFlags(const CTransaction &tx, script_verify\n             // WITNESS requires P2SH\n             test_flags |= SCRIPT_VERIFY_P2SH;\n         }\n-        bool ret = CheckInputScripts(tx, state, &active_coins_tip, test_flags, /*check_for_block=*/false, true, add_to_cache, txdata, validation_cache, nullp\n+        auto ret = CheckInputScripts(tx, &active_coins_tip, test_flags, true, add_to_cache, txdata, validation_cache, nullptr);\n         // CheckInputScripts should succeed iff test_flags doesn't intersect with\n         // failing_flags\n         bool expected_return_value = !(test_flags & failing_flags);\n-        BOOST_CHECK_EQUAL(ret, expected_return_value);\n-\n+        BOOST_CHECK_EQUAL(!ret.has_value(), expected_return_value);\n+        if (ret.has_value()) {\n+            BOOST_CHECK(ret->error != SCRIPT_ERR_OK);\n+            BOOST_CHECK(!ret->debug_message.empty());\n+        }\n         // Test the caching\n-        if (ret && add_to_cache) {\n+        if (!ret.has_value() && add_to_cache) {\n             // Check that we get a cache hit if the tx was valid\n             std::vector<CScriptCheck> scriptchecks;\n-            BOOST_CHECK(CheckInputScripts(tx, state, &active_coins_tip, test_flags, /*check_for_block=*/false, true, add_to_cache, txdata, validation_cache,\n+            BOOST_CHECK(!CheckInputScripts(tx, &active_coins_tip, test_flags, true, add_to_cache, txdata, validation_cache, &scriptchecks).has_value());\n             BOOST_CHECK(scriptchecks.empty());\n         } else {\n             // Check that we get script executions to check, if the transaction\n             // was invalid, or we didn't add to cache.\n             std::vector<CScriptCheck> scriptchecks;\n-            BOOST_CHECK(CheckInputScripts(tx, state, &active_coins_tip, test_flags, /*check_for_block=*/false, true, add_to_cache, txdata, validation_cache,\n+            BOOST_CHECK(!CheckInputScripts(tx, &active_coins_tip, test_flags, true, add_to_cache, txdata, validation_cache, &scriptchecks).has_value());\n             BOOST_CHECK_EQUAL(scriptchecks.size(), tx.vin.size());\n         }\n     }\n@@ -214,16 +214,18 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, Dersig100Setup)\n     {\n         LOCK(cs_main);\n\n-        TxValidationState state;\n         PrecomputedTransactionData ptd_spend_tx;\n-\n-        BOOST_CHECK(!CheckInputScripts(CTransaction(spend_tx), state, &m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_DER\n+        auto ret = CheckInputScripts(CTransaction(spend_tx), &m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_DERSIG, true\n+        BOOST_CHECK(ret.has_value());\n+        BOOST_CHECK(ret->error == SCRIPT_ERR_SIG_DER);\n+        BOOST_CHECK_EQUAL(ret->debug_message, strprintf(\"input 0 of %s (wtxid %s), spending %s:0\", spend_tx.GetHash().ToString(), CTransaction(spend_tx).GetW\n\n         // If we call again asking for scriptchecks (as happens in\n         // ConnectBlock), we should add a script check object for this -- we're\n         // not caching invalidity (if that changes, delete this test case).\n         std::vector<CScriptCheck> scriptchecks;\n-        BOOST_CHECK(CheckInputScripts(CTransaction(spend_tx), state, &m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_DERS\n+        auto non_cached_ret{CheckInputScripts(CTransaction(spend_tx), &m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_DER\n+        BOOST_CHECK(!non_cached_ret.has_value());\n         BOOST_CHECK_EQUAL(scriptchecks.size(), 1U);\n\n         // Test that CheckInputScripts returns true iff DERSIG-enforcing flags are\n@@ -283,9 +285,8 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, Dersig100Setup)\n\n         // Make it valid, and check again\n         invalid_with_cltv_tx.vin[0].scriptSig = CScript() << vchSig << 100;\n-        TxValidationState state;\n         PrecomputedTransactionData txdata;\n-        BOOST_CHECK(CheckInputScripts(CTransaction(invalid_with_cltv_tx), state, m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_CHECKLOCKTIMEV\n+        BOOST_CHECK(!CheckInputScripts(CTransaction(invalid_with_cltv_tx), m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY,\n     }\n\n     // TEST CHECKSEQUENCEVERIFY\n@@ -311,9 +312,8 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, Dersig100Setup)\n\n         // Make it valid, and check again\n         invalid_with_csv_tx.vin[0].scriptSig = CScript() << vchSig << 100;\n-        TxValidationState state;\n         PrecomputedTransactionData txdata;\n-        BOOST_CHECK(CheckInputScripts(CTransaction(invalid_with_csv_tx), state, &m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_CHECKSEQUENCEV\n+        BOOST_CHECK(!CheckInputScripts(CTransaction(invalid_with_csv_tx), &m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_CHECKSEQUENCEVERIFY,\n     }\n\n     // TODO: add tests for remaining script flags\n@@ -372,15 +372,17 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, Dersig100Setup)\n         // Invalidate vin[1]\n         tx.vin[1].scriptWitness.SetNull();\n\n-        TxValidationState state;\n         PrecomputedTransactionData txdata;\n         // This transaction is now invalid under segwit, because of the second input.\n-        BOOST_CHECK(!CheckInputScripts(CTransaction(tx), state, &m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS,\n+        auto ret = CheckInputScripts(CTransaction(tx), &m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, true, tru\n+        BOOST_CHECK(ret.has_value());\n+        BOOST_CHECK(ret->error == SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH);\n+        BOOST_CHECK_EQUAL(ret->debug_message, strprintf(\"input 1 of %s (wtxid %s), spending %s:1\", tx.GetHash().ToString(), CTransaction(tx).GetWitnessHash()\n\n         std::vector<CScriptCheck> scriptchecks;\n         // Make sure this transaction was not cached (ie because the first\n         // input was valid)\n-        BOOST_CHECK(CheckInputScripts(CTransaction(tx), state, &m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, /\n+        BOOST_CHECK(!CheckInputScripts(CTransaction(tx), &m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, true, t\n         // Should get 2 script checks back -- caching is on a whole-transaction basis.\n         BOOST_CHECK_EQUAL(scriptchecks.size(), 2U);\n     }\ndiff --git a/src/validation.cpp b/src/validation.cpp\nindex 5d95edf4db..005702d09b 100644\n--- a/src/validation.cpp\n+++ b/src/validation.cpp\n@@ -138,8 +138,8 @@ const CBlockIndex* Chainstate::FindForkInGlobalIndex(const CBlockLocator& locato\n     return m_chain.Genesis();\n }\n\n-bool CheckInputScripts(const CTransaction& tx, TxValidationState& state,\n-                       const CCoinsViewCache& inputs, script_verify_flags flags, bool check_for_block,\n+std::optional<ScriptCheckError> CheckInputScripts(const CTransaction& tx,\n+                       const CCoinsViewCache& inputs, script_verify_flags flags,\n                        bool cacheSigStore,\n                        bool cacheFullScriptStore, PrecomputedTransactionData& txdata,\n                        ValidationCache& validation_cache,\n@@ -429,7 +429,12 @@ static bool CheckInputsFromMempoolAndCache(const CTransaction& tx, TxValidationS\n     }\n\n     // Call CheckInputScripts() to cache signature and script validity against current tip consensus rules.\n-    return CheckInputScripts(tx, state, view, flags, /*check_for_block=*/false, /* cacheSigStore= */ true, /* cacheFullScriptStore= */ true, txdata, validati\n+    if (auto err = CheckInputScripts(tx, view, flags, /* cacheSigStore= */ true, /* cacheFullScriptStore= */ true, txdata, validation_cache)) {\n+        auto validation_result = flags & STANDARD_NOT_MANDATORY_VERIFY_FLAGS ? TxValidationResult::TX_NOT_STANDARD : TxValidationResult::TX_CONSENSUS;\n+        state.Invalid(validation_result, strprintf(\"mempool-script-verify-flag-failed (%s)\", ScriptErrorString(err->error)), err->debug_message);\n+        return false;\n+    }\n+    return true;\n }\n\n namespace {\n@@ -1253,13 +1258,14 @@ bool MemPoolAccept::PolicyScriptChecks(const ATMPArgs& args, Workspace& ws)\n\n     // Check input scripts and signatures.\n     // This is done last to help prevent CPU exhaustion denial-of-service attacks.\n-    if (!CheckInputScripts(tx, state, m_view, scriptVerifyFlags, /*check_for_block=*/false, true, false, ws.m_precomputed_txdata, GetValidationCache())) {\n+    if (auto err = CheckInputScripts(tx, m_view, scriptVerifyFlags, true, false, ws.m_precomputed_txdata, GetValidationCache())) {\n+        auto validation_result = scriptVerifyFlags & STANDARD_NOT_MANDATORY_VERIFY_FLAGS ? TxValidationResult::TX_NOT_STANDARD : TxValidationResult::TX_CONSE\n         // Detect a failure due to a missing witness so that p2p code can handle rejection caching appropriately.\n         if (!tx.HasWitness() && SpendsNonAnchorWitnessProg(tx, m_view)) {\n-            state.Invalid(TxValidationResult::TX_WITNESS_STRIPPED,\n-                    state.GetRejectReason(), state.GetDebugMessage());\n+            validation_result = TxValidationResult::TX_WITNESS_STRIPPED;\n         }\n-        return false; // state filled in by CheckInputScripts\n+        state.Invalid(validation_result, strprintf(\"mempool-script-verify-flag-failed (%s)\", ScriptErrorString(err->error)),  err->debug_message);\n+        return false;\n     }\n\n     return true;\n@@ -2134,23 +2140,17 @@ ValidationCache::ValidationCache(const size_t script_execution_cache_bytes, cons\n  * which are matched. This is useful for checking blocks where we will likely never need the cache\n  * entry again.\n  *\n- * Note that we may set state.reason to NOT_STANDARD for extra soft-fork flags in flags, block-checking\n- * callers should probably reset it to CONSENSUS in such cases.\n  *\n  * Non-static (and redeclared) in src/test/txvalidationcache_tests.cpp\n  */\n-bool CheckInputScripts(const CTransaction& tx, TxValidationState& state,\n-                       const CCoinsViewCache& inputs, script_verify_flags flags, bool check_for_block,\n+std::optional<ScriptCheckError> CheckInputScripts(const CTransaction& tx,\n+                       const CCoinsViewCache& inputs, script_verify_flags flags,\n                        bool cacheSigStore,\n                        bool cacheFullScriptStore, PrecomputedTransactionData& txdata,\n                        ValidationCache& validation_cache,\n                        std::vector<CScriptCheck>* pvChecks)\n {\n-    if (tx.IsCoinBase()) return true;\n-\n-    if (flags & STANDARD_NOT_MANDATORY_VERIFY_FLAGS) {\n-        assert(!check_for_block);\n-    }\n+    if (tx.IsCoinBase()) return std::nullopt;\n\n     if (pvChecks) {\n         pvChecks->reserve(tx.vin.size());\n@@ -2166,7 +2166,7 @@ bool CheckInputScripts(const CTransaction& tx, TxValidationState& state,\n     hasher.Write(UCharCast(tx.GetWitnessHash().begin()), 32).Write((unsigned char*)&flags, sizeof(flags)).Finalize(hashCacheEntry.begin());\n     AssertLockHeld(cs_main); //TODO: Remove this requirement by making CuckooCache not require external locks\n     if (validation_cache.m_script_execution_cache.contains(hashCacheEntry, !cacheFullScriptStore)) {\n-        return true;\n+        return std::nullopt;\n     }\n\n     if (!txdata.m_spent_outputs_ready) {\n@@ -2196,17 +2196,7 @@ bool CheckInputScripts(const CTransaction& tx, TxValidationState& state,\n         if (pvChecks) {\n             pvChecks->emplace_back(std::move(check));\n         } else if (auto result = check(); result.has_value()) {\n-            // Tx failures never trigger disconnections/bans.\n-            // This is so that network splits aren't triggered\n-            // either due to non-consensus relay policies (such as\n-            // non-standard DER encodings or non-null dummy\n-            // arguments) or due to new consensus rules introduced in\n-            // soft forks.\n-            if (!check_for_block) {\n-                return state.Invalid(TxValidationResult::TX_NOT_STANDARD, strprintf(\"mempool-script-verify-flag-failed (%s)\", ScriptErrorString(result->first)), result->second);\n-            } else {\n-                return state.Invalid(TxValidationResult::TX_CONSENSUS, strprintf(\"block-script-verify-flag-failed (%s)\", ScriptErrorString(result->first)), result->second);\n-            }\n+            return ScriptCheckError{result->first, result->second};\n         }\n     }\n\n@@ -2216,7 +2206,7 @@ bool CheckInputScripts(const CTransaction& tx, TxValidationState& state,\n         validation_cache.m_script_execution_cache.insert(hashCacheEntry);\n     }\n\n-    return true;\n+    return std::nullopt;\n }\n\n bool FatalError(Notifications& notifications, BlockValidationState& state, const bilingual_str& message)\n@@ -2663,21 +2653,20 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state,\n         if (!tx.IsCoinBase() && fScriptChecks)\n         {\n             bool fCacheResults = fJustCheck; /* Don't cache results if we're actually connecting blocks (still consult the cache, though) */\n-            bool tx_ok;\n-            TxValidationState tx_state;\n+            std::optional<ScriptCheckError> script_check_err;\n             // If CheckInputScripts is called with a pointer to a checks vector, the resulting checks are appended to it. In that case\n             // they need to be added to control which runs them asynchronously. Otherwise, CheckInputScripts runs the checks before returning.\n             if (control) {\n                 std::vector<CScriptCheck> vChecks;\n-                tx_ok = CheckInputScripts(tx, tx_state, view, flags, /*check_for_block=*/true, fCacheResults, fCacheResults, txsdata[i], m_chainman.m_validation_cache, &vChecks);\n-                if (tx_ok) control->Add(std::move(vChecks));\n+                script_check_err = CheckInputScripts(tx, view, flags, fCacheResults, fCacheResults, txsdata[i], m_chainman.m_validation_cache, &vChecks);\n+                if (!script_check_err) control->Add(std::move(vChecks));\n             } else {\n-                tx_ok = CheckInputScripts(tx, tx_state, view, flags, /*check_for_block=*/true, fCacheResults, fCacheResults, txsdata[i], m_chainman.m_validation_cache);\n+                script_check_err = CheckInputScripts(tx, view, flags, fCacheResults, fCacheResults, txsdata[i], m_chainman.m_validation_cache);\n             }\n-            if (!tx_ok) {\n+            if (script_check_err) {\n                 // Any transaction validation failure in ConnectBlock is a block consensus failure\n                 state.Invalid(BlockValidationResult::BLOCK_CONSENSUS,\n-                              tx_state.GetRejectReason(), tx_state.GetDebugMessage());\n+                              strprintf(\"block-script-verify-flag-failed (%s)\", ScriptErrorString(script_check_err->error)), script_check_err->debug_message);\n                 break;\n             }\n         }\ndiff --git a/src/validation.h b/src/validation.h\nindex cd448f3ca9..c34ae41159 100644\n--- a/src/validation.h\n+++ b/src/validation.h\n@@ -331,6 +331,11 @@ bool CheckSequenceLocksAtTip(CBlockIndex* tip,\n  * Closure representing one script verification\n  * Note that this stores references to the spending transaction\n  */\n+struct ScriptCheckError {\n+    ScriptError error;\n+    std::string debug_message;\n+};\n+\n class CScriptCheck\n {\n private:\n\n```"
  },
  {
   "t": "2026-03-30T14:21:09Z",
   "kind": "comment",
   "who": "ismaelsadeeq",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nYeah, fair point, but worth noting that in that case, it makes sense we hit the policy first,  but in this case, there is no policy violation, it is a consensus violation that is collapsed to policy.\n\n[quoted text omitted]\nYeah, worth making it explicit."
  },
  {
   "t": "2026-03-30T14:22:17Z",
   "kind": "comment",
   "who": "ismaelsadeeq",
   "assoc": "MEMBER",
   "text": "I noticed stale comment in `MANDATORY_SCRIPT_VERIFY_FLAGS ` since #33050 , fixed in https://github.com/bitcoin/bitcoin/pull/34957"
  },
  {
   "t": "2026-04-02T09:22:20Z",
   "kind": "comment",
   "who": "ajtowns",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nThat means you need to validate each caller correctly adds either `TX_NOT_STANDARD` and `mempool-script-verify-flag-failed` or `TX_CONSENSUS` and `block-script-verify-flag-failed`, rather than correct passing a boolean? That seems more work to review and more prone to error?"
  },
  {
   "t": "2026-04-04T02:35:23Z",
   "kind": "comment",
   "who": "ismaelsadeeq",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nFor the tx validation paths, the setting of the error message can be abstracted away to maintain consistency of the error string. Indeed, it adds some work for callers now to correctly map the result, but this is preferable to relying on a boolean, because it separates the logic of checking input scripts from that decision and makes testing and reasoning about the subroutine much more straightforward.\n\nI\u2019m curious to hear other reviewers' thoughts, though, not rigid on this at all. Just bringing up ideas here."
  },
  {
   "t": "2026-04-06T17:08:17Z",
   "kind": "review_comment",
   "who": "instagibbs",
   "assoc": "MEMBER",
   "path": "test/functional/p2p_segwit.py",
   "commit": "dfe4cc6cc2003b077f95e30e50c47f1fa28fb2e2",
   "in_reply_to": null,
   "text": "```Suggestion\n        # Building a block with the transaction must be valid, however, even without -acceptnonstdtxn.\n```"
  },
  {
   "t": "2026-04-06T17:20:27Z",
   "kind": "review_comment",
   "who": "instagibbs",
   "assoc": "MEMBER",
   "path": "test/functional/p2p_invalid_tx.py",
   "commit": "2bf3c6421f9efea9261922b6b5943d42688361d2",
   "in_reply_to": null,
   "text": "this or similar?\n```Suggestion\n            std_reject = template.std_reject_reason or template.reject_reason\n            assert std_reject is not None\n```"
  },
  {
   "t": "2026-04-06T20:31:34Z",
   "kind": "comment",
   "who": "instagibbs",
   "assoc": "MEMBER",
   "text": "One suggestion that limits the scope a bit: just don't change the CheckInputScripts args, so the report per-tx message turns into \"block-*\" if it's a block validation related script failure when standardness knob is off?\n\nIs that too confusing/ugly or simply Broken in some way?"
  },
  {
   "t": "2026-04-07T12:54:25Z",
   "kind": "force_push",
   "who": "ajtowns",
   "commit": "dfe4cc6cc2003b077f95e30e50c47f1fa28fb2e2"
  },
  {
   "t": "2026-04-07T12:54:30Z",
   "kind": "comment",
   "who": "ajtowns",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nThat's exactly what passing the bool is doing though?\n\n[quoted text omitted]\nHaving mempool errors become \"block\" errors depending on a config settings seems a bit confusing, so that doesn't really seem preferable, unless it's much easier. And making the change (`check_for_block = !(flags & STANDARD_NOT_MANDATORY_VERIFY_FLAGS);` at the top of `CheckInputScripts()`) interferes with tests that use acceptnonstdtxn for specific reasons (eg feature_cltv, feature_segwit, p2p_segwit). I think I looked at changing those previously, but it didn't seem much easier, and also seemed more confusing to have mempool actions be called \"block-\" instead of \"mempool-\".\n\nThe \"validation: Be explicit about whether CheckInputScripts is for block/mempool\" commit seems pretty easy to review with `git show HEAD^ --word-diff` to me, fwiw? I'm not really seeing the problem?\n\nAlso apparently this picked up a silent merge conflict via #31823 by the looks. Rebased and fixed."
  },
  {
   "t": "2026-04-07T13:46:40Z",
   "kind": "comment",
   "who": "instagibbs",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nI was simply suggesting less code if the side effects were acceptable. I just don't see a block level error being reported as one as particularly odd. If others disagree, then you can ignore the suggestion."
  },
  {
   "t": "2026-04-07T15:43:26Z",
   "kind": "comment",
   "who": "ajtowns",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nIt's not a block level error, though: it's a tx level error? (We're submitting a tx to the mempool that's consensus invalid)\n\nWe previously differentiated consensus-level errors vs policy errors; we're currently differentiating errors during block-validation (always consensus-level) vs errors during mempool-validation (currently consensus plus policy checks, but changed by this PR to be, depending on the acceptnonstdtxn setting, either consensus plus policy checks, or the hardcoded set of consensus checks we expect to be enforced at tip but which might be different from what's actually being enforced at the node's current tip). We could not differentiate at all, and just always report \"script-verify-flag-failed\" (with no \"block-\" or \"mempool-\" prefix), but that would be a bigger code change (though maybe could be a scripted-diff?), as lots of tests would need updating."
  },
  {
   "t": "2026-04-07T15:55:35Z",
   "kind": "comment",
   "who": "instagibbs",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nOk this difference vs actual chaintip is persuasive against my idea"
  },
  {
   "t": "2026-04-08T15:13:34Z",
   "kind": "review_comment",
   "who": "instagibbs",
   "assoc": "MEMBER",
   "path": "src/validation.cpp",
   "commit": "f876cffa9c03151746a7fd03be76ec00e8b4a223",
   "in_reply_to": null,
   "text": "The log message in ConsensusScriptChecks on failure is a little off after this patch. Maybe\n\n[quoted text omitted]\nor something? not a huge deal for a never should happen log"
  },
  {
   "t": "2026-04-08T15:28:45Z",
   "kind": "review_comment",
   "who": "instagibbs",
   "assoc": "MEMBER",
   "path": "src/validation.cpp",
   "commit": "dfe4cc6cc2003b077f95e30e50c47f1fa28fb2e2",
   "in_reply_to": null,
   "text": "dfe4cc6cc2003b077f95e30e50c47f1fa28fb2e2\n\ncan we assert this property later in the function instead of just a comment?"
  },
  {
   "t": "2026-04-08T15:33:39Z",
   "kind": "review_comment",
   "who": "instagibbs",
   "assoc": "MEMBER",
   "path": "src/validation.cpp",
   "commit": "f876cffa9c03151746a7fd03be76ec00e8b4a223",
   "in_reply_to": null,
   "text": "f876cffa9c03151746a7fd03be76ec00e8b4a223\n\njust noting crashing here in release build being the correct thing since it would be a block check with erroneous flags"
  },
  {
   "t": "2026-04-08T15:35:40Z",
   "kind": "review",
   "who": "instagibbs",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "dfe4cc6cc2003b077f95e30e50c47f1fa28fb2e2",
   "text": "logic looks correct, some suggestions dfe4cc6cc2003b077f95e30e50c47f1fa28fb2e2"
  },
  {
   "t": "2026-04-08T15:44:31Z",
   "kind": "comment",
   "who": "instagibbs",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nTo check: Risk seems similar to what already can happen on testnet if any of the other standardness checks fail? Just now with script interpreter things?"
  },
  {
   "t": "2026-04-09T18:54:42Z",
   "kind": "comment",
   "who": "ajtowns",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nHmm, I think I've been contradictory here. On the one hand:\n\n[quoted text omitted]\nThat implies `GetBlockScriptFlags` can and should return values that aren't inside `MANDATORY_SCRIPT_VERIFY_FLAGS`, but\n\n```c++\n    if (flags & STANDARD_NOT_MANDATORY_VERIFY_FLAGS) {\n        assert(!check_for_block);\n    }\n```\n\nimplies that it should not (well, unless they're flags that aren't checked for mempool acceptance even with `acceptnonstdtxn=0`). That doesn't make any difference today, but would make a difference with inquisition builds (where currently softfork flags are added to `STANDARD_SCRIPT_VERIFY_FLAGS` and `GetBlockScriptFlags`).\n\nSo I think that means the std-not-mandatory check-and-assert above is better removed from this PR.\n\nAnyway:\n\n[quoted text omitted]\nSimilar, but not the same?\n\neg, with core v31, bip 54 is enforced as a standardness rule, and I believe you can disable it with `-acceptnonstdtxn=1`, so provided you can violate it without violating `bad-txns-too-many-sigops`, I think you can add a bip 54 violating tx to your mempool and mine it later.\n\nHowever, if we setup bip 54 for consensus activation [the same way](https://github.com/bitcoin-inquisition/bitcoin/pull/99/changes#diff-97c3a52bc5fad452d82670a7fd291800bae20c7bc35bb82686c2c0a4ea7b5b98R875) as it's implemented for inquisition then we'll always enforce it in the mempool, and it won't be possible to get a violation into the mempool even prior to activation."
  },
  {
   "t": "2026-04-09T19:07:12Z",
   "kind": "comment",
   "who": "instagibbs",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nNot sure I understand the qualatative difference between this and, say, an unspent dust output with standardness checks off. If we somehow softforked a rule about that, they'd be stuck either way? Just making sure I'm not missing a nuance."
  },
  {
   "t": "2026-04-21T07:03:43Z",
   "kind": "review_comment",
   "who": "sedited",
   "assoc": "MEMBER",
   "path": "src/test/txvalidationcache_tests.cpp",
   "commit": "f876cffa9c03151746a7fd03be76ec00e8b4a223",
   "in_reply_to": null,
   "text": "I have a slight preference for making this an enum, which seems more in line with communicating whether it is either block or mempool, like you state in the commit title. Something like\n\nClick to expand diff\n\n```diff\ndiff --git a/src/test/txvalidationcache_tests.cpp b/src/test/txvalidationcache_tests.cpp\nindex b7f90dd8b5..a4d53cd355 100644\n--- a/src/test/txvalidationcache_tests.cpp\n+++ b/src/test/txvalidationcache_tests.cpp\n@@ -24 +24 @@ bool CheckInputScripts(const CTransaction& tx, TxValidationState& state,\n-                       const CCoinsViewCache& inputs, script_verify_flags flags, bool check_for_block,\n+                       const CCoinsViewCache& inputs, script_verify_flags flags, CheckType check_type,\n@@ -146 +146 @@ static void ValidateCheckInputsForAllFlags(const CTransaction &tx, script_verify\n-        bool ret = CheckInputScripts(tx, state, &active_coins_tip, test_flags, /*check_for_block=*/false, true, add_to_cache, txdata, validation_cache, nullptr);\n+        bool ret = CheckInputScripts(tx, state, &active_coins_tip, test_flags, CheckType::Mempool, true, add_to_cache, txdata, validation_cache, nullptr);\n@@ -156 +156 @@ static void ValidateCheckInputsForAllFlags(const CTransaction &tx, script_verify\n-            BOOST_CHECK(CheckInputScripts(tx, state, &active_coins_tip, test_flags, /*check_for_block=*/false, true, add_to_cache, txdata, validation_cache, &scriptchecks));\n+            BOOST_CHECK(CheckInputScripts(tx, state, &active_coins_tip, test_flags, CheckType::Mempool, true, add_to_cache, txdata, validation_cache, &scriptchecks));\n@@ -162 +162 @@ static void ValidateCheckInputsForAllFlags(const CTransaction &tx, script_verify\n-            BOOST_CHECK(CheckInputScripts(tx, state, &active_coins_tip, test_flags, /*check_for_block=*/false, true, add_to_cache, txdata, validation_cache, &scriptchecks));\n+            BOOST_CHECK(CheckInputScripts(tx, state, &active_coins_tip, test_flags, CheckType::Mempool, true, add_to_cache, txdata, validation_cache, &scriptchecks));\n@@ -220 +220 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, Dersig100Setup)\n-        BOOST_CHECK(!CheckInputScripts(CTransaction(spend_tx), state, &m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_DERSIG, /*check_for_block=*/false, true, true, ptd_spend_tx, m_node.chainman->m_validation_cache, nullptr));\n+        BOOST_CHECK(!CheckInputScripts(CTransaction(spend_tx), state, &m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_DERSIG, CheckType::Mempool, true, true, ptd_spend_tx, m_node.chainman->m_validation_cache, nullptr));\n@@ -226 +226 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, Dersig100Setup)\n-        BOOST_CHECK(CheckInputScripts(CTransaction(spend_tx), state, &m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_DERSIG, /*check_for_block=*/false, true, true, ptd_spend_tx, m_node.chainman->m_validation_cache, &scriptchecks));\n+        BOOST_CHECK(CheckInputScripts(CTransaction(spend_tx), state, &m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_DERSIG, CheckType::Mempool, true, true, ptd_spend_tx, m_node.chainman->m_validation_cache, &scriptchecks));\n@@ -288 +288 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, Dersig100Setup)\n-        BOOST_CHECK(CheckInputScripts(CTransaction(invalid_with_cltv_tx), state, m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY, /*check_for_block=*/false, true, true, txdata, m_node.chainman->m_validation_cache, nullptr));\n+        BOOST_CHECK(CheckInputScripts(CTransaction(invalid_with_cltv_tx), state, m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY, CheckType::Mempool, true, true, txdata, m_node.chainman->m_validation_cache, nullptr));\n@@ -316 +316 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, Dersig100Setup)\n-        BOOST_CHECK(CheckInputScripts(CTransaction(invalid_with_csv_tx), state, &m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_CHECKSEQUENCEVERIFY, /*check_for_block=*/false, true, true, txdata, m_node.chainman->m_validation_cache, nullptr));\n+        BOOST_CHECK(CheckInputScripts(CTransaction(invalid_with_csv_tx), state, &m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_CHECKSEQUENCEVERIFY, CheckType::Mempool, true, true, txdata, m_node.chainman->m_validation_cache, nullptr));\n@@ -378 +378 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, Dersig100Setup)\n-        BOOST_CHECK(!CheckInputScripts(CTransaction(tx), state, &m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, /*check_for_block=*/false, true, true, txdata, m_node.chainman->m_validation_cache, nullptr));\n+        BOOST_CHECK(!CheckInputScripts(CTransaction(tx), state, &m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, CheckType::Mempool, true, true, txdata, m_node.chainman->m_validation_cache, nullptr));\n@@ -383 +383 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, Dersig100Setup)\n-        BOOST_CHECK(CheckInputScripts(CTransaction(tx), state, &m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, /*check_for_block=*/false, true, true, txdata, m_node.chainman->m_validation_cache, &scriptchecks));\n+        BOOST_CHECK(CheckInputScripts(CTransaction(tx), state, &m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, CheckType::Mempool, true, true, txdata, m_node.chainman->m_validation_cache, &scriptchecks));\ndiff --git a/src/validation.cpp b/src/validation.cpp\nindex 46a892d906..b0b77e3164 100644\n--- a/src/validation.cpp\n+++ b/src/validation.cpp\n@@ -141 +141 @@ bool CheckInputScripts(const CTransaction& tx, TxValidationState& state,\n-                       const CCoinsViewCache& inputs, script_verify_flags flags, bool check_for_block,\n+                       const CCoinsViewCache& inputs, script_verify_flags flags, CheckType check_type,\n@@ -431 +431 @@ static bool CheckInputsFromMempoolAndCache(const CTransaction& tx, TxValidationS\n-    return CheckInputScripts(tx, state, view, flags, /*check_for_block=*/false, /* cacheSigStore= */ true, /* cacheFullScriptStore= */ true, txdata, validation_cache);\n+    return CheckInputScripts(tx, state, view, flags, CheckType::Mempool, /* cacheSigStore= */ true, /* cacheFullScriptStore= */ true, txdata, validation_cache);\n@@ -1147 +1147 @@ bool MemPoolAccept::PolicyScriptChecks(const ATMPArgs& args, Workspace& ws)\n-    if (!CheckInputScripts(tx, state, m_view, scriptVerifyFlags, /*check_for_block=*/false, true, false, ws.m_precomputed_txdata, GetValidationCache())) {\n+    if (!CheckInputScripts(tx, state, m_view, scriptVerifyFlags, CheckType::Mempool, true, false, ws.m_precomputed_txdata, GetValidationCache())) {\n@@ -2063 +2063 @@ bool CheckInputScripts(const CTransaction& tx, TxValidationState& state,\n-                       const CCoinsViewCache& inputs, script_verify_flags flags, bool check_for_block,\n+                       const CCoinsViewCache& inputs, script_verify_flags flags, CheckType check_type,\n@@ -2072 +2072 @@ bool CheckInputScripts(const CTransaction& tx, TxValidationState& state,\n-        assert(!check_for_block);\n+        assert(check_type == CheckType::Mempool);\n@@ -2125 +2125 @@ bool CheckInputScripts(const CTransaction& tx, TxValidationState& state,\n-            if (!check_for_block) {\n+            if (check_type == CheckType::Mempool) {\n@@ -2593 +2593 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state,\n-                tx_ok = CheckInputScripts(tx, tx_state, view, flags, /*check_for_block=*/true, fCacheResults, fCacheResults, txsdata[i], m_chainman.m_validation_cache, &vChecks);\n+                tx_ok = CheckInputScripts(tx, tx_state, view, flags, CheckType::Block, fCacheResults, fCacheResults, txsdata[i], m_chainman.m_validation_cache, &vChecks);\n@@ -2596 +2596 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state,\n-                tx_ok = CheckInputScripts(tx, tx_state, view, flags, /*check_for_block=*/true, fCacheResults, fCacheResults, txsdata[i], m_chainman.m_validation_cache);\n+                tx_ok = CheckInputScripts(tx, tx_state, view, flags, CheckType::Block, fCacheResults, fCacheResults, txsdata[i], m_chainman.m_validation_cache);\ndiff --git a/src/validation.h b/src/validation.h\nindex cee0dd728b..6d8bd2ddab 100644\n--- a/src/validation.h\n+++ b/src/validation.h\n@@ -259,0 +260,5 @@ struct PackageMempoolAcceptResult\n+enum class CheckType {\n+    Block,\n+    Mempool\n+};\n+\n```"
  },
  {
   "t": "2026-05-11T19:57:34Z",
   "kind": "review",
   "who": "sedited",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "dfe4cc6cc2003b077f95e30e50c47f1fa28fb2e2",
   "text": "Approach ACK\n\nRead through the discussion again, and think this is the pragmatic solution. Would also suggest removing the assert, as you mentioned above, and applying the other fixes from the review comments."
  },
  {
   "t": "2026-07-22T06:15:37Z",
   "kind": "review_comment",
   "who": "ajtowns",
   "assoc": "MEMBER",
   "path": "src/validation.cpp",
   "commit": "dfe4cc6cc2003b077f95e30e50c47f1fa28fb2e2",
   "in_reply_to": 3052418404,
   "text": "I had a go previously at making this a compile time check, but it got kind-of ugly, especially when it's a no-op currently. Suggest exploring it further either in inquisition or when we have an actual deployment."
  },
  {
   "t": "2026-07-22T07:06:28Z",
   "kind": "review_comment",
   "who": "ajtowns",
   "assoc": "MEMBER",
   "path": "test/functional/p2p_invalid_tx.py",
   "commit": "2bf3c6421f9efea9261922b6b5943d42688361d2",
   "in_reply_to": 3040675735,
   "text": "This change wouldn't be correct -- `BadInputOutpointIndex` tests something that's accepted as an orphan when relayed, but rejected when included in a block."
  },
  {
   "t": "2026-07-22T07:18:28Z",
   "kind": "force_push",
   "who": "ajtowns",
   "commit": "d6eb6c97b6d29f066558bb2429476b37fd7fbbd0"
  },
  {
   "t": "2026-07-22T07:25:37Z",
   "kind": "force_push",
   "who": "ajtowns",
   "commit": "1f5447d74a8b735be03bb684a3f354c927294c14"
  },
  {
   "t": "2026-07-22T09:23:33Z",
   "kind": "force_push",
   "who": "ajtowns",
   "commit": "2bf3c6421f9efea9261922b6b5943d42688361d2"
  },
  {
   "t": "2026-07-22T09:30:26Z",
   "kind": "comment",
   "who": "ajtowns",
   "assoc": "MEMBER",
   "text": "Updated. The approach I took was:\n\n * Introduced `GetAllConsensusScriptFlags()` that covers the superset of consensus flags that `GetBlockScriptFlags()` can return. This is evaluated at startup and stored in chainman.\n * `-acceptnonstdtxn` uses that value instead of `STANDARD_SCRIPT_VERIFY_FLAGS`. That way you won't accept any txs into the mempool that might be invalid via some deployment activation, even via a reorg. But if you disable a deployment (regtest or inquisition's `-renounce` feature), then you can override that restriction.\n * Compile-time check to ensure that `STANDARD_SCRIPT_VERIFY_FLAGS` covers everything that `GetBlockScriptFlags` might return -- this ensures you'll never have a consensus violating tx in your mempool in normal operation.\n * Runtime check to ensure that `GetAllConsensusScriptFlags()` covers everything that `GetBlockScriptFlags` does return -- this ensures you'll never have a consensus violating tx in your mempool with `-acceptnonstdtxn=1` either\n * `GetAllConsensusScriptFlags()` is in some sense duplicated code vs `GetBlockScriptFlags` -- it's essentially replacing `DeploymentActiveAt` with `DeploymentEnabled`.\n\n After this series of commits, `MANDATORY_SCRIPT_VERIFY_FLAGS` and `STANDARD_NOT_MANDATORY_VERIFY_FLAGS` are unused and could be dropped (well, MANDATORY merged into STANDARD, of course)."
  },
  {
   "t": "2026-07-22T09:31:24Z",
   "kind": "review_comment",
   "who": "ajtowns",
   "assoc": "MEMBER",
   "path": "src/test/txvalidationcache_tests.cpp",
   "commit": "f876cffa9c03151746a7fd03be76ec00e8b4a223",
   "in_reply_to": 3115631327,
   "text": "Made it an enum, but chose a slightly different name for it."
  },
  {
   "t": "2026-07-22T09:32:45Z",
   "kind": "review_comment",
   "who": "ajtowns",
   "assoc": "MEMBER",
   "path": "src/validation.cpp",
   "commit": "dfe4cc6cc2003b077f95e30e50c47f1fa28fb2e2",
   "in_reply_to": 3052418404,
   "text": "Nevermind, I think this is done now."
  },
  {
   "t": "2026-09-17T08:50:58Z",
   "kind": "review_comment",
   "who": "sedited",
   "assoc": "MEMBER",
   "path": "src/validation.cpp",
   "commit": "2bf3c6421f9efea9261922b6b5943d42688361d2",
   "in_reply_to": null,
   "text": "It still seems confusing to me to introduce this type change here in a function that has nothing to do with standardness. Is this check really required in the first place? Maybe the invariant \"GetBlockScriptFlags returns a subset of STANDARD_SCRIPT_VERIFY_FLAGS\" can just be checked in a unit test instead?"
  }
 ],
 "labels_log": [
  {
   "t": "2024-04-10T09:08:44Z",
   "action": "labeled",
   "label": "TX fees and policy",
   "who": "DrahtBot"
  },
  {
   "t": "2024-05-15T00:51:15Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2024-05-16T18:35:15Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2025-03-17T14:27:56Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2025-03-24T08:08:48Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2025-08-12T22:49:55Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-08-13T04:19:54Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2025-08-13T05:10:04Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-08-28T16:40:44Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2025-10-07T21:53:34Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-11-18T10:03:02Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-07-22T07:26:26Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-07-22T08:32:18Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  }
 ],
 "state_log": [
  {
   "t": "2025-08-14T22:34:15Z",
   "kind": "convert_to_draft",
   "who": "ajtowns"
  },
  {
   "t": "2025-08-28T17:33:22Z",
   "kind": "ready_for_review",
   "who": "ajtowns"
  }
 ],
 "text_chars": 60149,
 "text_tokens_estimate": 15037,
 "changed_paths": [
  "src/test/txvalidationcache_tests.cpp",
  "src/validation.cpp",
  "src/validation.h",
  "test/functional/data/invalid_txs.py",
  "test/functional/p2p_invalid_tx.py",
  "test/functional/p2p_segwit.py"
 ],
 "files": [
  {
   "path": "src/test/txvalidationcache_tests.cpp",
   "add": 9,
   "del": 15
  },
  {
   "path": "src/validation.cpp",
   "add": 83,
   "del": 23
  },
  {
   "path": "src/validation.h",
   "add": 21,
   "del": 0
  },
  {
   "path": "test/functional/data/invalid_txs.py",
   "add": 11,
   "del": 1
  },
  {
   "path": "test/functional/p2p_invalid_tx.py",
   "add": 15,
   "del": 5
  },
  {
   "path": "test/functional/p2p_segwit.py",
   "add": 10,
   "del": 11
  }
 ],
 "test_lines": 77,
 "git": {
  "head": "2bf3c6421f9efea9261922b6b5943d42688361d2",
  "head_matches_backup": true,
  "base": "32eb52100296718f7c0469e3210ce1db73694793",
  "commits": [
   {
    "sha": "9b6342e5d2",
    "subject": "tests: in p2p_segwit, check non-mandatory errors with -acceptnonstdtxn=0 node",
    "files": 1,
    "add": 8,
    "del": 8
   },
   {
    "sha": "4c9a00f565",
    "subject": "tests: test tx rejection for both -acceptnonstdtxn=0 and 1",
    "files": 2,
    "add": 25,
    "del": 5
   },
   {
    "sha": "1eb2d8828f",
    "subject": "validation: Be explicit about whether CheckInputScripts is for block/mempool",
    "files": 3,
    "add": 34,
    "del": 34
   },
   {
    "sha": "daea759c18",
    "subject": "validation: Check only (potential) block script flags when -acceptnonstdtxn is set",
    "files": 4,
    "add": 39,
    "del": 6
   },
   {
    "sha": "2bf3c6421f",
    "subject": "validation: Ensure STANDARD_SCRIPT_VERIFY_FLAGS is a superset of GetBlockScriptFlags",
    "files": 1,
    "add": 43,
    "del": 2
   }
  ],
  "patch_truncated": false
 },
 "input_hash": "890b1dd578b1f5e3",
 "extracted_at": "2026-09-17T16:15:31+00:00"
}