kernel: Expose CheckTransaction consensus validation function #33796

pull w0xlt wants to merge 1 commits into bitcoin:master from w0xlt:kernel_checktransaction changing 4 files +298 −0
  1. w0xlt commented at 11:59 pm on November 5, 2025: contributor

    This PR exposes the consensus-level CheckTransaction function through the libbitcoinkernel C API and adds a corresponding C++ wrapper.

    Currently, libkernel only provided script-level validation via btck_script_pubkey_verify and ScriptPubkeyApi<>::Verify.

    AFAIK there was no way to perform context-free consensus checks on a transaction’s structure (e.g., coinbase rules, money-range, duplicate inputs).

    This change introduces a new API:

    0int btck_check_transaction(const btck_Transaction* tx, btck_TxValidationState** out_state);
    

    and a C++ convenience wrapper:

    0std::pair<bool, TxValidationState> btck::CheckTransaction(const Transaction& tx);
    

    Both follow the ownership and error-handling conventions established in bitcoinkernel.h.

    The test suite is extended with cases covering:

    • coinbase scriptSig length bounds
    • empty vin / vout detection
    • negative or out-of-range output values
    • duplicate inputs
    • null prevouts in non-coinbase transactions
  2. DrahtBot commented at 11:59 pm on November 5, 2025: contributor

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

    Code Coverage & Benchmarks

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

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    Concept ACK sedited, janb84, alexanderwiederin, stickies-v
    Approach ACK yuvicc

    If your review is incorrectly listed, please copy-paste <!–meta-tag:bot-skip–> into the comment that the bot should ignore.

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #33908 (kernel: add context‑free block validation API (btck_check_block_context_free) with POW/Merkle flags by w0xlt)

    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.

  3. in src/kernel/bitcoinkernel.h:367 in a262282abe outdated
    362+#define btck_TxValidationResult_WITNESS_STRIPPED    ((btck_TxValidationResult)(7))
    363+#define btck_TxValidationResult_CONFLICT            ((btck_TxValidationResult)(8))
    364+#define btck_TxValidationResult_MEMPOOL_POLICY      ((btck_TxValidationResult)(9))
    365+#define btck_TxValidationResult_NO_MEMPOOL          ((btck_TxValidationResult)(10))
    366+#define btck_TxValidationResult_RECONSIDERABLE      ((btck_TxValidationResult)(11))
    367+#define btck_TxValidationResult_UNKNOWN             ((btck_TxValidationResult)(12))
    


    l0rinc commented at 9:04 am on November 6, 2025:
    shouldn’t this be bigger than 12 (or could maybe be 0) so that new values can be added in the future?

    w0xlt commented at 1:42 am on February 10, 2026:
    Done. Thanks.

    alexanderwiederin commented at 1:59 pm on April 10, 2026:
    Was this addressed? It looks unchanged to me.
  4. sedited commented at 11:01 am on November 6, 2025: contributor

    Concept ACK on adding more checks. I am not sure how useful this check by itself is though, since it lacks finality, inputs, sigops, amount + fee, and script checks. Are you planning on adding these too and if not, what is the purpose served from surfacing the context-free checks, but not the others?

    I think the unit tests are going a bit too far. We don’t have to verify again that our validation logic works internally and should instead just verify that the function’s contract is correct. If you want to check that the mapping for each of the result enums is correct maybe pass in a few hard-coded transactions instead? We do the same in our unit tests too so maybe just reuse a few of the vectors from test/data/tx_invalid.json?

  5. w0xlt commented at 6:44 pm on November 6, 2025: contributor

    I am not sure how useful this check by itself is … what is the purpose served from surfacing the context‑free checks, but not the others?

    The new API intentionally exposes only the context‑free consensus predicate (consensus/tx_check::CheckTransaction) so callers can fail fast on malformed transactions without needing a kernel context, UTXO set, or policy knobs.

    This gives library users (indexers, gateways, alternative mempool layers, etc.) a cheap pre‑filter to catch structural rule violations like empty vin/vout, out‑of‑range amounts, coinbase scriptSig length bounds, duplicate inputs, or null prevouts in non‑coinbase txs—before doing any stateful or expensive work.

    Script checks are already available via btck_script_pubkey_verify in this API; inputs/fees/sigops/finality all need UTXO and/or chain context and are out of scope for a context‑free entry point.

    think the unit tests are going a bit too far … instead just verify that the function’s contract is correct.

    Agreed. This can be simplified.

  6. ?
    project_v2_item_status_changed sedited
  7. ?
    added_to_project_v2 sedited
  8. fanquake renamed this:
    [kernel] Expose `CheckTransaction` consensus validation function
    kernel: Expose `CheckTransaction` consensus validation function
    on Nov 7, 2025
  9. DrahtBot added the label Validation on Nov 7, 2025
  10. in src/kernel/bitcoinkernel.h:603 in a262282abe
    598+ */
    599+///@{
    600+/**
    601+ * @brief Run consensus/tx_check::CheckTransaction on a tx and return the filled state.
    602+ * @return 1 if valid, 0 if invalid.
    603+ */
    


    yuvicc commented at 6:27 am on November 8, 2025:
     0/**
     1   * [@brief](/bitcoin-bitcoin/contributor/brief/) Run consensus/tx_check::CheckTransaction on a transaction.
     2   * 
     3   * Performs context-free consensus validation on a transaction without
     4   * requiring blockchain state.
     5   * 
     6   * [@param](/bitcoin-bitcoin/contributor/param/)[in] tx The transaction to validate
     7   * [@param](/bitcoin-bitcoin/contributor/param/)[out] out_state Pointer to receive the validation state (always set,
     8   *                       caller must destroy with btck_tx_validation_state_destroy)
     9   * [@return](/bitcoin-bitcoin/contributor/return/) 1 if valid, 0 if invalid
    10   */
    

    w0xlt commented at 1:42 am on February 10, 2026:
    Done. Thanks.
  11. yuvicc commented at 6:31 am on November 8, 2025: contributor

    Approach ACK

    Great addition exposing CheckTransaction to the kernel API. This will be useful to external users needing context-free transaction validation.

  12. in src/test/kernel/test_kernel.cpp:1075 in a262282abe outdated
    1068@@ -973,3 +1069,133 @@ BOOST_AUTO_TEST_CASE(btck_chainman_regtest_tests)
    1069     std::filesystem::remove_all(test_directory.m_directory / "blocks" / "rev00000.dat");
    1070     BOOST_CHECK_THROW(chainman->ReadBlockSpentOutputs(tip), std::runtime_error);
    1071 }
    1072+
    1073+// -----------------------------------------------------------------------------
    1074+// CheckTransaction tests
    1075+// -----------------------------------------------------------------------------
    


    janb84 commented at 10:20 am on November 17, 2025:

    NIT: (non-blocking) maybe add a note why only the 2 (possible) states are tested ? For a non-bitcoin-core dev this is maybe a bit unclear given all the TxValidationResults and the Check_Transaction that these are the 2 outcomes. It’s for me not logical to add this as a comment on the function because it returns a pointer to the results.

    0// -----------------------------------------------------------------------------
    1// Note: CheckTransaction performs only basic consensus checks and returns either:
    2//   - VALID (ValidationMode::VALID, TxValidationResult::UNSET) for valid transactions
    3//   - INVALID (ValidationMode::INVALID, TxValidationResult::CONSENSUS) for violations
    4//
    5// Other TxValidationResult values are set by higher-level validation functions and
    6// not exposed through btck_check_transaction.
    7// -----------------------------------------------------------------------------
    

    w0xlt commented at 1:43 am on February 10, 2026:
    Done. Thanks.
  13. janb84 commented at 10:35 am on November 17, 2025: contributor

    Concept ACK a262282abefc0a16be329ab2179c193b4b32cc5b

    PR extends bitcoin kernel library with the checkTransaction consensus validation function.

    Added a suggestion NIT (non-blocking) and I agree with the NIT from yuvicc

    Steps taken:

  14. DrahtBot added the label Needs rebase on Dec 27, 2025
  15. w0xlt force-pushed on Feb 10, 2026
  16. w0xlt commented at 1:44 am on February 10, 2026: contributor
    @sedited tests simplified. Vectors from test/data/tx_invalid.json are now being reused.
  17. DrahtBot removed the label Needs rebase on Feb 10, 2026
  18. sedited requested review from yuvicc on Mar 5, 2026
  19. sedited requested review from janb84 on Mar 5, 2026
  20. in src/kernel/bitcoinkernel.cpp:12 in 83b211dc84 outdated
     8@@ -9,6 +9,7 @@
     9 #include <chain.h>
    10 #include <coins.h>
    11 #include <consensus/validation.h>
    12+#include <consensus/tx_check.h>
    


    sedited commented at 9:03 pm on March 5, 2026:
    Nit (ordering): This should be one line higher.

    w0xlt commented at 11:04 pm on March 5, 2026:
    Done. Thanks.
  21. in src/kernel/bitcoinkernel.cpp:1403 in 83b211dc84
    1395@@ -1394,3 +1396,46 @@ void btck_block_header_destroy(btck_BlockHeader* header)
    1396 {
    1397     delete header;
    1398 }
    1399+
    1400+btck_ValidationMode btck_tx_validation_state_get_validation_mode(const btck_TxValidationState* state_)
    1401+{
    1402+    const auto& state = btck_TxValidationState::get(state_);
    1403+    if (state.IsValid())   return btck_ValidationMode_VALID;
    


    sedited commented at 9:04 pm on March 5, 2026:
    Nit (formatting): Aligning this with the line below looks a bit weird to me. I’d just apply a single space.

    w0xlt commented at 11:04 pm on March 5, 2026:
    Done. Thanks.
  22. in src/kernel/bitcoinkernel.h:526 in 83b211dc84 outdated
    521+        const btck_TxValidationState* state) BITCOINKERNEL_ARG_NONNULL(1);
    522+
    523+BITCOINKERNEL_API btck_TxValidationResult btck_tx_validation_state_get_tx_validation_result(
    524+    const btck_TxValidationState* state) BITCOINKERNEL_ARG_NONNULL(1);
    525+
    526+BITCOINKERNEL_API void btck_tx_validation_state_destroy(btck_TxValidationState* state);
    


    sedited commented at 9:08 pm on March 5, 2026:
    I think these should get doc strings.

    w0xlt commented at 11:04 pm on March 5, 2026:
    Done. Thanks.
  23. w0xlt force-pushed on Mar 5, 2026
  24. w0xlt force-pushed on Mar 6, 2026
  25. w0xlt force-pushed on Mar 6, 2026
  26. DrahtBot added the label CI failed on Mar 6, 2026
  27. DrahtBot removed the label CI failed on Mar 6, 2026
  28. in src/kernel/bitcoinkernel.cpp:1414 in 3e10c18cee outdated
    1409+{
    1410+    switch (btck_TxValidationState::get(state_).GetResult()) {
    1411+    case TxValidationResult::TX_RESULT_UNSET:        return btck_TxValidationResult_UNSET;
    1412+    case TxValidationResult::TX_CONSENSUS:           return btck_TxValidationResult_CONSENSUS;
    1413+    case TxValidationResult::TX_INPUTS_NOT_STANDARD: return btck_TxValidationResult_INPUTS_NOT_STANDARD;
    1414+    case TxValidationResult::TX_NOT_STANDARD:        return btck_TxValidationResult_NOT_STANDARD;
    


    sedited commented at 3:30 pm on March 13, 2026:
    Just a comment: It’s not ideal that we are just bulk exposing these when they cannot be reached with the current checks. Absent of a significant internal refactor I don’t really see a better way of handling them. Exposing only a subset for now also seems dangerous in case the internal implementation ever changes.

    w0xlt commented at 10:08 pm on March 17, 2026:
  29. in src/kernel/bitcoinkernel.h:693 in 3e10c18cee
    688+ * @param[in]  tx        Non-null, the transaction to validate.
    689+ * @param[out] out_state Non-null, receives a newly allocated validation state.
    690+ *                       The caller must destroy it with btck_tx_validation_state_destroy.
    691+ * @return               1 if valid, 0 if invalid.
    692+ */
    693+BITCOINKERNEL_API int BITCOINKERNEL_WARN_UNUSED_RESULT btck_check_transaction(
    


    sedited commented at 3:47 pm on March 13, 2026:
    Similarly to the other functions, I think this should follow the namespace_noun_verb naming convention, meaning btck_transaction_check.

  30. in src/kernel/bitcoinkernel.h:689 in 3e10c18cee
    684+ *
    685+ * Performs basic structural consensus checks (consensus/tx_check::CheckTransaction)
    686+ * without requiring blockchain state.
    687+ *
    688+ * @param[in]  tx        Non-null, the transaction to validate.
    689+ * @param[out] out_state Non-null, receives a newly allocated validation state.
    


    sedited commented at 3:57 pm on March 13, 2026:

    This seems dangerous to me. We state here that the output parameter should be non-null. A caller might expect this to mean that they can pass in a previously received validation state object. But looking at the implementation of this function, that would then leak memory, since we just create a new validation state object without destructing it first, because we assign to its pointer directly.

    There are two ways to solve this from what I can tell. We can either make this function also take a mutable pointer, like the script verify and header processing function, and introduce a creation function for the validation state, or we fix this in the implementation by explicitly de-referencing and then assigning to the out_state. I think I have a slight preference to the former.

    We’ve so far managed to keep away from mutable **, which imo have confusing semantics, so I think my preference would be the former solution.


  31. sedited commented at 3:58 pm on March 13, 2026: contributor
    Thanks for applying the fixes, found something else that should be addressed.
  32. sedited removed review request from yuvicc on Mar 13, 2026
  33. sedited requested review from alexanderwiederin on Mar 13, 2026
  34. alexanderwiederin commented at 1:03 pm on March 16, 2026: contributor
    Is there a consumer of this already?
  35. w0xlt force-pushed on Mar 17, 2026
  36. w0xlt commented at 10:03 pm on March 17, 2026: contributor

    @sedited Addressed the three actionable points in f490d9ec3d1a8d3f45d18b7ae6b9f85867fedc78.

    Renamed the C entry point to btck_transaction_check to match the existing namespace_noun_verb convention. I also changed the API to take a caller-owned btck_TxValidationState* and added btck_tx_validation_state_create(), so callers can safely reuse an existing state object and we no longer allocate through ** in the check function. . The C++ wrapper and kernel tests were updated accordingly, including a regression test that reuses the same validation state across multiple calls.

    For the enum exposure comment, I did not change behavior btck_transaction_check still only reaches UNSET and CONSENSUS today, but I documented that explicitly while keeping the full enum exposed for forward compatibility.

  37. w0xlt commented at 10:07 pm on March 17, 2026: contributor

    Is there a consumer of this already?

    The MEVPool project (currently under development) uses this functionality (as well as #33908) to validate transactions

  38. in src/kernel/bitcoinkernel.cpp:1441 in f490d9ec3d outdated
    1436+void btck_tx_validation_state_destroy(btck_TxValidationState* state)
    1437+{
    1438+    delete state;
    1439+}
    1440+
    1441+int btck_transaction_check(const btck_Transaction* tx, btck_TxValidationState* validation_state)
    


    alexanderwiederin commented at 10:54 am on March 18, 2026:
    The method name was not updated in the commit message.

    w0xlt commented at 7:53 pm on March 18, 2026:
    Done. Thanks.
  39. in src/kernel/bitcoinkernel.h:553 in f490d9ec3d
    548+    const btck_TxValidationState* state) BITCOINKERNEL_ARG_NONNULL(1);
    549+
    550+/**
    551+ * Destroy the btck_TxValidationState.
    552+ */
    553+BITCOINKERNEL_API void btck_tx_validation_state_destroy(btck_TxValidationState* state);
    


    alexanderwiederin commented at 11:24 am on March 18, 2026:
    I would also add the BITCOINKERNEL_ARG_NONNULL

    w0xlt commented at 7:53 pm on March 18, 2026:
    Done. Thanks.

    alexanderwiederin commented at 12:03 pm on March 19, 2026:
    I just saw that it was only the block validation state that also required a non-null parameter on the destroy function. All others do not. Maybe it was better to omit after all. @sedited, do you know why only the block validation state’s destroy method requires a non-null argument?

    sedited commented at 12:47 pm on March 19, 2026:
    I don’t think there is a reason, it seems to just be inconsistent. Probably I missed removing it at some point.

    alexanderwiederin commented at 12:54 pm on March 19, 2026:

    Ok, thanks!

    Sorry about that @w0xlt. I’ll open a PR to fix it for BlockValidationState later. I’ll leave you to decide if you want to change back the TxValiadationState or have me do it in the later PR.


    w0xlt commented at 1:09 am on March 20, 2026:
    No problem. Removed the BITCOINKERNEL_ARG_NONNULL(1) annotation.
  40. in src/kernel/bitcoinkernel.cpp:1432 in f490d9ec3d
    1427+    case TxValidationResult::TX_MEMPOOL_POLICY:      return btck_TxValidationResult_MEMPOOL_POLICY;
    1428+    case TxValidationResult::TX_NO_MEMPOOL:          return btck_TxValidationResult_NO_MEMPOOL;
    1429+    case TxValidationResult::TX_RECONSIDERABLE:      return btck_TxValidationResult_RECONSIDERABLE;
    1430+    case TxValidationResult::TX_UNKNOWN:             return btck_TxValidationResult_UNKNOWN;
    1431+    }
    1432+    /* Unreachable unless new enum values are added. */
    


    alexanderwiederin commented at 11:40 am on March 18, 2026:
    Why not assert, like in the BlockValidationResult?

    w0xlt commented at 7:53 pm on March 18, 2026:
    Done. Thanks.
  41. alexanderwiederin changes_requested
  42. alexanderwiederin commented at 1:25 pm on March 18, 2026: contributor
    Concept ACK
  43. stickies-v commented at 5:37 pm on March 18, 2026: contributor
    Concept ACK
  44. w0xlt force-pushed on Mar 18, 2026
  45. in src/test/kernel/test_kernel.cpp:1292 in 4c277d3e27 outdated
    1287+    expect_invalid(
    1288+        "01000000010000000000000000000000000000000000000000000000000000000000000000"
    1289+        "ffffffff6551515151515151515151515151515151515151515151515151515151515151515151"
    1290+        "515151515151515151515151515151515151515151515151515151515151515151515151515151"
    1291+        "51515151515151515151515151515151515151515151515151515151ffffffff01000000000000"
    1292+        "0000015100000000");
    


    alexanderwiederin commented at 11:54 am on March 19, 2026:
    Should we have one test for a valid coinbase tx?

    w0xlt commented at 1:09 am on March 20, 2026:
    Test added.
  46. w0xlt force-pushed on Mar 20, 2026
  47. in src/kernel/bitcoinkernel.h:704 in 0388330bb9 outdated
    699+ *
    700+ * @param[in]     tx               Non-null, the transaction to validate.
    701+ * @param[in,out] validation_state Non-null, previously created with
    702+ *                                 btck_tx_validation_state_create and updated
    703+ *                                 in-place with the validation result.
    704+ * @return                         1 if valid, 0 if invalid.
    


    alexanderwiederin commented at 11:43 am on March 23, 2026:

    I recommend adding:

    0 * [@return](/bitcoin-bitcoin/contributor/return/)                         1 if valid, 0 if invalid.
    1 * [@note](/bitcoin-bitcoin/contributor/note/)                           Only btck_TxValidationResult_UNSET and
    2 *                                 btck_TxValidationResult_CONSENSUS are
    3 *                                 reachable via this function.
    

    w0xlt commented at 5:36 pm on March 24, 2026:
    Done. Thanks.
  48. in src/kernel/bitcoinkernel.h:695 in 0388330bb9
    686@@ -636,6 +687,27 @@ BITCOINKERNEL_API void btck_precomputed_transaction_data_destroy(btck_Precompute
    687 
    688 ///@}
    689 
    690+/** @name Consensus
    691+ *  Context‑free consensus checks.
    692+ */
    693+///@{
    694+/**
    695+ * @brief Run context-free consensus validation on a transaction.
    


    alexanderwiederin commented at 12:03 pm on March 23, 2026:
    0 * [@brief](/bitcoin-bitcoin/contributor/brief/) Run context-free consensus validation on a btck_Transaction.
    

    w0xlt commented at 5:35 pm on March 24, 2026:
    Done. Thanks.
  49. in src/kernel/bitcoinkernel.cpp:1416 in 0388330bb9
    1411+}
    1412+
    1413+btck_TxValidationResult btck_tx_validation_state_get_tx_validation_result(const btck_TxValidationState* state_)
    1414+{
    1415+    // Expose the full TxValidationResult enum for forward compatibility, even
    1416+    // though btck_transaction_check currently only reaches UNSET/CONSENSUS.
    


    alexanderwiederin commented at 12:23 pm on March 23, 2026:
    This is redundant.

    w0xlt commented at 5:35 pm on March 24, 2026:
    Done. Thanks.
  50. in src/kernel/bitcoinkernel.h:389 in 0388330bb9
    384@@ -377,6 +385,24 @@ typedef uint32_t btck_BlockValidationResult;
    385 #define btck_BlockValidationResult_TIME_FUTURE ((btck_BlockValidationResult)(7))     //!< block timestamp was > 2 hours in the future (or our clock is bad)
    386 #define btck_BlockValidationResult_HEADER_LOW_WORK ((btck_BlockValidationResult)(8)) //!< the block header may be on a too-little-work chain
    387 
    388+/**
    389+ * A granular "reason" why a transaction was invalid.
    


    alexanderwiederin commented at 12:24 pm on March 23, 2026:
    0/**
    1 * Indicates the reason why a transaction failed validation. The subset of
    2 * values reachable depends on which validation function was used.
    3 */
    

    w0xlt commented at 5:35 pm on March 24, 2026:
    Done. Thanks.
  51. alexanderwiederin commented at 12:28 pm on March 23, 2026: contributor
    The commit message is inaccurate. btck_TxValidationResult does not provide granular failure reasons for this function, only UNSET and CONSENSUS are reachable via btck_transaction_check.
  52. kernel: Expose btck_transaction_check consensus function
    Add btck_transaction_check() to the libbitcoinkernel C API, exposing
    context-free transaction consensus validation (consensus/tx_check.h).
    
    Introduces btck_TxValidationState with introspection and lifecycle
    functions. btck_TxValidationResult is exposed for compatibility with
    existing validation-state APIs, though btck_transaction_check currently
    reaches only UNSET and CONSENSUS.
    
    Includes C++ wrapper and test coverage for btck_transaction_check using
    test vectors from tx_valid.json / tx_invalid.json.
    93c2d82eb0
  53. w0xlt force-pushed on Mar 24, 2026
  54. w0xlt commented at 5:36 pm on March 24, 2026: contributor
    Commit message updated.
  55. achow101 referenced this in commit 1189702d2f on Apr 1, 2026
  56. DrahtBot commented at 0:12 am on April 3, 2026: contributor
    🐙 This pull request conflicts with the target branch and needs rebase.
  57. DrahtBot added the label Needs rebase on Apr 3, 2026
  58. alexanderwiederin commented at 1:40 pm on April 10, 2026: contributor
    @w0xlt, are you still on this? Apologies if my PR caused the need for rebase.

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-10 21:13 UTC

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