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:
- Wrapping this in a try/catch and returning an int status code
- Reverting to the
WriterStream approach, which adds some unnecessary overhead
- 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};