diff --git a/src/kernel/bitcoinkernel.cpp b/src/kernel/bitcoinkernel.cpp index 3295fe153f2..dc03be71461 100644 --- a/src/kernel/bitcoinkernel.cpp +++ b/src/kernel/bitcoinkernel.cpp @@ -1423,6 +1423,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 accd5617aa0..d3028b491d2 100644 --- a/src/kernel/bitcoinkernel.h +++ b/src/kernel/bitcoinkernel.h @@ -1836,6 +1836,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 269bcdc1c9e..cb7af8b35dd 100644 --- a/src/kernel/bitcoinkernel_wrapper.h +++ b/src/kernel/bitcoinkernel_wrapper.h @@ -768,6 +768,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/streams.h b/src/streams.h index f70adcf74a7..d6d6d57fb93 100644 --- a/src/streams.h +++ b/src/streams.h @@ -124,6 +124,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/kernel/test_kernel.cpp b/src/test/kernel/test_kernel.cpp index 2c20a66011f..b22a5edede9 100644 --- a/src/test/kernel/test_kernel.cpp +++ b/src/test/kernel/test_kernel.cpp @@ -266,7 +266,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) @@ -686,6 +688,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()}; @@ -694,6 +700,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) diff --git a/src/test/streams_tests.cpp b/src/test/streams_tests.cpp index dfddc415b50..6a6026bf410 100644 --- a/src/test/streams_tests.cpp +++ b/src/test/streams_tests.cpp @@ -208,6 +208,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};