Add capnp serialization code for bitcoin types

- Add capnp ToBlob, ToArray, Wrap, Serialize, and Unserialize helper functions
- Add support for std::chrono::seconds capnp serialization
- Add support for util::Result capnp serialization
This commit is contained in:
Ryan Ofsky 2024-07-26 08:35:33 -04:00
parent 1d813e4bf5
commit 516f8637b8
2 changed files with 118 additions and 2 deletions

View file

@ -8,9 +8,12 @@
#include <clientversion.h>
#include <interfaces/types.h>
#include <primitives/transaction.h>
#include <protocol.h>
#include <serialize.h>
#include <streams.h>
#include <univalue.h>
#include <util/result.h>
#include <util/translation.h>
#include <cstddef>
#include <mp/proxy-types.h>
@ -28,12 +31,27 @@
#include <mp/type-threadmap.h>
#include <mp/type-vector.h>
#include <type_traits>
#include <unordered_set>
#include <utility>
namespace ipc {
namespace capnp {
//! Convert kj::ArrayPtr to base_blob.
template <typename T>
inline T ToBlob(const kj::ArrayPtr<const kj::byte>& array)
{
return T({array.begin(), array.begin() + array.size()});
}
//! Convert base_blob to kj::ArrayPtr.
template <typename T>
inline kj::ArrayPtr<const kj::byte> ToArray(const T& blob)
{
return {reinterpret_cast<const kj::byte*>(blob.data()), blob.size()};
}
//! Construct a ParamStream wrapping a data stream with serialization parameters
//! needed to pass transaction objects between bitcoin processes.
//! needed to pass transaction and address objects between bitcoin processes.
//! In the future, more params may be added here to serialize other objects that
//! require serialization parameters. Params should just be chosen to serialize
//! objects completely and ensure that serializing and deserializing objects
@ -42,7 +60,28 @@ namespace capnp {
template <typename S>
auto Wrap(S& s)
{
return ParamsStream{s, TX_WITH_WITNESS};
return ParamsStream{s, TX_WITH_WITNESS, CAddress::V2_NETWORK};
}
//! Serialize bitcoin value.
template <typename T>
DataStream Serialize(const T& value)
{
DataStream stream;
auto wrapper{Wrap(stream)};
value.Serialize(wrapper);
return stream;
}
//! Deserialize bitcoin value.
template <typename T>
T Unserialize(const kj::ArrayPtr<const kj::byte>& data)
{
SpanReader stream{{data.begin(), data.end()}};
T value;
auto wrapper{Wrap(stream)};
value.Unserialize(wrapper);
return value;
}
//! Detect if type has a deserialize_type constructor, which is
@ -127,6 +166,63 @@ decltype(auto) CustomReadField(TypeList<UniValue>, Priority<1>, InvokeContext& i
});
}
//! Overload CustomBuildField and CustomReadField to serialize
//! UniValue::type_error exceptions as text strings.
template <typename Value, typename Output>
void CustomBuildField(TypeList<UniValue::type_error>, Priority<1>, InvokeContext& invoke_context,
Value&& value, Output&& output)
{
BuildField(TypeList<std::string>(), invoke_context, output, std::string(value.what()));
}
template <typename Input, typename ReadDest>
decltype(auto) CustomReadField(TypeList<UniValue::type_error>, Priority<1>, InvokeContext& invoke_context,
Input&& input, ReadDest&& read_dest)
{
read_dest.construct(ReadField(TypeList<std::string>(), invoke_context, input, mp::ReadDestTemp<std::string>()));
}
//! Overload CustomBuildField and CustomReadField to serialize util::Result
//! return values as common.capnp Result and ResultVoid structs
template <typename LocalType, typename Value, typename Output>
void CustomBuildField(TypeList<util::Result<LocalType>>, Priority<1>, InvokeContext& invoke_context, Value&& value,
Output&& output)
{
auto result = output.init();
if (value) {
if constexpr (!std::is_same_v<LocalType, void>) {
using ValueAccessor = typename ProxyStruct<typename decltype(result)::Builds>::ValueAccessor;
BuildField(TypeList<LocalType>(), invoke_context, Make<StructField, ValueAccessor>(result), *value);
}
} else {
BuildField(TypeList<bilingual_str>(), invoke_context, Make<ValueField>(result.initError()),
util::ErrorString(value));
}
}
template <typename LocalType, typename Input, typename ReadDest>
decltype(auto) CustomReadField(TypeList<util::Result<LocalType>>, Priority<1>, InvokeContext& invoke_context,
Input&& input, ReadDest&& read_dest)
{
auto result = input.get();
if (result.hasError()) {
bilingual_str error;
ReadField(mp::TypeList<bilingual_str>(), invoke_context, mp::Make<mp::ValueField>(result.getError()),
mp::ReadDestUpdate(error));
read_dest.construct(util::Error{std::move(error)});
} else {
if constexpr (!std::is_same_v<LocalType, void>) {
assert (result.hasValue());
ReadField(mp::TypeList<LocalType>(), invoke_context, mp::Make<mp::ValueField>(result.getValue()),
mp::ReadDestEmplace(
mp::TypeList<LocalType>(), [&](auto&&... args) -> auto& {
return *read_dest.construct(LocalType{std::forward<decltype(args)>(args)...});
}));
} else {
read_dest.construct();
}
}
}
} // namespace mp
#endif // BITCOIN_IPC_CAPNP_COMMON_TYPES_H

View file

@ -14,3 +14,23 @@ struct BlockRef $Proxy.wrap("interfaces::BlockRef") {
hash @0 :Data;
height @1 :Int32;
}
struct BilingualStr $Proxy.wrap("bilingual_str") {
original @0 :Text;
translated @1 :Text;
}
struct Result(Value) {
value @0 :Value;
error @1: BilingualStr;
}
# Wrapper for util::Result<void>
struct ResultVoid(Value) {
error @0: BilingualStr;
}
struct Pair(Key, Value) {
key @0 :Key;
value @1 :Value;
}