kernel: add serialization method for btck_BlockHeader API #34401

pull yuvicc wants to merge 1 commits into bitcoin:master from yuvicc:2026-11-followup_33822 changing 4 files +35 −2
  1. yuvicc commented at 6:50 am on January 25, 2026: contributor

    This adds serialization for btck_BlockHeader API. Also, updated the CheckHandle to compare the byte content instead of size.

    Follow-up to #33822 .

  2. DrahtBot added the label Validation on Jan 25, 2026
  3. DrahtBot commented at 6:50 am on January 25, 2026: 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/34401.

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    Concept ACK stickies-v

    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:

    • #34488 (refactor: Small style and test fixups for bitcoinkernel by maflcko)

    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.

    LLM Linter (✨ experimental)

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

    • std::memcpy(output, stream.data(), 80) in src/kernel/bitcoinkernel.cpp

    2026-01-31 14:14:24

  4. yuvicc renamed this:
    kernel: add serialization for btck_BlockHeader API
    kernel: add serialization method for btck_BlockHeader API
    on Jan 25, 2026
  5. in src/kernel/bitcoinkernel.h:1772 in d14cdea5cf outdated
    1767@@ -1768,6 +1768,20 @@ BITCOINKERNEL_API int32_t BITCOINKERNEL_WARN_UNUSED_RESULT btck_block_header_get
    1768 BITCOINKERNEL_API uint32_t BITCOINKERNEL_WARN_UNUSED_RESULT btck_block_header_get_nonce(
    1769     const btck_BlockHeader* header) BITCOINKERNEL_ARG_NONNULL(1);
    1770 
    1771+/**
    1772+ * @brief Serializes the btck_BlockHeader through the passed in callback to bytes.
    


    stickies-v commented at 3:58 pm on January 27, 2026:

    For consistency with other serialization function documentation, would be good to add

    This is consensus serialization that is also used for the P2P network.

  6. in src/kernel/bitcoinkernel.h:1780 in d14cdea5cf
    1775+ * @param[in] writer    Non-null, callback to a write bytes function.
    1776+ * @param[in] user_data Holds a user-defined opaque structure that will be
    1777+ *                      passed back through the writer callback.
    1778+ * @return              0 on success.
    1779+ */
    1780+BITCOINKERNEL_API int btck_block_header_to_bytes(
    


    stickies-v commented at 3:59 pm on January 27, 2026:

    Headers are fixed 80 byte structures, so I think we can just return a fixed-length buffer instead of using a writer - see e.g. btck_block_hash_to_bytes.

     0diff --git a/src/kernel/bitcoinkernel.cpp b/src/kernel/bitcoinkernel.cpp
     1index 525d861827..2fd5a51873 100644
     2--- a/src/kernel/bitcoinkernel.cpp
     3+++ b/src/kernel/bitcoinkernel.cpp
     4@@ -1389,15 +1389,11 @@ uint32_t btck_block_header_get_nonce(const btck_BlockHeader* header)
     5     return btck_BlockHeader::get(header).nNonce;
     6 }
     7 
     8-int btck_block_header_to_bytes(const btck_BlockHeader* header, btck_WriteBytes writer, void* user_data)
     9+void btck_block_header_to_bytes(const btck_BlockHeader* header, unsigned char output[80])
    10 {
    11-    try {
    12-        WriterStream ws{writer, user_data};
    13-        ws << btck_BlockHeader::get(header);
    14-        return 0;
    15-    } catch (...) {
    16-        return -1;
    17-    }
    18+    DataStream stream{};
    19+    stream << btck_BlockHeader::get(header);
    20+    std::memcpy(output, stream.data(), 80);
    21 }
    22 
    23 void btck_block_header_destroy(btck_BlockHeader* header)
    24diff --git a/src/kernel/bitcoinkernel.h b/src/kernel/bitcoinkernel.h
    25index 53491ea69e..4f6ae41c61 100644
    26--- a/src/kernel/bitcoinkernel.h
    27+++ b/src/kernel/bitcoinkernel.h
    28@@ -1769,18 +1769,13 @@ BITCOINKERNEL_API uint32_t BITCOINKERNEL_WARN_UNUSED_RESULT btck_block_header_ge
    29     const btck_BlockHeader* header) BITCOINKERNEL_ARG_NONNULL(1);
    30 
    31 /**
    32- * [@brief](/bitcoin-bitcoin/contributor/brief/) Serializes the btck_BlockHeader through the passed in callback to bytes.
    33+ * [@brief](/bitcoin-bitcoin/contributor/brief/) Serializes the btck_BlockHeader to bytes.
    34  *
    35  * [@param](/bitcoin-bitcoin/contributor/param/)[in] header    Non-null.
    36- * [@param](/bitcoin-bitcoin/contributor/param/)[in] writer    Non-null, callback to a write bytes function.
    37- * [@param](/bitcoin-bitcoin/contributor/param/)[in] user_data Holds a user-defined opaque structure that will be
    38- *                      passed back through the writer callback.
    39- * [@return](/bitcoin-bitcoin/contributor/return/)              0 on success.
    40+ * [@param](/bitcoin-bitcoin/contributor/param/)[out] output   The serialized block header (80 bytes).
    41  */
    42-BITCOINKERNEL_API int btck_block_header_to_bytes(
    43-    const btck_BlockHeader* header,
    44-    btck_WriteBytes writer,
    45-    void* user_data) BITCOINKERNEL_ARG_NONNULL(1, 2);
    46+BITCOINKERNEL_API void btck_block_header_to_bytes(
    47+    const btck_BlockHeader* header, unsigned char output[80]) BITCOINKERNEL_ARG_NONNULL(1, 2);
    48 
    49 /**
    50  * Destroy the btck_BlockHeader.
    51diff --git a/src/kernel/bitcoinkernel_wrapper.h b/src/kernel/bitcoinkernel_wrapper.h
    52index c23ad8ba3b..95afd90480 100644
    53--- a/src/kernel/bitcoinkernel_wrapper.h
    54+++ b/src/kernel/bitcoinkernel_wrapper.h
    55@@ -747,9 +747,11 @@ public:
    56         return btck_block_header_get_nonce(impl());
    57     }
    58 
    59-    std::vector<std::byte> ToBytes() const
    60+    std::array<std::byte, 80> ToBytes() const
    61     {
    62-        return write_bytes(impl(), btck_block_header_to_bytes);
    63+        std::array<std::byte, 80> header;
    64+        btck_block_header_to_bytes(impl(), reinterpret_cast<unsigned char*>(header.data()));
    65+        return header;
    66     }
    67 };
    68 
    69diff --git a/src/test/kernel/test_kernel.cpp b/src/test/kernel/test_kernel.cpp
    70index 86c2e25057..79c88c94c7 100644
    71--- a/src/test/kernel/test_kernel.cpp
    72+++ b/src/test/kernel/test_kernel.cpp
    73@@ -266,7 +266,7 @@ void run_verify_test(
    74 
    75 template <typename T>
    76 concept HasToBytes = requires(T t) {
    77-    { t.ToBytes() } -> std::convertible_to<std::vector<std::byte>>;
    78+    { t.ToBytes() } -> std::convertible_to<std::span<const std::byte>>;
    79 };
    80 
    81 template <typename T>
    

    yuvicc commented at 7:47 am on January 31, 2026:
    Makes sense, thanks.
  7. stickies-v commented at 4:47 pm on January 27, 2026: contributor
    Concept ACK, definitely useful to have.
  8. yuvicc force-pushed on Jan 31, 2026
  9. DrahtBot added the label CI failed on Jan 31, 2026
  10. DrahtBot commented at 9:13 am on January 31, 2026: contributor

    🚧 At least one of the CI tasks failed. Task lint: https://github.com/bitcoin/bitcoin/actions/runs/21541544208/job/62076715283 LLM reason (✨ experimental): Linting failed due to include-usage warnings (lint-includes.py) causing the all_python_linters check to fail.

    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.

  11. add: serialization method for block header in the kernel api. Also, fix CheckHandle to compare the byte content instead of sizes. ebc8433637
  12. yuvicc force-pushed on Jan 31, 2026
  13. DrahtBot removed the label CI failed on Jan 31, 2026
  14. yuvicc commented at 3:38 pm on January 31, 2026: contributor

    Thanks for review @stickies-v.

    • return fixed length buffer instead of writer comment
    • Addressed nit
  15. in src/kernel/bitcoinkernel.cpp:1399 in ebc8433637
    1390@@ -1391,6 +1391,13 @@ uint32_t btck_block_header_get_nonce(const btck_BlockHeader* header)
    1391     return btck_BlockHeader::get(header).nNonce;
    1392 }
    1393 
    1394+void btck_block_header_to_bytes(const btck_BlockHeader* header, unsigned char output[80])
    1395+{
    1396+    DataStream stream{};
    1397+    stream << btck_BlockHeader::get(header);
    1398+    std::memcpy(output, stream.data(), 80);
    1399+}
    


    stickies-v commented at 12:03 pm on February 2, 2026:

    Sorry, my previous suggestion wasn’t entirely sound. This function can throw an exception that crosses the C boundary (e.g. if memory allocation fails), so we should better handle this.

    Some options are:

    1. Wrapping this in a try/catch and returning an int status code
    2. Reverting to the WriterStream approach, which adds some unnecessary overhead
    3. Implement a SpanWriter to avoid allocating memory. I’m not sure about the guarantees we have that serializing the block header will never throw, though, so this might be in addition to Option 1 (but still has the benefit of avoiding allocation)

    Possible SpanWriter approach:

     0diff --git a/src/kernel/bitcoinkernel.cpp b/src/kernel/bitcoinkernel.cpp
     1index 2b129f2952..cdac10a05d 100644
     2--- a/src/kernel/bitcoinkernel.cpp
     3+++ b/src/kernel/bitcoinkernel.cpp
     4@@ -1393,9 +1393,7 @@ uint32_t btck_block_header_get_nonce(const btck_BlockHeader* header)
     5 
     6 void btck_block_header_to_bytes(const btck_BlockHeader* header, unsigned char output[80])
     7 {
     8-    DataStream stream{};
     9-    stream << btck_BlockHeader::get(header);
    10-    std::memcpy(output, stream.data(), 80);
    11+    SpanWriter{std::span{output, 80}} << btck_BlockHeader::get(header);
    12 }
    13 
    14 void btck_block_header_destroy(btck_BlockHeader* header)
    15diff --git a/src/streams.h b/src/streams.h
    16index 466084e9fa..3358fa994a 100644
    17--- a/src/streams.h
    18+++ b/src/streams.h
    19@@ -77,6 +77,38 @@ private:
    20     size_t nPos;
    21 };
    22 
    23+/** Minimal stream for writing to an existing span of bytes.
    24+ */
    25+class SpanWriter
    26+{
    27+private:
    28+    std::span<std::byte> m_data;
    29+    size_t m_pos{0};
    30+
    31+public:
    32+    explicit SpanWriter(std::span<unsigned char> data) : m_data{std::as_writable_bytes(data)} {}
    33+    explicit SpanWriter(std::span<std::byte> data) : m_data{data} {}
    34+    template <typename... Args>
    35+    SpanWriter(std::span<unsigned char> data, Args&&... args) : SpanWriter{data}
    36+    {
    37+        ::SerializeMany(*this, std::forward<Args>(args)...);
    38+    }
    39+
    40+    void write(std::span<const std::byte> src)
    41+    {
    42+        assert(m_pos + src.size() <= m_data.size());
    43+        memcpy(m_data.data() + m_pos, src.data(), src.size());
    44+        m_pos += src.size();
    45+    }
    46+
    47+    template <typename T>
    48+    SpanWriter& operator<<(const T& obj)
    49+    {
    50+        ::Serialize(*this, obj);
    51+        return *this;
    52+    }
    53+};
    54+
    55 /** Minimal stream for reading from an existing byte array by std::span.
    56  */
    57 class SpanReader
    58diff --git a/src/test/streams_tests.cpp b/src/test/streams_tests.cpp
    59index af75ee987a..29fce0612d 100644
    60--- a/src/test/streams_tests.cpp
    61+++ b/src/test/streams_tests.cpp
    62@@ -207,6 +207,31 @@ BOOST_AUTO_TEST_CASE(streams_vector_writer)
    63     vch.clear();
    64 }
    65 
    66+BOOST_AUTO_TEST_CASE(streams_span_writer)
    67+{
    68+    unsigned char a(1);
    69+    unsigned char b(2);
    70+    unsigned char bytes[] = {3, 4, 5, 6};
    71+    std::array<unsigned char, 8> arr{};
    72+
    73+    // Test operator<<.
    74+    SpanWriter writer{arr};
    75+    writer << a << b;
    76+    std::array<unsigned char, 8> expected1{{1, 2, 0, 0, 0, 0, 0, 0}};
    77+    BOOST_CHECK_EQUAL_COLLECTIONS(arr.begin(), arr.end(), expected1.begin(), expected1.end());
    78+
    79+    // Use variadic constructor and write to subspan.
    80+    SpanWriter{std::span{arr}.subspan(2), a, bytes, b};
    81+    std::array<unsigned char, 8> expected2{{1, 2, 1, 3, 4, 5, 6, 2}};
    82+    BOOST_CHECK_EQUAL_COLLECTIONS(arr.begin(), arr.end(), expected2.begin(), expected2.end());
    83+
    84+    // Test std::byte span constructor.
    85+    std::array<std::byte, 2> byte_arr{};
    86+    SpanWriter{std::span{byte_arr}} << a << b;
    87+    std::array<std::byte, 2> expected3{{std::byte{1}, std::byte{2}}};
    88+    BOOST_CHECK_EQUAL_COLLECTIONS(byte_arr.begin(), byte_arr.end(), expected3.begin(), expected3.end());
    89+}
    90+
    91 BOOST_AUTO_TEST_CASE(streams_vector_reader)
    92 {
    93     std::vector<unsigned char> vch = {1, 255, 3, 4, 5, 6};
    

    yuvicc commented at 5:41 pm on February 2, 2026:
    I think as a defensive mechanism, we should still use try-catch here to ensure exceptions never cross the C API boundary, even though header serialization shouldn’t fail under normal circumstances.
  16. DrahtBot added the label Needs rebase on Feb 4, 2026
  17. DrahtBot commented at 3:41 pm on February 4, 2026: contributor
    🐙 This pull request conflicts with the target branch and needs 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-02-19 03:13 UTC

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