Skip to content

Commit 44ac0c3

Browse files
committed
Merge bitcoin/bitcoin#34401: kernel: add serialization method for btck_BlockHeader API
577a3e7 test: Add check for return type in `HasToBytes` concept (yuvicc) 1ad5512 kernel: Add Block Header serialization method (yuvicc) 8666262 Add `SpanWriter` class for zero-allocation stream writing (yuvicc) Pull request description: This adds serialization for `btck_BlockHeader` API. Also, updated the `CheckHandle` to compare the byte content instead of size. The changes here is done in two commits. First commit adds the `SpanWriter` class and next one moves the block header serialization to `SpanWriter`. See commit message for more details. Follow-up to #33822 . ACKs for top commit: stickies-v: re-ACK 577a3e7 alexanderwiederin: ACK bitcoin/bitcoin@577a3e7 theStack: Code-review ACK 577a3e7 w0xlt: ACK 577a3e7 Tree-SHA512: 1eda5b204588ccb23e9357f68c5529474e7d248736a371c47d8db71ba6ca95e121869514478ad7a519d190e4c30725f64fd1ef4dd9f97d2627dc4441e51458e0
2 parents 53f4743 + 577a3e7 commit 44ac0c3

6 files changed

Lines changed: 97 additions & 1 deletion

File tree

src/kernel/bitcoinkernel.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1423,6 +1423,16 @@ uint32_t btck_block_header_get_nonce(const btck_BlockHeader* header)
14231423
return btck_BlockHeader::get(header).nNonce;
14241424
}
14251425

1426+
int btck_block_header_to_bytes(const btck_BlockHeader* header, unsigned char output[80])
1427+
{
1428+
try {
1429+
SpanWriter{std::as_writable_bytes(std::span{output, 80})} << btck_BlockHeader::get(header);
1430+
return 0;
1431+
} catch (...) {
1432+
return -1;
1433+
}
1434+
}
1435+
14261436
void btck_block_header_destroy(btck_BlockHeader* header)
14271437
{
14281438
delete header;

src/kernel/bitcoinkernel.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1836,6 +1836,17 @@ BITCOINKERNEL_API int32_t BITCOINKERNEL_WARN_UNUSED_RESULT btck_block_header_get
18361836
BITCOINKERNEL_API uint32_t BITCOINKERNEL_WARN_UNUSED_RESULT btck_block_header_get_nonce(
18371837
const btck_BlockHeader* header) BITCOINKERNEL_ARG_NONNULL(1);
18381838

1839+
/**
1840+
* @brief Serializes the btck_BlockHeader to bytes.
1841+
* This is consensus serialization that is also used for the P2P network.
1842+
*
1843+
* @param[in] header Non-null.
1844+
* @param[out] output The serialized block header (80 bytes).
1845+
* @return 0 on success.
1846+
*/
1847+
BITCOINKERNEL_API int BITCOINKERNEL_WARN_UNUSED_RESULT btck_block_header_to_bytes(
1848+
const btck_BlockHeader* header, unsigned char output[80]) BITCOINKERNEL_ARG_NONNULL(1, 2);
1849+
18391850
/**
18401851
* Destroy the btck_BlockHeader.
18411852
*/

src/kernel/bitcoinkernel_wrapper.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -768,6 +768,16 @@ class BlockHeaderApi
768768
{
769769
return btck_block_header_get_nonce(impl());
770770
}
771+
772+
std::array<std::byte, 80> ToBytes() const
773+
{
774+
std::array<std::byte, 80> header;
775+
int res{btck_block_header_to_bytes(impl(), reinterpret_cast<unsigned char*>(header.data()))};
776+
if (res != 0) {
777+
throw std::runtime_error("Failed to serialize block header");
778+
}
779+
return header;
780+
}
771781
};
772782

773783
class BlockHeaderView : public View<btck_BlockHeader>, public BlockHeaderApi<BlockHeaderView>

src/streams.h

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,38 @@ class SpanReader
124124
}
125125
};
126126

127+
/** Minimal stream for writing to an existing span of bytes.
128+
*/
129+
class SpanWriter
130+
{
131+
private:
132+
std::span<std::byte> m_dest;
133+
134+
public:
135+
explicit SpanWriter(std::span<std::byte> dest) : m_dest{dest} {}
136+
template <typename... Args>
137+
SpanWriter(std::span<std::byte> dest, Args&&... args) : SpanWriter{dest}
138+
{
139+
::SerializeMany(*this, std::forward<Args>(args)...);
140+
}
141+
142+
void write(std::span<const std::byte> src)
143+
{
144+
if (src.size() > m_dest.size()) {
145+
throw std::ios_base::failure("SpanWriter::write(): exceeded buffer size");
146+
}
147+
memcpy(m_dest.data(), src.data(), src.size());
148+
m_dest = m_dest.subspan(src.size());
149+
}
150+
151+
template<typename T>
152+
SpanWriter& operator<<(const T& obj)
153+
{
154+
::Serialize(*this, obj);
155+
return *this;
156+
}
157+
};
158+
127159
/** Double ended buffer combining vector and stream-like interfaces.
128160
*
129161
* >> and << read and write unformatted data using the above serialization templates.

src/test/kernel/test_kernel.cpp

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,9 @@ void run_verify_test(
266266
}
267267

268268
template <typename T>
269-
concept HasToBytes = requires(T t) { t.ToBytes(); };
269+
concept HasToBytes = requires(T t) {
270+
{ t.ToBytes() } -> std::convertible_to<std::span<const std::byte>>;
271+
};
270272

271273
template <typename T>
272274
void CheckHandle(T object, T distinct_object)
@@ -686,6 +688,10 @@ BOOST_AUTO_TEST_CASE(btck_block_header_tests)
686688
auto prev_hash = header.PrevHash();
687689
BOOST_CHECK_EQUAL(byte_span_to_hex_string_reversed(prev_hash.ToBytes()), "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f");
688690

691+
// Test round-trip serialization of block header
692+
auto header_roundtrip{BlockHeader{header.ToBytes()}};
693+
check_equal(header_roundtrip.ToBytes(), mainnet_block_1_header);
694+
689695
auto raw_block = hex_string_to_byte_vec("010000006fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000982051fd1e4ba744bbbe680e1fee14677ba1a3c3540bf7b1cdb606e857233e0e61bc6649ffff001d01e362990101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d0104ffffffff0100f2052a0100000043410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac00000000");
690696
Block block{raw_block};
691697
BlockHeader block_header{block.GetHeader()};
@@ -694,6 +700,11 @@ BOOST_AUTO_TEST_CASE(btck_block_header_tests)
694700
BOOST_CHECK_EQUAL(block_header.Bits(), 0x1d00ffff);
695701
BOOST_CHECK_EQUAL(block_header.Nonce(), 2573394689);
696702
BOOST_CHECK_EQUAL(byte_span_to_hex_string_reversed(block_header.Hash().ToBytes()), "00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048");
703+
704+
// Verify header from block serializes to first 80 bytes of raw block
705+
auto block_header_bytes = block_header.ToBytes();
706+
BOOST_CHECK_EQUAL(block_header_bytes.size(), 80);
707+
check_equal(block_header_bytes, std::span<const std::byte>(raw_block.data(), 80));
697708
}
698709

699710
BOOST_AUTO_TEST_CASE(btck_block)

src/test/streams_tests.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,28 @@ BOOST_AUTO_TEST_CASE(streams_vector_writer)
208208
vch.clear();
209209
}
210210

211+
BOOST_AUTO_TEST_CASE(streams_span_writer)
212+
{
213+
unsigned char a(1);
214+
unsigned char b(2);
215+
unsigned char bytes[] = {3, 4, 5, 6};
216+
std::array<std::byte, 8> arr{};
217+
218+
// Test operator<<
219+
SpanWriter writer{arr};
220+
writer << a << b;
221+
BOOST_CHECK_EQUAL(HexStr(arr), "0102000000000000");
222+
223+
// Use variadic constructor and write to subspan.
224+
SpanWriter{std::span{arr}.subspan(2), a, bytes, b};
225+
BOOST_CHECK_EQUAL(HexStr(arr), "0102010304050602");
226+
227+
// Writing past the end throws
228+
std::array<std::byte, 1> small{};
229+
BOOST_CHECK_THROW(SpanWriter(std::span{small}, a, b), std::ios_base::failure);
230+
BOOST_CHECK_THROW(SpanWriter(std::span{small}) << a << b, std::ios_base::failure);
231+
}
232+
211233
BOOST_AUTO_TEST_CASE(streams_vector_reader)
212234
{
213235
std::vector<unsigned char> vch = {1, 255, 3, 4, 5, 6};

0 commit comments

Comments
 (0)