From e483a689e30708d5a953fc226712bbbc8e47b9e6 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <6098974-kittywhiskers@users.noreply.gitlab.com> Date: Fri, 7 Jul 2017 15:48:13 -0700 Subject: [PATCH 1/6] Merge #17850: Convert addrdb/addrman to new serialization https://github.com/bitcoin/bitcoin/pull/17850/commits/9250a087d2f450f37342010aea0a5d583eea508b Partial #17850: Introduce new serialization macros without casts https://github.com/bitcoin/bitcoin/pull/17850/commits/ca33451535dc29f64d37c49af8b22142d7716d0d --- src/addrdb.h | 10 +--------- src/addrman.h | 14 +++++--------- src/serialize.h | 24 ++++++++++++++++++++++++ 3 files changed, 30 insertions(+), 18 deletions(-) diff --git a/src/addrdb.h b/src/addrdb.h index c42494f88f16..b467645d098a 100644 --- a/src/addrdb.h +++ b/src/addrdb.h @@ -43,15 +43,7 @@ class CBanEntry nCreateTime = nCreateTimeIn; } - ADD_SERIALIZE_METHODS; - - template - inline void SerializationOp(Stream& s, Operation ser_action) { - READWRITE(this->nVersion); - READWRITE(nCreateTime); - READWRITE(nBanUntil); - READWRITE(banReason); - } + SERIALIZE_METHODS(CBanEntry, obj) { READWRITE(obj.nVersion, obj.nCreateTime, obj.nBanUntil, obj.banReason); } void SetNull() { diff --git a/src/addrman.h b/src/addrman.h index 48ee327e6994..7a4cd4ee647d 100644 --- a/src/addrman.h +++ b/src/addrman.h @@ -55,14 +55,10 @@ class CAddrInfo : public CAddress public: - ADD_SERIALIZE_METHODS; - - template - inline void SerializationOp(Stream& s, Operation ser_action) { - READWRITEAS(CAddress, *this); - READWRITE(source); - READWRITE(nLastSuccess); - READWRITE(nAttempts); + SERIALIZE_METHODS(CAddrInfo, obj) + { + READWRITEAS(CAddress, obj); + READWRITE(obj.source, obj.nLastSuccess, obj.nAttempts); } void Init() @@ -315,7 +311,7 @@ class CAddrMan * This format is more complex, but significantly smaller (at most 1.5 MiB), and supports * changes to the ADDRMAN_ parameters without breaking the on-disk structure. * - * We don't use ADD_SERIALIZE_METHODS since the serialization and deserialization code has + * We don't use SERIALIZE_METHODS since the serialization and deserialization code has * very little in common. */ template diff --git a/src/serialize.h b/src/serialize.h index 23ec49d47ca6..63bee528a84f 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -203,6 +203,30 @@ template const X& ReadWriteAsHelper(const X& x) { return x; } SerializationOp(s, CSerActionUnserialize()); \ } +/** + * Implement the Serialize and Unserialize methods by delegating to a single templated + * static method that takes the to-be-(de)serialized object as a parameter. This approach + * has the advantage that the constness of the object becomes a template parameter, and + * thus allows a single implementation that sees the object as const for serializing + * and non-const for deserializing, without casts. + */ +#define SERIALIZE_METHODS(cls, obj) \ + template \ + void Serialize(Stream& s) const \ + { \ + static_assert(std::is_same::value, "Serialize type mismatch"); \ + SerializationOps(*this, s, CSerActionSerialize()); \ + } \ + template \ + void Unserialize(Stream& s) \ + { \ + static_assert(std::is_same::value, "Unserialize type mismatch"); \ + SerializationOps(*this, s, CSerActionUnserialize()); \ + } \ + template \ + static inline void SerializationOps(Type& obj, Stream& s, Operation ser_action) \ + + #ifndef CHAR_EQUALS_INT8 template inline void Serialize(Stream& s, char a ) { ser_writedata8(s, a); } // TODO Get rid of bare char #endif From 46cb6e23dcdd6a5916c778f14336a968d051b546 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <6098974-kittywhiskers@users.noreply.gitlab.com> Date: Wed, 8 Jan 2020 08:56:19 -0800 Subject: [PATCH 2/6] Merge #17896: Convert chain to new serialization https://github.com/bitcoin/bitcoin/pull/17896/commits/9b66083788581c264a097e26795561cb3eac455d Partial #17896: Convert VARINT to the formatter/Using approach https://github.com/bitcoin/bitcoin/pull/17896/commits/2f1b2f4ed044fe005e5a6c1b55e95822e83c16df Partial #17896: Add a generic approach for (de)serialization of objects https://github.com/bitcoin/bitcoin/pull/17896/commits/ca62563df341786d1d1809a037d8b592924e78c4 --- src/chain.h | 60 +++++++++++++++++++++---------------------------- src/serialize.h | 53 +++++++++++++++++++++++++++++-------------- 2 files changed, 62 insertions(+), 51 deletions(-) diff --git a/src/chain.h b/src/chain.h index 89123185ab62..bb2763a1b1f5 100644 --- a/src/chain.h +++ b/src/chain.h @@ -39,17 +39,15 @@ class CBlockFileInfo uint64_t nTimeFirst; //!< earliest time of block in file uint64_t nTimeLast; //!< latest time of block in file - ADD_SERIALIZE_METHODS; - - template - inline void SerializationOp(Stream& s, Operation ser_action) { - READWRITE(VARINT(nBlocks)); - READWRITE(VARINT(nSize)); - READWRITE(VARINT(nUndoSize)); - READWRITE(VARINT(nHeightFirst)); - READWRITE(VARINT(nHeightLast)); - READWRITE(VARINT(nTimeFirst)); - READWRITE(VARINT(nTimeLast)); + SERIALIZE_METHODS(CBlockFileInfo, obj) + { + READWRITE(VARINT(obj.nBlocks)); + READWRITE(VARINT(obj.nSize)); + READWRITE(VARINT(obj.nUndoSize)); + READWRITE(VARINT(obj.nHeightFirst)); + READWRITE(VARINT(obj.nHeightLast)); + READWRITE(VARINT(obj.nTimeFirst)); + READWRITE(VARINT(obj.nTimeLast)); } void SetNull() { @@ -383,33 +381,27 @@ class CDiskBlockIndex : public CBlockIndex hashPrev = (pprev ? pprev->GetBlockHash() : uint256()); } - ADD_SERIALIZE_METHODS; - - template - inline void SerializationOp(Stream& s, Operation ser_action) { + SERIALIZE_METHODS(CDiskBlockIndex, obj) + { int _nVersion = s.GetVersion(); - if (!(s.GetType() & SER_GETHASH)) - READWRITE(VARINT(_nVersion, VarIntMode::NONNEGATIVE_SIGNED)); - - READWRITE(VARINT(nHeight, VarIntMode::NONNEGATIVE_SIGNED)); - READWRITE(VARINT(nStatus)); - READWRITE(VARINT(nTx)); - if (nStatus & (BLOCK_HAVE_DATA | BLOCK_HAVE_UNDO)) - READWRITE(VARINT(nFile, VarIntMode::NONNEGATIVE_SIGNED)); - if (nStatus & BLOCK_HAVE_DATA) - READWRITE(VARINT(nDataPos)); - if (nStatus & BLOCK_HAVE_UNDO) - READWRITE(VARINT(nUndoPos)); + if (!(s.GetType() & SER_GETHASH)) READWRITE(VARINT(_nVersion, VarIntMode::NONNEGATIVE_SIGNED)); + + READWRITE(VARINT(obj.nHeight, VarIntMode::NONNEGATIVE_SIGNED)); + READWRITE(VARINT(obj.nStatus)); + READWRITE(VARINT(obj.nTx)); + if (obj.nStatus & (BLOCK_HAVE_DATA | BLOCK_HAVE_UNDO)) READWRITE(VARINT(obj.nFile, VarIntMode::NONNEGATIVE_SIGNED)); + if (obj.nStatus & BLOCK_HAVE_DATA) READWRITE(VARINT(obj.nDataPos)); + if (obj.nStatus & BLOCK_HAVE_UNDO) READWRITE(VARINT(obj.nUndoPos)); // block hash - READWRITE(hash); + READWRITE(obj.hash); // block header - READWRITE(this->nVersion); - READWRITE(hashPrev); - READWRITE(hashMerkleRoot); - READWRITE(nTime); - READWRITE(nBits); - READWRITE(nNonce); + READWRITE(obj.nVersion); + READWRITE(obj.hashPrev); + READWRITE(obj.hashMerkleRoot); + READWRITE(obj.nTime); + READWRITE(obj.nBits); + READWRITE(obj.nNonce); } uint256 GetBlockHash() const diff --git a/src/serialize.h b/src/serialize.h index 63bee528a84f..c5d489ff972b 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -448,11 +448,37 @@ I ReadVarInt(Stream& is) } } +/** Simple wrapper class to serialize objects using a formatter; used by Using(). */ +template +class Wrapper +{ + static_assert(std::is_lvalue_reference::value, "Wrapper needs an lvalue reference type T"); +protected: + T m_object; +public: + explicit Wrapper(T obj) : m_object(obj) {} + template void Serialize(Stream &s) const { Formatter().Ser(s, m_object); } + template void Unserialize(Stream &s) { Formatter().Unser(s, m_object); } +}; + +/** Cause serialization/deserialization of an object to be done using a specified formatter class. + * + * To use this, you need a class Formatter that has public functions Ser(stream, const object&) for + * serialization, and Unser(stream, object&) for deserialization. Serialization routines (inside + * READWRITE, or directly with << and >> operators), can then use Using(object). + * + * This works by constructing a Wrapper-wrapped version of object, where T is + * const during serialization, and non-const during deserialization, which maintains const + * correctness. + */ +template +static inline Wrapper Using(T&& t) { return Wrapper(t); } + #define FIXEDBITSET(obj, size) CFixedBitSet(REF(obj), (size)) #define DYNBITSET(obj) CDynamicBitSet(REF(obj)) #define FIXEDVARINTSBITSET(obj, size) CFixedVarIntsBitSet(REF(obj), (size)) #define AUTOBITSET(obj, size) CAutoBitSet(REF(obj), (size)) -#define VARINT(obj, ...) WrapVarInt<__VA_ARGS__>(REF(obj)) +#define VARINT(obj, ...) Using>(obj) #define COMPACTSIZE(obj) CCompactSize(REF(obj)) #define LIMITED_STRING(obj,n) LimitedString< n >(REF(obj)) @@ -612,22 +638,18 @@ class CAutoBitSet } }; -template -class CVarInt +/** Serialization wrapper class for integers in VarInt format. */ +template +struct VarIntFormatter { -protected: - I &n; -public: - explicit CVarInt(I& nIn) : n(nIn) { } - - template - void Serialize(Stream &s) const { - WriteVarInt(s, n); + template void Ser(Stream &s, I v) + { + WriteVarInt::type>(s, v); } - template - void Unserialize(Stream& s) { - n = ReadVarInt(s); + template void Unser(Stream& s, I& v) + { + v = ReadVarInt::type>(s); } }; @@ -716,9 +738,6 @@ class LimitedString } }; -template -CVarInt WrapVarInt(I& n) { return CVarInt{n}; } - template BigEndian WrapBigEndian(I& n) { return BigEndian(n); } From 0802bdfa373e32af2b974bde02d0455c1088ee1e Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <6098974-kittywhiskers@users.noreply.gitlab.com> Date: Wed, 6 Sep 2017 11:25:03 -0700 Subject: [PATCH 3/6] Merge #18021: Convert undo.h to new serialization framework https://github.com/bitcoin/bitcoin/pull/18021/commits/3c94b0039d2ca2a8c41fd6127ff5019a2afc304e Partial #18021: Make std::vector and prevector reuse the VectorFormatter logic https://github.com/bitcoin/bitcoin/pull/18021/commits/3cd8ab9d11e4c0ea47e56be4f6f2fdd48806796c Partial #18021: Add custom vector-element formatter https://github.com/bitcoin/bitcoin/pull/18021/commits/abf86243568af380c1384ac4e0bfcdcfd4dab085 Partial #18021: Add a constant for the maximum vector allocation (5 Mbyte) 37d800bea016d5cba5635db036f53a486614ed30 --- src/serialize.h | 98 +++++++++++++++++++++++++++++++++---------------- src/undo.h | 68 +++++++--------------------------- 2 files changed, 79 insertions(+), 87 deletions(-) diff --git a/src/serialize.h b/src/serialize.h index c5d489ff972b..ef4f5ad9365f 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -29,6 +29,9 @@ static const unsigned int MAX_SIZE = 0x02000000; +/** Maximum amount of memory (in bytes) to allocate at once when deserializing vectors. */ +static const unsigned int MAX_VECTOR_ALLOCATE = 5000000; + /** * Dummy data type to identify deserializing constructors. * @@ -741,6 +744,53 @@ class LimitedString template BigEndian WrapBigEndian(I& n) { return BigEndian(n); } +/** Formatter to serialize/deserialize vector elements using another formatter + * + * Example: + * struct X { + * std::vector v; + * SERIALIZE_METHODS(X, obj) { READWRITE(Using>(obj.v)); } + * }; + * will define a struct that contains a vector of uint64_t, which is serialized + * as a vector of VarInt-encoded integers. + * + * V is not required to be an std::vector type. It works for any class that + * exposes a value_type, size, reserve, push_back, and const iterators. + */ +template +struct VectorFormatter +{ + template + void Ser(Stream& s, const V& v) + { + WriteCompactSize(s, v.size()); + for (const typename V::value_type& elem : v) { + s << Using(elem); + } + } + + template + void Unser(Stream& s, V& v) + { + v.clear(); + size_t size = ReadCompactSize(s); + size_t allocated = 0; + while (allocated < size) { + // For DoS prevention, do not blindly allocate as much as the stream claims to contain. + // Instead, allocate in 5MiB batches, so that an attacker actually needs to provide + // X MiB of data to make us allocate X+5 Mib. + static_assert(sizeof(typename V::value_type) <= MAX_VECTOR_ALLOCATE, "Vector element size too large"); + allocated = std::min(size, allocated + MAX_VECTOR_ALLOCATE / sizeof(typename V::value_type)); + v.reserve(allocated); + while (v.size() < allocated) { + typename V::value_type val; + s >> Using(val); + v.push_back(std::move(val)); + } + } + }; +}; + /** * Forward declarations */ @@ -863,8 +913,20 @@ inline void Unserialize(Stream& s, T& a ) a = (T)b; } +/** Default formatter. Serializes objects as themselves. + * + * The vector/prevector serialization code passes this to VectorFormatter + * to enable reusing that logic. It shouldn't be needed elsewhere. + */ +struct DefaultFormatter +{ + template + static void Ser(Stream& s, const T& t) { Serialize(s, t); } + template + static void Unser(Stream& s, T& t) { Unserialize(s, t); } +}; /** * string @@ -902,9 +964,7 @@ void Serialize_impl(Stream& os, const prevector& v, const unsigned char&) template void Serialize_impl(Stream& os, const prevector& v, const V&) { - WriteCompactSize(os, v.size()); - for (typename prevector::const_iterator vi = v.begin(); vi != v.end(); ++vi) - ::Serialize(os, (*vi)); + Serialize(os, Using>(v)); } template @@ -933,19 +993,7 @@ void Unserialize_impl(Stream& is, prevector& v, const unsigned char&) template void Unserialize_impl(Stream& is, prevector& v, const V&) { - v.clear(); - unsigned int nSize = ReadCompactSize(is); - unsigned int i = 0; - unsigned int nMid = 0; - while (nMid < nSize) - { - nMid += 5000000 / sizeof(T); - if (nMid > nSize) - nMid = nSize; - v.resize_uninitialized(nMid); - for (; i < nMid; ++i) - Unserialize(is, v[i]); - } + Unserialize(is, Using>(v)); } template @@ -970,9 +1018,7 @@ void Serialize_impl(Stream& os, const std::vector& v, const unsigned char& template void Serialize_impl(Stream& os, const std::vector& v, const V&) { - WriteCompactSize(os, v.size()); - for (typename std::vector::const_iterator vi = v.begin(); vi != v.end(); ++vi) - ::Serialize(os, (*vi)); + Serialize(os, Using>(v)); } template @@ -1001,19 +1047,7 @@ void Unserialize_impl(Stream& is, std::vector& v, const unsigned char&) template void Unserialize_impl(Stream& is, std::vector& v, const V&) { - v.clear(); - unsigned int nSize = ReadCompactSize(is); - unsigned int i = 0; - unsigned int nMid = 0; - while (nMid < nSize) - { - nMid += 5000000 / sizeof(T); - if (nMid > nSize) - nMid = nSize; - v.resize(nMid); - for (; i < nMid; i++) - Unserialize(is, v[i]); - } + Unserialize(is, Using>(v)); } template diff --git a/src/undo.h b/src/undo.h index 70ebc1e04f5b..36f881509843 100644 --- a/src/undo.h +++ b/src/undo.h @@ -12,57 +12,42 @@ #include #include -/** Undo information for a CTxIn +/** Formatter for undo information for a CTxIn * * Contains the prevout's CTxOut being spent, and its metadata as well * (coinbase or not, height). The serialization contains a dummy value of * zero. This is compatible with older versions which expect to see * the transaction version there. */ -class TxInUndoSerializer +struct TxInUndoFormatter { - const Coin* txout; - -public: template - void Serialize(Stream &s) const { - ::Serialize(s, VARINT(txout->nHeight * 2 + (txout->fCoinBase ? 1u : 0u))); - if (txout->nHeight > 0) { + void Ser(Stream &s, const Coin& txout) { + ::Serialize(s, VARINT(txout.nHeight * 2 + (txout.fCoinBase ? 1u : 0u))); + if (txout.nHeight > 0) { // Required to maintain compatibility with older undo format. ::Serialize(s, (unsigned char)0); } - ::Serialize(s, CTxOutCompressor(REF(txout->out))); + ::Serialize(s, CTxOutCompressor(REF(txout.out))); } - explicit TxInUndoSerializer(const Coin* coin) : txout(coin) {} -}; - -class TxInUndoDeserializer -{ - Coin* txout; - -public: template - void Unserialize(Stream &s) { + void Unser(Stream &s, Coin& txout) { unsigned int nCode = 0; ::Unserialize(s, VARINT(nCode)); - txout->nHeight = nCode / 2; - txout->fCoinBase = nCode & 1; - if (txout->nHeight > 0) { + txout.nHeight = nCode / 2; + txout.fCoinBase = nCode & 1; + if (txout.nHeight > 0) { // Old versions stored the version number for the last spend of // a transaction's outputs. Non-final spends were indicated with // height = 0. unsigned int nVersionDummy; ::Unserialize(s, VARINT(nVersionDummy)); } - ::Unserialize(s, CTxOutCompressor(REF(txout->out))); + ::Unserialize(s, CTxOutCompressor(REF(txout.out))); } - - explicit TxInUndoDeserializer(Coin* coin) : txout(coin) {} }; -static const size_t MAX_INPUTS_PER_BLOCK = MaxBlockSize() / ::GetSerializeSize(CTxIn(), SER_NETWORK, PROTOCOL_VERSION); - /** Undo information for a CTransaction */ class CTxUndo { @@ -70,29 +55,7 @@ class CTxUndo // undo information for all txins std::vector vprevout; - template - void Serialize(Stream& s) const { - // TODO: avoid reimplementing vector serializer - uint64_t count = vprevout.size(); - ::Serialize(s, COMPACTSIZE(REF(count))); - for (const auto& prevout : vprevout) { - ::Serialize(s, TxInUndoSerializer(&prevout)); - } - } - - template - void Unserialize(Stream& s) { - // TODO: avoid reimplementing vector deserializer - uint64_t count = 0; - ::Unserialize(s, COMPACTSIZE(count)); - if (count > MAX_INPUTS_PER_BLOCK) { - throw std::ios_base::failure("Too many input undo records"); - } - vprevout.resize(count); - for (auto& prevout : vprevout) { - ::Unserialize(s, TxInUndoDeserializer(&prevout)); - } - } + SERIALIZE_METHODS(CTxUndo, obj) { READWRITE(Using>(obj.vprevout)); } }; /** Undo information for a CBlock */ @@ -101,12 +64,7 @@ class CBlockUndo public: std::vector vtxundo; // for all but the coinbase - ADD_SERIALIZE_METHODS; - - template - inline void SerializationOp(Stream& s, Operation ser_action) { - READWRITE(vtxundo); - } + SERIALIZE_METHODS(CBlockUndo, obj) { READWRITE(obj.vtxundo); } }; #endif // BITCOIN_UNDO_H From 2f542a93a13a8c26d5a29decf094bebc315a176d Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <6098974-kittywhiskers@users.noreply.gitlab.com> Date: Sat, 8 Jul 2017 16:43:34 -0700 Subject: [PATCH 4/6] Merge #12752: Move compressor utility functions out of class https://github.com/bitcoin/bitcoin/commit/76a9aacd3fb75d5e0854e53bb3376f2ab603a561 --- src/compressor.cpp | 30 +++++++++++++++++++----------- src/compressor.h | 31 ++++++++++--------------------- src/test/compress_tests.cpp | 8 ++++---- 3 files changed, 33 insertions(+), 36 deletions(-) diff --git a/src/compressor.cpp b/src/compressor.cpp index a729e28f079f..b003a86ce7a4 100644 --- a/src/compressor.cpp +++ b/src/compressor.cpp @@ -9,7 +9,15 @@ #include #include