kernel: add wtxid accessor #36207

pull KY-U wants to merge 2 commits into bitcoin:master from KY-U:2026-09-kernel-wtxid-accessor changing 4 files +192 −0
  1. KY-U commented at 2:06 PM on September 9, 2026: contributor

    While the kernel API provides access to a transactions's txid, it does not expose its wtxid. Clients therefore have to hash the transaction's witness serialization themselves, even though CTransaction already calculates and caches this value in m_witness_hash.

    Expose the wtxid through a C API handle and corresponding C++ view and owning wrappers. Mirror the existing txid operations for copying, destruction, comparison and byte serialization.

    Also expose CTransaction::HasWitness(), allowing clients to determine whether a transaction contains witness data without having to inspect each input themselves.

    Tests cover legacy transactions, where txid and wtxid bytes match, and witness transactions, where they differ.

  2. DrahtBot commented at 2:06 PM on September 9, 2026: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

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

    <!--006a51241073e994b41acfe9ec718e94-->

    Code Coverage & Benchmarks

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

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

    See the guideline and AI policy for information on the review process.

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

    <!--174a7506f384e20aa4161008e828411d-->

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #35187 (kernel: Block validation without a complete UTXO set by sedited)
    • #34374 (kernel: use struct-based logging and simplify logging interface by stickies-v)
    • #33847 (kernel: Improve logging API by ryanofsky)
    • #30342 (kernel, logging: Pass Logger instances to kernel objects by ryanofsky)

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

    <!--5faf32d7da4f0f540f40219e4f7537a3-->

  3. KY-U renamed this:
    2026 09 kernel wtxid accessor
    kernel: add wtxid accessor
    on Sep 9, 2026
  4. DrahtBot added the label Validation on Sep 9, 2026
  5. sedited requested review from alexanderwiederin on Sep 9, 2026
  6. sedited approved
  7. sedited commented at 3:06 PM on September 9, 2026: contributor

    ACK 98d4f836ed426dbe9de98da5e3d5eaaba6600a0f

  8. stickies-v commented at 3:13 PM on September 9, 2026: contributor

    Concept ACK

  9. nervana21 commented at 3:59 PM on September 9, 2026: contributor

    Concept ACK

  10. alexanderwiederin commented at 6:39 AM on September 10, 2026: contributor

    ACK 98d4f836ed426dbe9de98da5e3d5eaaba6600a0f

  11. DrahtBot requested review from stickies-v on Sep 10, 2026
  12. nervana21 commented at 7:51 PM on September 10, 2026: contributor

    tACK 98d4f836ed426dbe9de98da5e3d5eaaba6600a0f

    Left some minor nits that are non-blocking

  13. in src/kernel/bitcoinkernel.cpp:567 in 98d4f836ed outdated
     562 | +
     563 | +int btck_transaction_has_witness(const btck_Transaction* transaction)
     564 | +{
     565 | +    return btck_Transaction::get(transaction)->HasWitness() ? 1 : 0;
     566 | +}
     567 | +
    


    nervana21 commented at 8:02 PM on September 10, 2026:

    a3649e5df3aa5e4df83408218b741ae0adf62696: kernel: expose transaction wtxid

    nit: It reads easier if btck_transaction_has_witness comes before btck_transaction_get_wtxid in both bitcoinkernel.cpp and bitcoinkernel.h


    KY-U commented at 10:15 PM on September 10, 2026:

    Done.

  14. in src/test/kernel/test_kernel.cpp:411 in 98d4f836ed outdated
     406 | +    const auto wtxid_2{tx2.Wtxid().ToBytes()};
     407 | +    BOOST_CHECK(txid == wtxid);
     408 | +    BOOST_CHECK(txid_2 != wtxid_2);
     409 | +    BOOST_CHECK(!tx.HasWitness());
     410 | +    BOOST_CHECK(tx2.HasWitness());
     411 | +
    


    nervana21 commented at 8:05 PM on September 10, 2026:

    a3649e5df3aa5e4df83408218b741ae0adf62696: kernel: expose transaction wtxid

        BOOST_CHECK(!tx.HasWitness());
        BOOST_CHECK(tx2.HasWitness());
        const auto txid{tx.Txid().ToBytes()};
        const auto wtxid{tx.Wtxid().ToBytes()};
        const auto txid_2{tx2.Txid().ToBytes()};
        const auto wtxid_2{tx2.Wtxid().ToBytes()};
    
        BOOST_CHECK_EQUAL(byte_span_to_hex_string_reversed(txid), "aca326a724eda9a461c10a876534ecd5ae7b27f10f26c3862fb996f80ea2d45d");
        BOOST_CHECK(txid == wtxid);
        BOOST_CHECK(txid_2 != wtxid_2);
    
        BOOST_CHECK_EQUAL(byte_span_to_hex_string_reversed(txid_2), "9d04c6435f39a114f26b5807e92117b388f15c54c2afe7e62c96757f18cec891");
        BOOST_CHECK_EQUAL(byte_span_to_hex_string_reversed(wtxid_2), "c5cd58b8eb3dda755eb99c89c4bcb7409e4074c8942c263e3d6d09011984a210");
    

    nit: Based solely on the preceding byte vectors, it's not immediately clear that tx does not have a witness and tx2 does have a witness. Putting the BOOST_CHECKs first makes it more obvious.

    Could also add known expected values similar to nearby tests.


    KY-U commented at 10:22 PM on September 10, 2026:

    Done. I've only changed the arrangement of the BOOST_CHECKs a bit so the read order is: check known values, then check relation between hashes, for both tx and tx2.

  15. KY-U force-pushed on Sep 10, 2026
  16. KY-U force-pushed on Sep 10, 2026
  17. nervana21 commented at 10:38 PM on September 10, 2026: contributor

    re-tACK 6599a99acf5944f5f2d39d81e156de6eb01aa56f

    Nits addressed. Now we use known hashes in tests. Thanks!

  18. DrahtBot requested review from sedited on Sep 10, 2026
  19. ViniciusCestarii commented at 1:57 PM on September 11, 2026: contributor

    tACK 6599a99acf5944f5f2d39d81e156de6eb01aa56f

    I wrote a small client in C against libbitcoinkernel and the new exposed interface worked as expected.

  20. in src/test/kernel/test_kernel.cpp:403 in 6599a99acf
     399 | @@ -400,6 +400,20 @@ BOOST_AUTO_TEST_CASE(btck_transaction_tests)
     400 |      auto tx2{Transaction{tx_data_2}};
     401 |      CheckHandle(tx, tx2);
     402 |  
     403 | +    BOOST_CHECK(!tx.HasWitness());
    


    stickies-v commented at 2:24 PM on September 11, 2026:

    ~nit: would prefer keeping test cases specific and isolated as much as we can, moving these tests into a btck_transaction_id_tests?

    <details> <summary>git diff on 6599a99acf</summary>

    diff --git a/src/test/kernel/test_kernel.cpp b/src/test/kernel/test_kernel.cpp
    index cbc137abd7..47a46b985c 100644
    --- a/src/test/kernel/test_kernel.cpp
    +++ b/src/test/kernel/test_kernel.cpp
    @@ -16,6 +16,7 @@
     #include <test/util/common.h>
     
     #include <charconv>
    +#include <concepts>
     #include <cstdint>
     #include <cstdlib>
     #include <iostream>
    @@ -400,20 +401,6 @@ BOOST_AUTO_TEST_CASE(btck_transaction_tests)
         auto tx2{Transaction{tx_data_2}};
         CheckHandle(tx, tx2);
     
    -    BOOST_CHECK(!tx.HasWitness());
    -    BOOST_CHECK(tx2.HasWitness());
    -    const auto txid{tx.Txid().ToBytes()};
    -    const auto wtxid{tx.Wtxid().ToBytes()};
    -    const auto txid_2{tx2.Txid().ToBytes()};
    -    const auto wtxid_2{tx2.Wtxid().ToBytes()};
    -
    -    BOOST_CHECK_EQUAL(byte_span_to_hex_string_reversed(txid), "aca326a724eda9a461c10a876534ecd5ae7b27f10f26c3862fb996f80ea2d45d");
    -    BOOST_CHECK(txid == wtxid);
    -
    -    BOOST_CHECK_EQUAL(byte_span_to_hex_string_reversed(txid_2), "9d04c6435f39a114f26b5807e92117b388f15c54c2afe7e62c96757f18cec891");
    -    BOOST_CHECK_EQUAL(byte_span_to_hex_string_reversed(wtxid_2), "c5cd58b8eb3dda755eb99c89c4bcb7409e4074c8942c263e3d6d09011984a210");
    -    BOOST_CHECK(txid_2 != wtxid_2);
    -
         auto invalid_data = hex_string_to_byte_vec("012300");
         BOOST_CHECK_THROW(Transaction{invalid_data}, std::runtime_error);
         auto empty_data = hex_string_to_byte_vec("");
    @@ -488,6 +475,46 @@ BOOST_AUTO_TEST_CASE(btck_transaction_tests)
         check_equal(script_pubkey_roundtrip.ToBytes(), script_pubkey.ToBytes());
     }
     
    +BOOST_AUTO_TEST_CASE(btck_transaction_id_tests)
    +{
    +    auto legacy_tx{Transaction{hex_string_to_byte_vec("02000000013f7cebd65c27431a90bba7f796914fe8cc2ddfc3f2cbd6f7e5f2fc854534da95000000006b483045022100de1ac3bcdfb0332207c4a91f3832bd2c2915840165f876ab47c5f8996b971c3602201c6c053d750fadde599e6f5c4e1963df0f01fc0d97815e8157e3d59fe09ca30d012103699b464d1d8bc9e47d4fb1cdaa89a1c5783d68363c4dbc4b524ed3d857148617feffffff02836d3c01000000001976a914fc25d6d5c94003bf5b0c7b640a248e2c637fcfb088ac7ada8202000000001976a914fbed3d9b11183209a57999d54d59f67c019e756c88ac6acb0700")}};
    +    auto witness_tx{Transaction{hex_string_to_byte_vec("02000000000101904f4ee5c87d20090b642f116e458cd6693292ad9ece23e72f15fb6c05b956210500000000fdffffff02e2010000000000002251200839a723933b56560487ec4d67dda58f09bae518ffa7e148313c5696ac837d9f10060000000000002251205826bcdae7abfb1c468204170eab00d887b61ab143464a4a09e1450bdc59a3340140f26e7af574e647355830772946356c27e7bbc773c5293688890f58983499581be84de40be7311a14e6d6422605df086620e75adae84ff06b75ce5894de5e994a00000000")}};
    +
    +    BOOST_CHECK(!legacy_tx.HasWitness());
    +    BOOST_CHECK(witness_tx.HasWitness());
    +
    +    // Txid and Wtxid are distinct types and cannot be compared with each other
    +    static_assert(!std::equality_comparable_with<TxidView, WtxidView>);
    +    static_assert(!std::equality_comparable_with<Txid, Wtxid>);
    +
    +    // View equality on the same object and across objects
    +    BOOST_CHECK(legacy_tx.Txid() == legacy_tx.Txid());
    +    BOOST_CHECK(legacy_tx.Wtxid() == legacy_tx.Wtxid());
    +    BOOST_CHECK(legacy_tx.Txid() != witness_tx.Txid());
    +    BOOST_CHECK(legacy_tx.Wtxid() != witness_tx.Wtxid());
    +
    +    // Owned handles created from views compare equal to their copies
    +    Txid owned_txid{legacy_tx.Txid()};
    +    Txid owned_txid_copy{owned_txid};
    +    BOOST_CHECK(owned_txid == owned_txid_copy);
    +    BOOST_CHECK(owned_txid != Txid{witness_tx.Txid()});
    +    CheckHandle(owned_txid, Txid{witness_tx.Txid()});
    +
    +    Wtxid owned_wtxid{legacy_tx.Wtxid()};
    +    Wtxid owned_wtxid_copy{owned_wtxid};
    +    BOOST_CHECK(owned_wtxid == owned_wtxid_copy);
    +    BOOST_CHECK(owned_wtxid != Wtxid{witness_tx.Wtxid()});
    +    CheckHandle(owned_wtxid, Wtxid{witness_tx.Wtxid()});
    +
    +    // Without witness data the wtxid equals the txid, with witness data it differs
    +    BOOST_CHECK_EQUAL(byte_span_to_hex_string_reversed(legacy_tx.Txid().ToBytes()), "aca326a724eda9a461c10a876534ecd5ae7b27f10f26c3862fb996f80ea2d45d");
    +    check_equal(legacy_tx.Txid().ToBytes(), legacy_tx.Wtxid().ToBytes());
    +
    +    BOOST_CHECK_EQUAL(byte_span_to_hex_string_reversed(witness_tx.Txid().ToBytes()), "9d04c6435f39a114f26b5807e92117b388f15c54c2afe7e62c96757f18cec891");
    +    BOOST_CHECK_EQUAL(byte_span_to_hex_string_reversed(witness_tx.Wtxid().ToBytes()), "c5cd58b8eb3dda755eb99c89c4bcb7409e4074c8942c263e3d6d09011984a210");
    +    BOOST_CHECK(!std::ranges::equal(witness_tx.Txid().ToBytes(), witness_tx.Wtxid().ToBytes()));
    +}
    +
     BOOST_AUTO_TEST_CASE(btck_script_pubkey)
     {
         auto script_data{hex_string_to_byte_vec("76a9144bfbaf6afb76cc5771bc6404810d1cc041a6933988ac")};
    @@ -1202,12 +1229,6 @@ BOOST_AUTO_TEST_CASE(btck_chainman_regtest_tests)
         BOOST_CHECK(txid == txid);
         CheckHandle(txid, txid_2);
     
    -    Wtxid wtxid = read_block.Transactions()[0].Wtxid();
    -    Wtxid wtxid_2 = read_block_2.Transactions()[0].Wtxid();
    -    BOOST_CHECK(wtxid != wtxid_2);
    -    BOOST_CHECK(wtxid == wtxid);
    -    CheckHandle(wtxid, wtxid_2);
    -
         auto find_transaction = [&chainman](const TxidView& target_txid) -> std::optional<Transaction> {
             auto chain = chainman->GetChain();
             for (const auto block_tree_entry : chain.Entries()) {
    
    

    </details>


    KY-U commented at 7:31 PM on September 11, 2026:

    Nice, it is much better organized that way. I would just like to clarify my understanding of these specific asserts.

    From the diff:

    // Txid and Wtxid are distinct types and cannot be compared with each other
    static_assert(!std::equality_comparable_with<TxidView, WtxidView>);
    static_assert(!std::equality_comparable_with<Txid, Wtxid>);
    

    My understanding is that the comment implies these assertions guarantee both that Txid and Wtxid are distinct types and that no cross-type comparison expressions are valid. However, std::equality_comparable_with would remain false even if all cross-type == and != expressions became valid, as long as the types still lacked a common reference type. Is the purpose of this section simply to verify that Txid and Wtxid do not model std::equality_comparable_with, or is it to also enforce the stronger condition that no cross-type comparisons are possible?

    If the objective is to verify the stronger condition, I suppose the asserts would look similar to this:

    template <typename T, typename U>
    concept HasAnyEqualityComparison =
        requires(const T& t, const U& u) { t == u; } ||
        requires(const T& t, const U& u) { u == t; } ||
        requires(const T& t, const U& u) { t != u; } ||
        requires(const T& t, const U& u) { u != t; };
    
    static_assert(!std::same_as<TxidView, WtxidView>);
    static_assert(!std::same_as<Txid, Wtxid>);
    
    static_assert(!HasAnyEqualityComparison<TxidView, WtxidView>);
    static_assert(!HasAnyEqualityComparison<Txid, Wtxid>);
    

    I might be reading too much into it, though.


    stickies-v commented at 10:51 AM on September 15, 2026:

    However, std::equality_comparable_with would remain false even if all cross-type == and != expressions became valid, as long as the types still lacked a common reference type.

    Yeah, you're right, these tests aren't as strong as I thought they were, and your suggestion is better. std::is_constructible_v seems more appropriate than std::same_as.

  21. stickies-v approved
  22. stickies-v commented at 2:28 PM on September 11, 2026: contributor

    ACK 6599a99acf5944f5f2d39d81e156de6eb01aa56f

  23. KY-U force-pushed on Sep 14, 2026
  24. KY-U commented at 2:40 PM on September 14, 2026: contributor

    Grouped transaction identity tests into a new btck_transaction_id_tests test case.

  25. DrahtBot added the label CI failed on Sep 14, 2026
  26. DrahtBot commented at 4:18 PM on September 14, 2026: contributor

    <!--85328a0da195eb286784d51f73fa0af9-->

    🚧 At least one of the CI tasks failed. <sub>Task tidy: https://github.com/bitcoin/bitcoin/actions/runs/34856905031/job/104019073006</sub> <sub>LLM reason (✨ experimental): CI failed because clang-tidy (warnings-as-errors) reported performance-unnecessary-copy-initialization issues in src/test/kernel/test_kernel.cpp (unused copies of owned_txid/owned_wtxid).</sub>

    <details><summary>Hints</summary>

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

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

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

    • An intermittent issue.

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

    </details>

  27. sedited commented at 4:23 PM on September 14, 2026: contributor
    test_kernel.cpp:498:10: error: local copy 'owned_txid_copy' of the variable 'owned_txid' of type 'Txid' is never modified; consider avoiding the copy [performance-unnecessary-copy-initialization,-warnings-as-errors]
      498 |     Txid owned_txid_copy{owned_txid};
          |          ^
          |     const  &
    /home/runner/work/_temp/src/test/kernel/test_kernel.cpp:504:11: error: local copy 'owned_wtxid_copy' of the variable 'owned_wtxid' of type 'Wtxid' is never modified; consider avoiding the copy [performance-unnecessary-copy-initialization,-warnings-as-errors]
      504 |     Wtxid owned_wtxid_copy{owned_wtxid};
          |           ^
          |     const  &
    
    
  28. kernel: expose transaction wtxid
    Kernel clients can retrieve a transaction's txid but not its wtxid.
    Expose the cached witness transaction identifier through a distinct,
    type-safe C API handle.
    
    Mirror the existing txid API with borrowed and owned representations,
    copy and destroy operations, equality comparison, byte serialization,
    and corresponding C++ wrappers.
    
    Test that txid and wtxid bytes match for a legacy transaction and
    differ when witness data is present.
    31224d0680
  29. kernel: expose transaction witness presence
    Expose CTransaction::HasWitness(), allowing clients to determine whether
    a transaction contains witness data without inspecting each input.
    
    Test the accessor with legacy and witness transactions.
    43bba1ed74
  30. KY-U force-pushed on Sep 14, 2026
  31. KY-U commented at 6:13 PM on September 14, 2026: contributor
    test_kernel.cpp:498:10: error: local copy 'owned_txid_copy' of the variable 'owned_txid' of type 'Txid' is never modified; consider avoiding the copy [performance-unnecessary-copy-initialization,-warnings-as-errors]
      498 |     Txid owned_txid_copy{owned_txid};
          |          ^
          |     const  &
    /home/runner/work/_temp/src/test/kernel/test_kernel.cpp:504:11: error: local copy 'owned_wtxid_copy' of the variable 'owned_wtxid' of type 'Wtxid' is never modified; consider avoiding the copy [performance-unnecessary-copy-initialization,-warnings-as-errors]
      504 |     Wtxid owned_wtxid_copy{owned_wtxid};
          |           ^
          |     const  &
    

    Added NOLINT(performance-unnecessary-copy-initialization) to those lines since changing to const & as suggested would defeat the purpose of testing copied handles.

  32. stickies-v commented at 11:00 AM on September 15, 2026: contributor

    re-ACK 43bba1ed74f8301fe1ec8e3751cdc93d07b89e82

  33. DrahtBot requested review from ViniciusCestarii on Sep 15, 2026
  34. DrahtBot requested review from nervana21 on Sep 15, 2026
  35. sedited approved
  36. sedited commented at 11:42 AM on September 15, 2026: contributor

    ACK 43bba1ed74f8301fe1ec8e3751cdc93d07b89e82

  37. sedited merged this on Sep 15, 2026
  38. sedited closed this on Sep 15, 2026


github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-09-15 21:50 UTC

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