Skip to content

Commit 71f0f22

Browse files
committed
Merge bitcoin/bitcoin#28929: serialization: Support for multiple parameters
8d491ae serialization: Add ParamsStream GetStream() method (Ryan Ofsky) 951203b net: Simplify ParamsStream usage (Ryan Ofsky) e6794e4 serialization: Accept multiple parameters in ParamsStream constructor (Ryan Ofsky) cb28849 serialization: Reverse ParamsStream constructor order (Ryan Ofsky) 83436d1 serialization: Drop unnecessary ParamsStream references (Ryan Ofsky) 84502b7 serialization: Drop references to GetVersion/GetType (Ryan Ofsky) f3a2b52 serialization: Support for multiple parameters (Ryan Ofsky) Pull request description: Currently it is only possible to attach one serialization parameter to a stream at a time. For example, it is not possible to set a parameter controlling the transaction format and a parameter controlling the address format at the same time because one parameter will override the other. This limitation is inconvenient for multiprocess code since it is not possible to create just one type of stream and serialize any object to it. Instead it is necessary to create different streams for different object types, which requires extra boilerplate and makes using the new parameter fields a lot more awkward than the older version and type fields. Fix this problem by allowing an unlimited number of serialization stream parameters to be set, and allowing them to be requested by type. Later parameters will still override earlier parameters, but only if they have the same type. For an example of different ways multiple parameters can be set, see the new [`with_params_multi`](https://github.com/bitcoin/bitcoin/blob/40f505583f4edeb2859aeb70da20c6374d331a4f/src/test/serialize_tests.cpp#L394-L410) unit test. This change requires replacing the `stream.GetParams()` method with a `stream.GetParams<T>()` method in order for serialization code to retrieve the desired parameters. The change is more verbose, but probably a good thing for readability because previously it could be difficult to know what type the `GetParams()` method would return, and now it is more obvious. --- This PR is part of the [process separation project](bitcoin/bitcoin#28722). ACKs for top commit: maflcko: ACK 8d491ae 🔵 sipa: utACK 8d491ae TheCharlatan: ACK 8d491ae Tree-SHA512: 40b7041ee01c0372b1f86f7fd6f3b6af56ef24a6383f91ffcedd04d388e63407006457bf7ed056b0e37b4dec9ffd5ca006cb8192e488ea2c64678567e38d4647
2 parents 7a40f2a + 8d491ae commit 71f0f22

File tree

7 files changed

+176
-50
lines changed

7 files changed

+176
-50
lines changed

src/addrman.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ void AddrManImpl::Serialize(Stream& s_) const
173173
*/
174174

175175
// Always serialize in the latest version (FILE_FORMAT).
176-
ParamsStream s{CAddress::V2_DISK, s_};
176+
ParamsStream s{s_, CAddress::V2_DISK};
177177

178178
s << static_cast<uint8_t>(FILE_FORMAT);
179179

@@ -238,7 +238,7 @@ void AddrManImpl::Unserialize(Stream& s_)
238238
s_ >> Using<CustomUintFormatter<1>>(format);
239239

240240
const auto ser_params = (format >= Format::V3_BIP155 ? CAddress::V2_DISK : CAddress::V1_DISK);
241-
ParamsStream s{ser_params, s_};
241+
ParamsStream s{s_, ser_params};
242242

243243
uint8_t compat;
244244
s >> compat;

src/net.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -196,8 +196,7 @@ static std::vector<CAddress> ConvertSeeds(const std::vector<uint8_t> &vSeedsIn)
196196
const auto one_week{7 * 24h};
197197
std::vector<CAddress> vSeedsOut;
198198
FastRandomContext rng;
199-
DataStream underlying_stream{vSeedsIn};
200-
ParamsStream s{CAddress::V2_NETWORK, underlying_stream};
199+
ParamsStream s{DataStream{vSeedsIn}, CAddress::V2_NETWORK};
201200
while (!s.eof()) {
202201
CService endpoint;
203202
s >> endpoint;

src/netaddress.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ class CNetAddr
237237
template <typename Stream>
238238
void Serialize(Stream& s) const
239239
{
240-
if (s.GetParams().enc == Encoding::V2) {
240+
if (s.template GetParams<SerParams>().enc == Encoding::V2) {
241241
SerializeV2Stream(s);
242242
} else {
243243
SerializeV1Stream(s);
@@ -250,7 +250,7 @@ class CNetAddr
250250
template <typename Stream>
251251
void Unserialize(Stream& s)
252252
{
253-
if (s.GetParams().enc == Encoding::V2) {
253+
if (s.template GetParams<SerParams>().enc == Encoding::V2) {
254254
UnserializeV2Stream(s);
255255
} else {
256256
UnserializeV1Stream(s);

src/primitives/transaction.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -326,15 +326,15 @@ class CTransaction
326326

327327
template <typename Stream>
328328
inline void Serialize(Stream& s) const {
329-
SerializeTransaction(*this, s, s.GetParams());
329+
SerializeTransaction(*this, s, s.template GetParams<TransactionSerParams>());
330330
}
331331

332332
/** This deserializing constructor is provided instead of an Unserialize method.
333333
* Unserialize is not possible, since it would require overwriting const fields. */
334334
template <typename Stream>
335335
CTransaction(deserialize_type, const TransactionSerParams& params, Stream& s) : CTransaction(CMutableTransaction(deserialize, params, s)) {}
336336
template <typename Stream>
337-
CTransaction(deserialize_type, ParamsStream<TransactionSerParams,Stream>& s) : CTransaction(CMutableTransaction(deserialize, s)) {}
337+
CTransaction(deserialize_type, Stream& s) : CTransaction(CMutableTransaction(deserialize, s)) {}
338338

339339
bool IsNull() const {
340340
return vin.empty() && vout.empty();
@@ -386,12 +386,12 @@ struct CMutableTransaction
386386

387387
template <typename Stream>
388388
inline void Serialize(Stream& s) const {
389-
SerializeTransaction(*this, s, s.GetParams());
389+
SerializeTransaction(*this, s, s.template GetParams<TransactionSerParams>());
390390
}
391391

392392
template <typename Stream>
393393
inline void Unserialize(Stream& s) {
394-
UnserializeTransaction(*this, s, s.GetParams());
394+
UnserializeTransaction(*this, s, s.template GetParams<TransactionSerParams>());
395395
}
396396

397397
template <typename Stream>
@@ -400,7 +400,7 @@ struct CMutableTransaction
400400
}
401401

402402
template <typename Stream>
403-
CMutableTransaction(deserialize_type, ParamsStream<TransactionSerParams,Stream>& s) {
403+
CMutableTransaction(deserialize_type, Stream& s) {
404404
Unserialize(s);
405405
}
406406

src/protocol.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -375,9 +375,10 @@ class CAddress : public CService
375375
static constexpr SerParams V1_DISK{{CNetAddr::Encoding::V1}, Format::Disk};
376376
static constexpr SerParams V2_DISK{{CNetAddr::Encoding::V2}, Format::Disk};
377377

378-
SERIALIZE_METHODS_PARAMS(CAddress, obj, SerParams, params)
378+
SERIALIZE_METHODS(CAddress, obj)
379379
{
380380
bool use_v2;
381+
auto& params = SER_PARAMS(SerParams);
381382
if (params.fmt == Format::Disk) {
382383
// In the disk serialization format, the encoding (v1 or v2) is determined by a flag version
383384
// that's part of the serialization itself. ADDRV2_FORMAT in the stream version only determines

src/serialize.h

Lines changed: 78 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -182,17 +182,17 @@ const Out& AsBase(const In& x)
182182
static void SerializationOps(Type& obj, Stream& s, Operation ser_action)
183183

184184
/**
185-
* Variant of FORMATTER_METHODS that supports a declared parameter type.
186-
*
187-
* If a formatter has a declared parameter type, it must be invoked directly or
185+
* Formatter methods can retrieve parameters attached to a stream using the
186+
* SER_PARAMS(type) macro as long as the stream is created directly or
188187
* indirectly with a parameter of that type. This permits making serialization
189188
* depend on run-time context in a type-safe way.
190189
*
191190
* Example use:
192191
* struct BarParameter { bool fancy; ... };
193192
* struct Bar { ... };
194193
* struct FooFormatter {
195-
* FORMATTER_METHODS(Bar, obj, BarParameter, param) {
194+
* FORMATTER_METHODS(Bar, obj) {
195+
* auto& param = SER_PARAMS(BarParameter);
196196
* if (param.fancy) {
197197
* READWRITE(VARINT(obj.value));
198198
* } else {
@@ -214,13 +214,7 @@ const Out& AsBase(const In& x)
214214
* Compilation will fail in any context where serialization is invoked but
215215
* no parameter of a type convertible to BarParameter is provided.
216216
*/
217-
#define FORMATTER_METHODS_PARAMS(cls, obj, paramcls, paramobj) \
218-
template <typename Stream> \
219-
static void Ser(Stream& s, const cls& obj) { SerializationOps(obj, s, ActionSerialize{}, s.GetParams()); } \
220-
template <typename Stream> \
221-
static void Unser(Stream& s, cls& obj) { SerializationOps(obj, s, ActionUnserialize{}, s.GetParams()); } \
222-
template <typename Stream, typename Type, typename Operation> \
223-
static void SerializationOps(Type& obj, Stream& s, Operation ser_action, const paramcls& paramobj)
217+
#define SER_PARAMS(type) (s.template GetParams<type>())
224218

225219
#define BASE_SERIALIZE_METHODS(cls) \
226220
template <typename Stream> \
@@ -247,15 +241,6 @@ const Out& AsBase(const In& x)
247241
BASE_SERIALIZE_METHODS(cls) \
248242
FORMATTER_METHODS(cls, obj)
249243

250-
/**
251-
* Variant of SERIALIZE_METHODS that supports a declared parameter type.
252-
*
253-
* See FORMATTER_METHODS_PARAMS for more information on parameters.
254-
*/
255-
#define SERIALIZE_METHODS_PARAMS(cls, obj, paramcls, paramobj) \
256-
BASE_SERIALIZE_METHODS(cls) \
257-
FORMATTER_METHODS_PARAMS(cls, obj, paramcls, paramobj)
258-
259244
// Templates for serializing to anything that looks like a stream,
260245
// i.e. anything that supports .read(Span<std::byte>) and .write(Span<const std::byte>)
261246
//
@@ -1118,27 +1103,85 @@ size_t GetSerializeSize(const T& t)
11181103
return (SizeComputer() << t).size();
11191104
}
11201105

1121-
/** Wrapper that overrides the GetParams() function of a stream (and hides GetVersion/GetType). */
1122-
template <typename Params, typename SubStream>
1106+
//! Check if type contains a stream by seeing if has a GetStream() method.
1107+
template<typename T>
1108+
concept ContainsStream = requires(T t) { t.GetStream(); };
1109+
1110+
/** Wrapper that overrides the GetParams() function of a stream. */
1111+
template <typename SubStream, typename Params>
11231112
class ParamsStream
11241113
{
11251114
const Params& m_params;
1126-
SubStream& m_substream; // private to avoid leaking version/type into serialization code that shouldn't see it
1115+
// If ParamsStream constructor is passed an lvalue argument, Substream will
1116+
// be a reference type, and m_substream will reference that argument.
1117+
// Otherwise m_substream will be a substream instance and move from the
1118+
// argument. Letting ParamsStream contain a substream instance instead of
1119+
// just a reference is useful to make the ParamsStream object self contained
1120+
// and let it do cleanup when destroyed, for example by closing files if
1121+
// SubStream is a file stream.
1122+
SubStream m_substream;
11271123

11281124
public:
1129-
ParamsStream(const Params& params LIFETIMEBOUND, SubStream& substream LIFETIMEBOUND) : m_params{params}, m_substream{substream} {}
1125+
ParamsStream(SubStream&& substream, const Params& params LIFETIMEBOUND) : m_params{params}, m_substream{std::forward<SubStream>(substream)} {}
1126+
1127+
template <typename NestedSubstream, typename Params1, typename Params2, typename... NestedParams>
1128+
ParamsStream(NestedSubstream&& s, const Params1& params1 LIFETIMEBOUND, const Params2& params2 LIFETIMEBOUND, const NestedParams&... params LIFETIMEBOUND)
1129+
: ParamsStream{::ParamsStream{std::forward<NestedSubstream>(s), params2, params...}, params1} {}
1130+
11301131
template <typename U> ParamsStream& operator<<(const U& obj) { ::Serialize(*this, obj); return *this; }
11311132
template <typename U> ParamsStream& operator>>(U&& obj) { ::Unserialize(*this, obj); return *this; }
1132-
void write(Span<const std::byte> src) { m_substream.write(src); }
1133-
void read(Span<std::byte> dst) { m_substream.read(dst); }
1134-
void ignore(size_t num) { m_substream.ignore(num); }
1135-
bool eof() const { return m_substream.eof(); }
1136-
size_t size() const { return m_substream.size(); }
1137-
const Params& GetParams() const { return m_params; }
1138-
int GetVersion() = delete; // Deprecated with Params usage
1139-
int GetType() = delete; // Deprecated with Params usage
1133+
void write(Span<const std::byte> src) { GetStream().write(src); }
1134+
void read(Span<std::byte> dst) { GetStream().read(dst); }
1135+
void ignore(size_t num) { GetStream().ignore(num); }
1136+
bool eof() const { return GetStream().eof(); }
1137+
size_t size() const { return GetStream().size(); }
1138+
1139+
//! Get reference to stream parameters.
1140+
template <typename P>
1141+
const auto& GetParams() const
1142+
{
1143+
if constexpr (std::is_convertible_v<Params, P>) {
1144+
return m_params;
1145+
} else {
1146+
return m_substream.template GetParams<P>();
1147+
}
1148+
}
1149+
1150+
//! Get reference to underlying stream.
1151+
auto& GetStream()
1152+
{
1153+
if constexpr (ContainsStream<SubStream>) {
1154+
return m_substream.GetStream();
1155+
} else {
1156+
return m_substream;
1157+
}
1158+
}
1159+
const auto& GetStream() const
1160+
{
1161+
if constexpr (ContainsStream<SubStream>) {
1162+
return m_substream.GetStream();
1163+
} else {
1164+
return m_substream;
1165+
}
1166+
}
11401167
};
11411168

1169+
/**
1170+
* Explicit template deduction guide is required for single-parameter
1171+
* constructor so Substream&& is treated as a forwarding reference, and
1172+
* SubStream is deduced as reference type for lvalue arguments.
1173+
*/
1174+
template <typename Substream, typename Params>
1175+
ParamsStream(Substream&&, const Params&) -> ParamsStream<Substream, Params>;
1176+
1177+
/**
1178+
* Template deduction guide for multiple params arguments that creates a nested
1179+
* ParamsStream.
1180+
*/
1181+
template <typename Substream, typename Params1, typename Params2, typename... Params>
1182+
ParamsStream(Substream&& s, const Params1& params1, const Params2& params2, const Params&... params) ->
1183+
ParamsStream<decltype(ParamsStream{std::forward<Substream>(s), params2, params...}), Params1>;
1184+
11421185
/** Wrapper that serializes objects with the specified parameters. */
11431186
template <typename Params, typename T>
11441187
class ParamsWrapper
@@ -1152,13 +1195,13 @@ class ParamsWrapper
11521195
template <typename Stream>
11531196
void Serialize(Stream& s) const
11541197
{
1155-
ParamsStream ss{m_params, s};
1198+
ParamsStream ss{s, m_params};
11561199
::Serialize(ss, m_object);
11571200
}
11581201
template <typename Stream>
11591202
void Unserialize(Stream& s)
11601203
{
1161-
ParamsStream ss{m_params, s};
1204+
ParamsStream ss{s, m_params};
11621205
::Unserialize(ss, m_object);
11631206
}
11641207
};
@@ -1176,7 +1219,7 @@ class ParamsWrapper
11761219
/** \
11771220
* Return a wrapper around t that (de)serializes it with specified parameter params. \
11781221
* \
1179-
* See FORMATTER_METHODS_PARAMS for more information on serialization parameters. \
1222+
* See SER_PARAMS for more information on serialization parameters. \
11801223
*/ \
11811224
template <typename T> \
11821225
auto operator()(T&& t) const \

0 commit comments

Comments
 (0)