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:
<details>
<summary>git diff on ebc8433637</summary>
diff --git a/src/kernel/bitcoinkernel.cpp b/src/kernel/bitcoinkernel.cpp
index 2b129f2952..cdac10a05d 100644
--- a/src/kernel/bitcoinkernel.cpp
+++ b/src/kernel/bitcoinkernel.cpp
@@ -1393,9 +1393,7 @@ uint32_t btck_block_header_get_nonce(const btck_BlockHeader* header)
void btck_block_header_to_bytes(const btck_BlockHeader* header, unsigned char output[80])
{
- DataStream stream{};
- stream << btck_BlockHeader::get(header);
- std::memcpy(output, stream.data(), 80);
+ SpanWriter{std::span{output, 80}} << btck_BlockHeader::get(header);
}
void btck_block_header_destroy(btck_BlockHeader* header)
diff --git a/src/streams.h b/src/streams.h
index 466084e9fa..3358fa994a 100644
--- a/src/streams.h
+++ b/src/streams.h
@@ -77,6 +77,38 @@ private:
size_t nPos;
};
+/** Minimal stream for writing to an existing span of bytes.
+ */
+class SpanWriter
+{
+private:
+ std::span<std::byte> m_data;
+ size_t m_pos{0};
+
+public:
+ explicit SpanWriter(std::span<unsigned char> data) : m_data{std::as_writable_bytes(data)} {}
+ explicit SpanWriter(std::span<std::byte> data) : m_data{data} {}
+ template <typename... Args>
+ SpanWriter(std::span<unsigned char> data, Args&&... args) : SpanWriter{data}
+ {
+ ::SerializeMany(*this, std::forward<Args>(args)...);
+ }
+
+ void write(std::span<const std::byte> src)
+ {
+ assert(m_pos + src.size() <= m_data.size());
+ memcpy(m_data.data() + m_pos, src.data(), src.size());
+ m_pos += src.size();
+ }
+
+ template <typename T>
+ SpanWriter& operator<<(const T& obj)
+ {
+ ::Serialize(*this, obj);
+ return *this;
+ }
+};
+
/** Minimal stream for reading from an existing byte array by std::span.
*/
class SpanReader
diff --git a/src/test/streams_tests.cpp b/src/test/streams_tests.cpp
index af75ee987a..29fce0612d 100644
--- a/src/test/streams_tests.cpp
+++ b/src/test/streams_tests.cpp
@@ -207,6 +207,31 @@ 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<unsigned char, 8> arr{};
+
+ // Test operator<<.
+ SpanWriter writer{arr};
+ writer << a << b;
+ std::array<unsigned char, 8> expected1{{1, 2, 0, 0, 0, 0, 0, 0}};
+ BOOST_CHECK_EQUAL_COLLECTIONS(arr.begin(), arr.end(), expected1.begin(), expected1.end());
+
+ // Use variadic constructor and write to subspan.
+ SpanWriter{std::span{arr}.subspan(2), a, bytes, b};
+ std::array<unsigned char, 8> expected2{{1, 2, 1, 3, 4, 5, 6, 2}};
+ BOOST_CHECK_EQUAL_COLLECTIONS(arr.begin(), arr.end(), expected2.begin(), expected2.end());
+
+ // Test std::byte span constructor.
+ std::array<std::byte, 2> byte_arr{};
+ SpanWriter{std::span{byte_arr}} << a << b;
+ std::array<std::byte, 2> expected3{{std::byte{1}, std::byte{2}}};
+ BOOST_CHECK_EQUAL_COLLECTIONS(byte_arr.begin(), byte_arr.end(), expected3.begin(), expected3.end());
+}
+
BOOST_AUTO_TEST_CASE(streams_vector_reader)
{
std::vector<unsigned char> vch = {1, 255, 3, 4, 5, 6};
</details>
[@sedited](/bitcoin-bitcoin/contributor/sedited/) do you have any preferences here?