Skip to content

Commit 727857d

Browse files
committed
Merge #18112: Serialization improvements step 5 (blockencodings)
353f376 Convert blockencodings.h to new serialization framework (Pieter Wuille) e574fff Add CustomUintFormatter (Pieter Wuille) 1063339 Add DifferenceFormatter (Russell Yanofsky) 56dd9f0 Make VectorFormatter support stateful formatters (Russell Yanofsky) 3ca574c Convert CCompactSize to proper formatter (Pieter Wuille) Pull request description: This is probably the most involved change in the sequence of changes extracted from #10785. In order to implement the differential encoding of BIP152, this change changes `VectorFormatter` to permit a stateful sub-formatter, which is then used by `DifferenceFormatter`. A `CustomUintFormatter` is added as well to do the 48-bit serialization of short ids. ACKs for top commit: laanwj: ACK 353f376, nice change ryanofsky: Code review ACK 353f376. Only changes since last review are suggested assert change and MASK->MAX rename Tree-SHA512: 976618991a8be62ba0738725b7cfa166a56cde998ebf1031ba6f28557032f1577b666ac7ae25cd498c0e1e740108c3c56a342620b724df41d6cc9d8bdafac037
2 parents aaf0946 + 353f376 commit 727857d

File tree

3 files changed

+87
-126
lines changed

3 files changed

+87
-126
lines changed

src/blockencodings.h

Lines changed: 34 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,29 @@
1010

1111
class CTxMemPool;
1212

13-
// Dumb helper to handle CTransaction compression at serialize-time
14-
struct TransactionCompressor {
15-
private:
16-
CTransactionRef& tx;
17-
public:
18-
explicit TransactionCompressor(CTransactionRef& txIn) : tx(txIn) {}
13+
// Transaction compression schemes for compact block relay can be introduced by writing
14+
// an actual formatter here.
15+
using TransactionCompression = DefaultFormatter;
1916

20-
ADD_SERIALIZE_METHODS;
17+
class DifferenceFormatter
18+
{
19+
uint64_t m_shift = 0;
2120

22-
template <typename Stream, typename Operation>
23-
inline void SerializationOp(Stream& s, Operation ser_action) {
24-
READWRITE(tx); //TODO: Compress tx encoding
21+
public:
22+
template<typename Stream, typename I>
23+
void Ser(Stream& s, I v)
24+
{
25+
if (v < m_shift || v >= std::numeric_limits<uint64_t>::max()) throw std::ios_base::failure("differential value overflow");
26+
WriteCompactSize(s, v - m_shift);
27+
m_shift = uint64_t(v) + 1;
28+
}
29+
template<typename Stream, typename I>
30+
void Unser(Stream& s, I& v)
31+
{
32+
uint64_t n = ReadCompactSize(s);
33+
m_shift += n;
34+
if (m_shift < n || m_shift >= std::numeric_limits<uint64_t>::max() || m_shift < std::numeric_limits<I>::min() || m_shift > std::numeric_limits<I>::max()) throw std::ios_base::failure("differential value overflow");
35+
v = I(m_shift++);
2536
}
2637
};
2738

@@ -31,39 +42,9 @@ class BlockTransactionsRequest {
3142
uint256 blockhash;
3243
std::vector<uint16_t> indexes;
3344

34-
ADD_SERIALIZE_METHODS;
35-
36-
template <typename Stream, typename Operation>
37-
inline void SerializationOp(Stream& s, Operation ser_action) {
38-
READWRITE(blockhash);
39-
uint64_t indexes_size = (uint64_t)indexes.size();
40-
READWRITE(COMPACTSIZE(indexes_size));
41-
if (ser_action.ForRead()) {
42-
size_t i = 0;
43-
while (indexes.size() < indexes_size) {
44-
indexes.resize(std::min((uint64_t)(1000 + indexes.size()), indexes_size));
45-
for (; i < indexes.size(); i++) {
46-
uint64_t index = 0;
47-
READWRITE(COMPACTSIZE(index));
48-
if (index > std::numeric_limits<uint16_t>::max())
49-
throw std::ios_base::failure("index overflowed 16 bits");
50-
indexes[i] = index;
51-
}
52-
}
53-
54-
int32_t offset = 0;
55-
for (size_t j = 0; j < indexes.size(); j++) {
56-
if (int32_t(indexes[j]) + offset > std::numeric_limits<uint16_t>::max())
57-
throw std::ios_base::failure("indexes overflowed 16 bits");
58-
indexes[j] = indexes[j] + offset;
59-
offset = int32_t(indexes[j]) + 1;
60-
}
61-
} else {
62-
for (size_t i = 0; i < indexes.size(); i++) {
63-
uint64_t index = indexes[i] - (i == 0 ? 0 : (indexes[i - 1] + 1));
64-
READWRITE(COMPACTSIZE(index));
65-
}
66-
}
45+
SERIALIZE_METHODS(BlockTransactionsRequest, obj)
46+
{
47+
READWRITE(obj.blockhash, Using<VectorFormatter<DifferenceFormatter>>(obj.indexes));
6748
}
6849
};
6950

@@ -77,24 +58,9 @@ class BlockTransactions {
7758
explicit BlockTransactions(const BlockTransactionsRequest& req) :
7859
blockhash(req.blockhash), txn(req.indexes.size()) {}
7960

80-
ADD_SERIALIZE_METHODS;
81-
82-
template <typename Stream, typename Operation>
83-
inline void SerializationOp(Stream& s, Operation ser_action) {
84-
READWRITE(blockhash);
85-
uint64_t txn_size = (uint64_t)txn.size();
86-
READWRITE(COMPACTSIZE(txn_size));
87-
if (ser_action.ForRead()) {
88-
size_t i = 0;
89-
while (txn.size() < txn_size) {
90-
txn.resize(std::min((uint64_t)(1000 + txn.size()), txn_size));
91-
for (; i < txn.size(); i++)
92-
READWRITE(TransactionCompressor(txn[i]));
93-
}
94-
} else {
95-
for (size_t i = 0; i < txn.size(); i++)
96-
READWRITE(TransactionCompressor(txn[i]));
97-
}
61+
SERIALIZE_METHODS(BlockTransactions, obj)
62+
{
63+
READWRITE(obj.blockhash, Using<VectorFormatter<TransactionCompression>>(obj.txn));
9864
}
9965
};
10066

@@ -105,17 +71,7 @@ struct PrefilledTransaction {
10571
uint16_t index;
10672
CTransactionRef tx;
10773

108-
ADD_SERIALIZE_METHODS;
109-
110-
template <typename Stream, typename Operation>
111-
inline void SerializationOp(Stream& s, Operation ser_action) {
112-
uint64_t idx = index;
113-
READWRITE(COMPACTSIZE(idx));
114-
if (idx > std::numeric_limits<uint16_t>::max())
115-
throw std::ios_base::failure("index overflowed 16-bits");
116-
index = idx;
117-
READWRITE(TransactionCompressor(tx));
118-
}
74+
SERIALIZE_METHODS(PrefilledTransaction, obj) { READWRITE(COMPACTSIZE(obj.index), Using<TransactionCompression>(obj.tx)); }
11975
};
12076

12177
typedef enum ReadStatus_t
@@ -153,43 +109,15 @@ class CBlockHeaderAndShortTxIDs {
153109

154110
size_t BlockTxCount() const { return shorttxids.size() + prefilledtxn.size(); }
155111

156-
ADD_SERIALIZE_METHODS;
157-
158-
template <typename Stream, typename Operation>
159-
inline void SerializationOp(Stream& s, Operation ser_action) {
160-
READWRITE(header);
161-
READWRITE(nonce);
162-
163-
uint64_t shorttxids_size = (uint64_t)shorttxids.size();
164-
READWRITE(COMPACTSIZE(shorttxids_size));
112+
SERIALIZE_METHODS(CBlockHeaderAndShortTxIDs, obj)
113+
{
114+
READWRITE(obj.header, obj.nonce, Using<VectorFormatter<CustomUintFormatter<SHORTTXIDS_LENGTH>>>(obj.shorttxids), obj.prefilledtxn);
165115
if (ser_action.ForRead()) {
166-
size_t i = 0;
167-
while (shorttxids.size() < shorttxids_size) {
168-
shorttxids.resize(std::min((uint64_t)(1000 + shorttxids.size()), shorttxids_size));
169-
for (; i < shorttxids.size(); i++) {
170-
uint32_t lsb = 0; uint16_t msb = 0;
171-
READWRITE(lsb);
172-
READWRITE(msb);
173-
shorttxids[i] = (uint64_t(msb) << 32) | uint64_t(lsb);
174-
static_assert(SHORTTXIDS_LENGTH == 6, "shorttxids serialization assumes 6-byte shorttxids");
175-
}
176-
}
177-
} else {
178-
for (size_t i = 0; i < shorttxids.size(); i++) {
179-
uint32_t lsb = shorttxids[i] & 0xffffffff;
180-
uint16_t msb = (shorttxids[i] >> 32) & 0xffff;
181-
READWRITE(lsb);
182-
READWRITE(msb);
116+
if (obj.BlockTxCount() > std::numeric_limits<uint16_t>::max()) {
117+
throw std::ios_base::failure("indexes overflowed 16 bits");
183118
}
119+
obj.FillShortTxIDSelector();
184120
}
185-
186-
READWRITE(prefilledtxn);
187-
188-
if (BlockTxCount() > std::numeric_limits<uint16_t>::max())
189-
throw std::ios_base::failure("indexes overflowed 16 bits");
190-
191-
if (ser_action.ForRead())
192-
FillShortTxIDSelector();
193121
}
194122
};
195123

src/prevector.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -424,15 +424,20 @@ class prevector {
424424
return first;
425425
}
426426

427-
void push_back(const T& value) {
427+
template<typename... Args>
428+
void emplace_back(Args&&... args) {
428429
size_type new_size = size() + 1;
429430
if (capacity() < new_size) {
430431
change_capacity(new_size + (new_size >> 1));
431432
}
432-
new(item_ptr(size())) T(value);
433+
new(item_ptr(size())) T(std::forward<Args>(args)...);
433434
_size++;
434435
}
435436

437+
void push_back(const T& value) {
438+
emplace_back(value);
439+
}
440+
436441
void pop_back() {
437442
erase(end() - 1, end());
438443
}

src/serialize.h

Lines changed: 46 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -500,7 +500,7 @@ static inline Wrapper<Formatter, T&> Using(T&& t) { return Wrapper<Formatter, T&
500500

501501
#define VARINT_MODE(obj, mode) Using<VarIntFormatter<mode>>(obj)
502502
#define VARINT(obj) Using<VarIntFormatter<VarIntMode::DEFAULT>>(obj)
503-
#define COMPACTSIZE(obj) CCompactSize(REF(obj))
503+
#define COMPACTSIZE(obj) Using<CompactSizeFormatter>(obj)
504504
#define LIMITED_STRING(obj,n) LimitedString< n >(REF(obj))
505505

506506
/** Serialization wrapper class for integers in VarInt format. */
@@ -518,6 +518,28 @@ struct VarIntFormatter
518518
}
519519
};
520520

521+
template<int Bytes>
522+
struct CustomUintFormatter
523+
{
524+
static_assert(Bytes > 0 && Bytes <= 8, "CustomUintFormatter Bytes out of range");
525+
static constexpr uint64_t MAX = 0xffffffffffffffff >> (8 * (8 - Bytes));
526+
527+
template <typename Stream, typename I> void Ser(Stream& s, I v)
528+
{
529+
if (v < 0 || v > MAX) throw std::ios_base::failure("CustomUintFormatter value out of range");
530+
uint64_t raw = htole64(v);
531+
s.write((const char*)&raw, Bytes);
532+
}
533+
534+
template <typename Stream, typename I> void Unser(Stream& s, I& v)
535+
{
536+
static_assert(std::numeric_limits<I>::max() >= MAX && std::numeric_limits<I>::min() <= 0, "CustomUintFormatter type too small");
537+
uint64_t raw = 0;
538+
s.read((char*)&raw, Bytes);
539+
v = le64toh(raw);
540+
}
541+
};
542+
521543
/** Serialization wrapper class for big-endian integers.
522544
*
523545
* Use this wrapper around integer types that are stored in memory in native
@@ -552,21 +574,26 @@ class BigEndian
552574
}
553575
};
554576

555-
class CCompactSize
577+
/** Formatter for integers in CompactSize format. */
578+
struct CompactSizeFormatter
556579
{
557-
protected:
558-
uint64_t &n;
559-
public:
560-
explicit CCompactSize(uint64_t& nIn) : n(nIn) { }
561-
562-
template<typename Stream>
563-
void Serialize(Stream &s) const {
564-
WriteCompactSize<Stream>(s, n);
580+
template<typename Stream, typename I>
581+
void Unser(Stream& s, I& v)
582+
{
583+
uint64_t n = ReadCompactSize<Stream>(s);
584+
if (n < std::numeric_limits<I>::min() || n > std::numeric_limits<I>::max()) {
585+
throw std::ios_base::failure("CompactSize exceeds limit of type");
586+
}
587+
v = n;
565588
}
566589

567-
template<typename Stream>
568-
void Unserialize(Stream& s) {
569-
n = ReadCompactSize<Stream>(s);
590+
template<typename Stream, typename I>
591+
void Ser(Stream& s, I v)
592+
{
593+
static_assert(std::is_unsigned<I>::value, "CompactSize only supported for unsigned integers");
594+
static_assert(std::numeric_limits<I>::max() <= std::numeric_limits<uint64_t>::max(), "CompactSize only supports 64-bit integers and below");
595+
596+
WriteCompactSize<Stream>(s, v);
570597
}
571598
};
572599

@@ -613,23 +640,25 @@ BigEndian<I> WrapBigEndian(I& n) { return BigEndian<I>(n); }
613640
* as a vector of VarInt-encoded integers.
614641
*
615642
* V is not required to be an std::vector type. It works for any class that
616-
* exposes a value_type, size, reserve, push_back, and const iterators.
643+
* exposes a value_type, size, reserve, emplace_back, back, and const iterators.
617644
*/
618645
template<class Formatter>
619646
struct VectorFormatter
620647
{
621648
template<typename Stream, typename V>
622649
void Ser(Stream& s, const V& v)
623650
{
651+
Formatter formatter;
624652
WriteCompactSize(s, v.size());
625653
for (const typename V::value_type& elem : v) {
626-
s << Using<Formatter>(elem);
654+
formatter.Ser(s, elem);
627655
}
628656
}
629657

630658
template<typename Stream, typename V>
631659
void Unser(Stream& s, V& v)
632660
{
661+
Formatter formatter;
633662
v.clear();
634663
size_t size = ReadCompactSize(s);
635664
size_t allocated = 0;
@@ -641,9 +670,8 @@ struct VectorFormatter
641670
allocated = std::min(size, allocated + MAX_VECTOR_ALLOCATE / sizeof(typename V::value_type));
642671
v.reserve(allocated);
643672
while (v.size() < allocated) {
644-
typename V::value_type val;
645-
s >> Using<Formatter>(val);
646-
v.push_back(std::move(val));
673+
v.emplace_back();
674+
formatter.Unser(s, v.back());
647675
}
648676
}
649677
};

0 commit comments

Comments
 (0)