Skip to content

Commit 4c25787

Browse files
committed
Merge #18021: Serialization improvements step 4 (undo.h)
3c94b00 Convert undo.h to new serialization framework (Pieter Wuille) 3cd8ab9 Make std::vector and prevector reuse the VectorFormatter logic (Pieter Wuille) abf8624 Add custom vector-element formatter (Pieter Wuille) 37d800b Add a constant for the maximum vector allocation (5 Mbyte) (Pieter Wuille) Pull request description: The next step of changes from #10785. This one adds: * A meta-formatter for vectors, which serializes the vector elements using another formatter * Switch the undo.h code to the new framework, using the above (where undo entries are serialized as a vector, each of which uses a modified serializer for the UTXOs). ACKs for top commit: laanwj: code review ACK 3c94b00 jonatack: Qualified ACK 3c94b00 ryanofsky: Code review ACK 3c94b00. Changes since last review: renaming formatter classes, adding suggested static_assert, and removing temporary in VectorFormatter Tree-SHA512: 44eebf51a303f6adbbc1ca2b9d043e8ae7fd37e06778e026590892f8d09f8253067862a68ba8ca5d733fd2f8e7c84edd255370f5a4b6560259427a65f94632df
2 parents b063cb6 + 3c94b00 commit 4c25787

File tree

2 files changed

+81
-88
lines changed

2 files changed

+81
-88
lines changed

src/serialize.h

Lines changed: 68 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@
2525

2626
static const unsigned int MAX_SIZE = 0x02000000;
2727

28+
/** Maximum amount of memory (in bytes) to allocate at once when deserializing vectors. */
29+
static const unsigned int MAX_VECTOR_ALLOCATE = 5000000;
30+
2831
/**
2932
* Dummy data type to identify deserializing constructors.
3033
*
@@ -593,6 +596,53 @@ class LimitedString
593596
template<typename I>
594597
BigEndian<I> WrapBigEndian(I& n) { return BigEndian<I>(n); }
595598

599+
/** Formatter to serialize/deserialize vector elements using another formatter
600+
*
601+
* Example:
602+
* struct X {
603+
* std::vector<uint64_t> v;
604+
* SERIALIZE_METHODS(X, obj) { READWRITE(Using<VectorFormatter<VarInt>>(obj.v)); }
605+
* };
606+
* will define a struct that contains a vector of uint64_t, which is serialized
607+
* as a vector of VarInt-encoded integers.
608+
*
609+
* V is not required to be an std::vector type. It works for any class that
610+
* exposes a value_type, size, reserve, push_back, and const iterators.
611+
*/
612+
template<class Formatter>
613+
struct VectorFormatter
614+
{
615+
template<typename Stream, typename V>
616+
void Ser(Stream& s, const V& v)
617+
{
618+
WriteCompactSize(s, v.size());
619+
for (const typename V::value_type& elem : v) {
620+
s << Using<Formatter>(elem);
621+
}
622+
}
623+
624+
template<typename Stream, typename V>
625+
void Unser(Stream& s, V& v)
626+
{
627+
v.clear();
628+
size_t size = ReadCompactSize(s);
629+
size_t allocated = 0;
630+
while (allocated < size) {
631+
// For DoS prevention, do not blindly allocate as much as the stream claims to contain.
632+
// Instead, allocate in 5MiB batches, so that an attacker actually needs to provide
633+
// X MiB of data to make us allocate X+5 Mib.
634+
static_assert(sizeof(typename V::value_type) <= MAX_VECTOR_ALLOCATE, "Vector element size too large");
635+
allocated = std::min(size, allocated + MAX_VECTOR_ALLOCATE / sizeof(typename V::value_type));
636+
v.reserve(allocated);
637+
while (v.size() < allocated) {
638+
typename V::value_type val;
639+
s >> Using<Formatter>(val);
640+
v.push_back(std::move(val));
641+
}
642+
}
643+
};
644+
};
645+
596646
/**
597647
* Forward declarations
598648
*/
@@ -673,6 +723,20 @@ inline void Unserialize(Stream& is, T&& a)
673723
a.Unserialize(is);
674724
}
675725

726+
/** Default formatter. Serializes objects as themselves.
727+
*
728+
* The vector/prevector serialization code passes this to VectorFormatter
729+
* to enable reusing that logic. It shouldn't be needed elsewhere.
730+
*/
731+
struct DefaultFormatter
732+
{
733+
template<typename Stream, typename T>
734+
static void Ser(Stream& s, const T& t) { Serialize(s, t); }
735+
736+
template<typename Stream, typename T>
737+
static void Unser(Stream& s, T& t) { Unserialize(s, t); }
738+
};
739+
676740

677741

678742

@@ -713,9 +777,7 @@ void Serialize_impl(Stream& os, const prevector<N, T>& v, const unsigned char&)
713777
template<typename Stream, unsigned int N, typename T, typename V>
714778
void Serialize_impl(Stream& os, const prevector<N, T>& v, const V&)
715779
{
716-
WriteCompactSize(os, v.size());
717-
for (typename prevector<N, T>::const_iterator vi = v.begin(); vi != v.end(); ++vi)
718-
::Serialize(os, (*vi));
780+
Serialize(os, Using<VectorFormatter<DefaultFormatter>>(v));
719781
}
720782

721783
template<typename Stream, unsigned int N, typename T>
@@ -744,19 +806,7 @@ void Unserialize_impl(Stream& is, prevector<N, T>& v, const unsigned char&)
744806
template<typename Stream, unsigned int N, typename T, typename V>
745807
void Unserialize_impl(Stream& is, prevector<N, T>& v, const V&)
746808
{
747-
v.clear();
748-
unsigned int nSize = ReadCompactSize(is);
749-
unsigned int i = 0;
750-
unsigned int nMid = 0;
751-
while (nMid < nSize)
752-
{
753-
nMid += 5000000 / sizeof(T);
754-
if (nMid > nSize)
755-
nMid = nSize;
756-
v.resize_uninitialized(nMid);
757-
for (; i < nMid; ++i)
758-
Unserialize(is, v[i]);
759-
}
809+
Unserialize(is, Using<VectorFormatter<DefaultFormatter>>(v));
760810
}
761811

762812
template<typename Stream, unsigned int N, typename T>
@@ -793,9 +843,7 @@ void Serialize_impl(Stream& os, const std::vector<T, A>& v, const bool&)
793843
template<typename Stream, typename T, typename A, typename V>
794844
void Serialize_impl(Stream& os, const std::vector<T, A>& v, const V&)
795845
{
796-
WriteCompactSize(os, v.size());
797-
for (typename std::vector<T, A>::const_iterator vi = v.begin(); vi != v.end(); ++vi)
798-
::Serialize(os, (*vi));
846+
Serialize(os, Using<VectorFormatter<DefaultFormatter>>(v));
799847
}
800848

801849
template<typename Stream, typename T, typename A>
@@ -824,19 +872,7 @@ void Unserialize_impl(Stream& is, std::vector<T, A>& v, const unsigned char&)
824872
template<typename Stream, typename T, typename A, typename V>
825873
void Unserialize_impl(Stream& is, std::vector<T, A>& v, const V&)
826874
{
827-
v.clear();
828-
unsigned int nSize = ReadCompactSize(is);
829-
unsigned int i = 0;
830-
unsigned int nMid = 0;
831-
while (nMid < nSize)
832-
{
833-
nMid += 5000000 / sizeof(T);
834-
if (nMid > nSize)
835-
nMid = nSize;
836-
v.resize(nMid);
837-
for (; i < nMid; i++)
838-
Unserialize(is, v[i]);
839-
}
875+
Unserialize(is, Using<VectorFormatter<DefaultFormatter>>(v));
840876
}
841877

842878
template<typename Stream, typename T, typename A>

src/undo.h

Lines changed: 13 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -13,88 +13,50 @@
1313
#include <serialize.h>
1414
#include <version.h>
1515

16-
/** Undo information for a CTxIn
16+
/** Formatter for undo information for a CTxIn
1717
*
1818
* Contains the prevout's CTxOut being spent, and its metadata as well
1919
* (coinbase or not, height). The serialization contains a dummy value of
2020
* zero. This is compatible with older versions which expect to see
2121
* the transaction version there.
2222
*/
23-
class TxInUndoSerializer
23+
struct TxInUndoFormatter
2424
{
25-
const Coin* txout;
26-
27-
public:
2825
template<typename Stream>
29-
void Serialize(Stream &s) const {
30-
::Serialize(s, VARINT(txout->nHeight * 2 + (txout->fCoinBase ? 1u : 0u)));
31-
if (txout->nHeight > 0) {
26+
void Ser(Stream &s, const Coin& txout) {
27+
::Serialize(s, VARINT(txout.nHeight * 2 + (txout.fCoinBase ? 1u : 0u)));
28+
if (txout.nHeight > 0) {
3229
// Required to maintain compatibility with older undo format.
3330
::Serialize(s, (unsigned char)0);
3431
}
35-
::Serialize(s, Using<TxOutCompression>(REF(txout->out)));
32+
::Serialize(s, Using<TxOutCompression>(txout.out));
3633
}
3734

38-
explicit TxInUndoSerializer(const Coin* coin) : txout(coin) {}
39-
};
40-
41-
class TxInUndoDeserializer
42-
{
43-
Coin* txout;
44-
45-
public:
4635
template<typename Stream>
47-
void Unserialize(Stream &s) {
36+
void Unser(Stream &s, Coin& txout) {
4837
unsigned int nCode = 0;
4938
::Unserialize(s, VARINT(nCode));
50-
txout->nHeight = nCode / 2;
51-
txout->fCoinBase = nCode & 1;
52-
if (txout->nHeight > 0) {
39+
txout.nHeight = nCode / 2;
40+
txout.fCoinBase = nCode & 1;
41+
if (txout.nHeight > 0) {
5342
// Old versions stored the version number for the last spend of
5443
// a transaction's outputs. Non-final spends were indicated with
5544
// height = 0.
5645
unsigned int nVersionDummy;
5746
::Unserialize(s, VARINT(nVersionDummy));
5847
}
59-
::Unserialize(s, Using<TxOutCompression>(REF(txout->out)));
48+
::Unserialize(s, Using<TxOutCompression>(txout.out));
6049
}
61-
62-
explicit TxInUndoDeserializer(Coin* coin) : txout(coin) {}
6350
};
6451

65-
static const size_t MIN_TRANSACTION_INPUT_WEIGHT = WITNESS_SCALE_FACTOR * ::GetSerializeSize(CTxIn(), PROTOCOL_VERSION);
66-
static const size_t MAX_INPUTS_PER_BLOCK = MAX_BLOCK_WEIGHT / MIN_TRANSACTION_INPUT_WEIGHT;
67-
6852
/** Undo information for a CTransaction */
6953
class CTxUndo
7054
{
7155
public:
7256
// undo information for all txins
7357
std::vector<Coin> vprevout;
7458

75-
template <typename Stream>
76-
void Serialize(Stream& s) const {
77-
// TODO: avoid reimplementing vector serializer
78-
uint64_t count = vprevout.size();
79-
::Serialize(s, COMPACTSIZE(REF(count)));
80-
for (const auto& prevout : vprevout) {
81-
::Serialize(s, TxInUndoSerializer(&prevout));
82-
}
83-
}
84-
85-
template <typename Stream>
86-
void Unserialize(Stream& s) {
87-
// TODO: avoid reimplementing vector deserializer
88-
uint64_t count = 0;
89-
::Unserialize(s, COMPACTSIZE(count));
90-
if (count > MAX_INPUTS_PER_BLOCK) {
91-
throw std::ios_base::failure("Too many input undo records");
92-
}
93-
vprevout.resize(count);
94-
for (auto& prevout : vprevout) {
95-
::Unserialize(s, TxInUndoDeserializer(&prevout));
96-
}
97-
}
59+
SERIALIZE_METHODS(CTxUndo, obj) { READWRITE(Using<VectorFormatter<TxInUndoFormatter>>(obj.vprevout)); }
9860
};
9961

10062
/** Undo information for a CBlock */
@@ -103,12 +65,7 @@ class CBlockUndo
10365
public:
10466
std::vector<CTxUndo> vtxundo; // for all but the coinbase
10567

106-
ADD_SERIALIZE_METHODS;
107-
108-
template <typename Stream, typename Operation>
109-
inline void SerializationOp(Stream& s, Operation ser_action) {
110-
READWRITE(vtxundo);
111-
}
68+
SERIALIZE_METHODS(CBlockUndo, obj) { READWRITE(obj.vtxundo); }
11269
};
11370

11471
#endif // BITCOIN_UNDO_H

0 commit comments

Comments
 (0)