From 86662623ec2b4af15e181a02ac57fade26bdb3a6 Mon Sep 17 00:00:00 2001 From: yuvicc Date: Wed, 1 Apr 2026 20:11:57 +0530 Subject: [PATCH 1/3] Add `SpanWriter` class for zero-allocation stream writing Co-authored-by: stickies-v --- src/streams.h | 32 ++++++++++++++++++++++++++++++++ src/test/streams_tests.cpp | 22 ++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/src/streams.h b/src/streams.h index 466084e9fa0..12a2cac2c79 100644 --- a/src/streams.h +++ b/src/streams.h @@ -121,6 +121,38 @@ public: } }; +/** Minimal stream for writing to an existing span of bytes. + */ +class SpanWriter +{ +private: + std::span m_dest; + +public: + explicit SpanWriter(std::span dest) : m_dest{dest} {} + template + SpanWriter(std::span dest, Args&&... args) : SpanWriter{dest} + { + ::SerializeMany(*this, std::forward(args)...); + } + + void write(std::span src) + { + if (src.size() > m_dest.size()) { + throw std::ios_base::failure("SpanWriter::write(): exceeded buffer size"); + } + memcpy(m_dest.data(), src.data(), src.size()); + m_dest = m_dest.subspan(src.size()); + } + + template + SpanWriter& operator<<(const T& obj) + { + ::Serialize(*this, obj); + return *this; + } +}; + /** Double ended buffer combining vector and stream-like interfaces. * * >> and << read and write unformatted data using the above serialization templates. diff --git a/src/test/streams_tests.cpp b/src/test/streams_tests.cpp index af75ee987ad..cb1ca6eeddf 100644 --- a/src/test/streams_tests.cpp +++ b/src/test/streams_tests.cpp @@ -207,6 +207,28 @@ BOOST_AUTO_TEST_CASE(streams_vector_writer) vch.clear(); } +BOOST_AUTO_TEST_CASE(streams_span_writer) +{ + unsigned char a(1); + unsigned char b(2); + unsigned char bytes[] = {3, 4, 5, 6}; + std::array arr{}; + + // Test operator<< + SpanWriter writer{arr}; + writer << a << b; + BOOST_CHECK_EQUAL(HexStr(arr), "0102000000000000"); + + // Use variadic constructor and write to subspan. + SpanWriter{std::span{arr}.subspan(2), a, bytes, b}; + BOOST_CHECK_EQUAL(HexStr(arr), "0102010304050602"); + + // Writing past the end throws + std::array small{}; + BOOST_CHECK_THROW(SpanWriter(std::span{small}, a, b), std::ios_base::failure); + BOOST_CHECK_THROW(SpanWriter(std::span{small}) << a << b, std::ios_base::failure); +} + BOOST_AUTO_TEST_CASE(streams_vector_reader) { std::vector vch = {1, 255, 3, 4, 5, 6}; From 1ad551281aaa481efe88a08003fcdda0de96a06e Mon Sep 17 00:00:00 2001 From: yuvicc Date: Wed, 1 Apr 2026 20:12:39 +0530 Subject: [PATCH 2/3] kernel: Add Block Header serialization method Add `btck_block_header_to_bytes` serialization method to serialize a `btck_BlockHeader` into an 80-byte buffer using `SpanWriter` to ensure zero-allocation serialization. --- src/kernel/bitcoinkernel.cpp | 10 ++++++++++ src/kernel/bitcoinkernel.h | 11 +++++++++++ src/kernel/bitcoinkernel_wrapper.h | 10 ++++++++++ src/test/kernel/test_kernel.cpp | 9 +++++++++ 4 files changed, 40 insertions(+) diff --git a/src/kernel/bitcoinkernel.cpp b/src/kernel/bitcoinkernel.cpp index ea646cd551f..b456f41a002 100644 --- a/src/kernel/bitcoinkernel.cpp +++ b/src/kernel/bitcoinkernel.cpp @@ -1390,6 +1390,16 @@ uint32_t btck_block_header_get_nonce(const btck_BlockHeader* header) return btck_BlockHeader::get(header).nNonce; } +int btck_block_header_to_bytes(const btck_BlockHeader* header, unsigned char output[80]) +{ + try { + SpanWriter{std::as_writable_bytes(std::span{output, 80})} << btck_BlockHeader::get(header); + return 0; + } catch (...) { + return -1; + } +} + void btck_block_header_destroy(btck_BlockHeader* header) { delete header; diff --git a/src/kernel/bitcoinkernel.h b/src/kernel/bitcoinkernel.h index 5427e776177..f3a85cf2221 100644 --- a/src/kernel/bitcoinkernel.h +++ b/src/kernel/bitcoinkernel.h @@ -1768,6 +1768,17 @@ BITCOINKERNEL_API int32_t BITCOINKERNEL_WARN_UNUSED_RESULT btck_block_header_get BITCOINKERNEL_API uint32_t BITCOINKERNEL_WARN_UNUSED_RESULT btck_block_header_get_nonce( const btck_BlockHeader* header) BITCOINKERNEL_ARG_NONNULL(1); +/** + * @brief Serializes the btck_BlockHeader to bytes. + * This is consensus serialization that is also used for the P2P network. + * + * @param[in] header Non-null. + * @param[out] output The serialized block header (80 bytes). + * @return 0 on success. + */ +BITCOINKERNEL_API int BITCOINKERNEL_WARN_UNUSED_RESULT btck_block_header_to_bytes( + const btck_BlockHeader* header, unsigned char output[80]) BITCOINKERNEL_ARG_NONNULL(1, 2); + /** * Destroy the btck_BlockHeader. */ diff --git a/src/kernel/bitcoinkernel_wrapper.h b/src/kernel/bitcoinkernel_wrapper.h index 064d0dd14b9..63ca62b0ca4 100644 --- a/src/kernel/bitcoinkernel_wrapper.h +++ b/src/kernel/bitcoinkernel_wrapper.h @@ -746,6 +746,16 @@ public: { return btck_block_header_get_nonce(impl()); } + + std::array ToBytes() const + { + std::array header; + int res{btck_block_header_to_bytes(impl(), reinterpret_cast(header.data()))}; + if (res != 0) { + throw std::runtime_error("Failed to serialize block header"); + } + return header; + } }; class BlockHeaderView : public View, public BlockHeaderApi diff --git a/src/test/kernel/test_kernel.cpp b/src/test/kernel/test_kernel.cpp index 5a380065397..5958b8d41e6 100644 --- a/src/test/kernel/test_kernel.cpp +++ b/src/test/kernel/test_kernel.cpp @@ -682,6 +682,10 @@ BOOST_AUTO_TEST_CASE(btck_block_header_tests) auto prev_hash = header.PrevHash(); BOOST_CHECK_EQUAL(byte_span_to_hex_string_reversed(prev_hash.ToBytes()), "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"); + // Test round-trip serialization of block header + auto header_roundtrip{BlockHeader{header.ToBytes()}}; + check_equal(header_roundtrip.ToBytes(), mainnet_block_1_header); + auto raw_block = hex_string_to_byte_vec("010000006fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000982051fd1e4ba744bbbe680e1fee14677ba1a3c3540bf7b1cdb606e857233e0e61bc6649ffff001d01e362990101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d0104ffffffff0100f2052a0100000043410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac00000000"); Block block{raw_block}; BlockHeader block_header{block.GetHeader()}; @@ -690,6 +694,11 @@ BOOST_AUTO_TEST_CASE(btck_block_header_tests) BOOST_CHECK_EQUAL(block_header.Bits(), 0x1d00ffff); BOOST_CHECK_EQUAL(block_header.Nonce(), 2573394689); BOOST_CHECK_EQUAL(byte_span_to_hex_string_reversed(block_header.Hash().ToBytes()), "00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048"); + + // Verify header from block serializes to first 80 bytes of raw block + auto block_header_bytes = block_header.ToBytes(); + BOOST_CHECK_EQUAL(block_header_bytes.size(), 80); + check_equal(block_header_bytes, std::span(raw_block.data(), 80)); } BOOST_AUTO_TEST_CASE(btck_block) From 577a3e74c82d942cba24c2b3affda9c777f3c75d Mon Sep 17 00:00:00 2001 From: yuvicc Date: Wed, 1 Apr 2026 20:12:58 +0530 Subject: [PATCH 3/3] test: Add check for return type in `HasToBytes` concept Add return type check for `ToBytes()` which should be convertible to `std::span`. --- src/test/kernel/test_kernel.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/test/kernel/test_kernel.cpp b/src/test/kernel/test_kernel.cpp index 5958b8d41e6..e43954de730 100644 --- a/src/test/kernel/test_kernel.cpp +++ b/src/test/kernel/test_kernel.cpp @@ -265,7 +265,9 @@ void run_verify_test( } template -concept HasToBytes = requires(T t) { t.ToBytes(); }; +concept HasToBytes = requires(T t) { + { t.ToBytes() } -> std::convertible_to>; +}; template void CheckHandle(T object, T distinct_object)