1 // Copyright (c) 2023-present The Bitcoin Core developers
2 // Distributed under the MIT software license, see the accompanying
3 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4 5 #ifndef BITCOIN_IPC_CAPNP_COMMON_TYPES_H
6 #define BITCOIN_IPC_CAPNP_COMMON_TYPES_H
7 8 #include <clientversion.h>
9 #include <interfaces/types.h>
10 #include <primitives/transaction.h>
11 #include <serialize.h>
12 #include <streams.h>
13 #include <univalue.h>
14 15 #include <cstddef>
16 #include <mp/proxy-types.h>
17 #include <mp/type-chrono.h>
18 #include <mp/type-context.h>
19 #include <mp/type-data.h>
20 #include <mp/type-decay.h>
21 #include <mp/type-interface.h>
22 #include <mp/type-message.h>
23 #include <mp/type-number.h>
24 #include <mp/type-optional.h>
25 #include <mp/type-pointer.h>
26 #include <mp/type-string.h>
27 #include <mp/type-struct.h>
28 #include <mp/type-threadmap.h>
29 #include <mp/type-vector.h>
30 #include <type_traits>
31 #include <utility>
32 33 namespace ipc {
34 namespace capnp {
35 //! Construct a ParamStream wrapping a data stream with serialization parameters
36 //! needed to pass transaction objects between bitcoin processes.
37 //! In the future, more params may be added here to serialize other objects that
38 //! require serialization parameters. Params should just be chosen to serialize
39 //! objects completely and ensure that serializing and deserializing objects
40 //! with the specified parameters produces equivalent objects. It's also
41 //! harmless to specify serialization parameters here that are not used.
42 template <typename S>
43 auto Wrap(S& s)
44 {
45 return ParamsStream{s, TX_WITH_WITNESS};
46 }
47 48 //! Detect if type has a deserialize_type constructor, which is
49 //! used to deserialize types like CTransaction that can't be unserialized into
50 //! existing objects because they are immutable.
51 template <typename T>
52 concept Deserializable = std::is_constructible_v<T, ::deserialize_type, ::DataStream&>;
53 } // namespace capnp
54 } // namespace ipc
55 56 //! Functions to serialize / deserialize common bitcoin types.
57 namespace mp {
58 //! Overload multiprocess library's CustomBuildField hook to allow any
59 //! serializable object to be stored in a capnproto Data field or passed to a
60 //! capnproto interface. Use Priority<1> so this hook has medium priority, and
61 //! higher priority hooks could take precedence over this one.
62 template <typename LocalType, typename Value, typename Output>
63 void CustomBuildField(TypeList<LocalType>, Priority<1>, InvokeContext& invoke_context, Value&& value, Output&& output)
64 // Enable if serializeable and if LocalType is not cv or reference qualified. If
65 // LocalType is cv or reference qualified, it is important to fall back to
66 // lower-priority Priority<0> implementation of this function that strips cv
67 // references, to prevent this CustomBuildField overload from taking precedence
68 // over more narrow overloads for specific LocalTypes.
69 requires Serializable<LocalType, DataStream> && std::is_same_v<LocalType, std::remove_cv_t<std::remove_reference_t<LocalType>>>
70 {
71 DataStream stream;
72 auto wrapper{ipc::capnp::Wrap(stream)};
73 value.Serialize(wrapper);
74 auto result = output.init(stream.size());
75 memcpy(result.begin(), stream.data(), stream.size());
76 }
77 78 //! Overload multiprocess library's CustomReadField hook to allow any object
79 //! with an Unserialize method to be read from a capnproto Data field or
80 //! returned from capnproto interface. Use Priority<1> so this hook has medium
81 //! priority, and higher priority hooks could take precedence over this one.
82 template <typename LocalType, typename Input, typename ReadDest>
83 decltype(auto) CustomReadField(TypeList<LocalType>, Priority<1>, InvokeContext& invoke_context, Input&& input, ReadDest&& read_dest)
84 requires Unserializable<LocalType, DataStream> && (!ipc::capnp::Deserializable<LocalType>)
85 {
86 return read_dest.update([&](auto& value) {
87 if (!input.has()) return;
88 auto data = input.get();
89 SpanReader stream({data.begin(), data.end()});
90 auto wrapper{ipc::capnp::Wrap(stream)};
91 value.Unserialize(wrapper);
92 });
93 }
94 95 //! Overload multiprocess library's CustomReadField hook to allow any object
96 //! with a deserialize constructor to be read from a capnproto Data field or
97 //! returned from capnproto interface. Use Priority<1> so this hook has medium
98 //! priority, and higher priority hooks could take precedence over this one.
99 template <typename LocalType, typename Input, typename ReadDest>
100 decltype(auto) CustomReadField(TypeList<LocalType>, Priority<1>, InvokeContext& invoke_context, Input&& input, ReadDest&& read_dest)
101 requires ipc::capnp::Deserializable<LocalType>
102 {
103 assert(input.has());
104 auto data = input.get();
105 SpanReader stream({data.begin(), data.end()});
106 auto wrapper{ipc::capnp::Wrap(stream)};
107 return read_dest.construct(::deserialize, wrapper);
108 }
109 110 //! Overload CustomBuildField and CustomReadField to serialize UniValue
111 //! parameters and return values as JSON strings.
112 template <typename Value, typename Output>
113 void CustomBuildField(TypeList<UniValue>, Priority<1>, InvokeContext& invoke_context, Value&& value, Output&& output)
114 {
115 std::string str = value.write();
116 auto result = output.init(str.size());
117 memcpy(result.begin(), str.data(), str.size());
118 }
119 120 template <typename Input, typename ReadDest>
121 decltype(auto) CustomReadField(TypeList<UniValue>, Priority<1>, InvokeContext& invoke_context, Input&& input,
122 ReadDest&& read_dest)
123 {
124 return read_dest.update([&](auto& value) {
125 auto data = input.get();
126 value.read(std::string_view{data.begin(), data.size()});
127 });
128 }
129 130 //! Interpret empty Data fields as null CTransactionRef values. This is safe to
131 //! do because no CTransaction is ever serialized as empty Data, and it is
132 //! convenient because this allows std::vector<CTransactionRef> to be passed as
133 //! List(Data) even if the vector contains null values, and even though Cap'n
134 //! Proto does not (currently) allow distinguishing between null and empty Data
135 //! values in a List. Interpreting empty Data values as null CTransactionRef
136 //! values works well for this purpose.
137 template <typename Input>
138 bool CustomHasField(TypeList<CTransaction>, InvokeContext& invoke_context, const Input& input)
139 {
140 return input.get().size() > 0;
141 }
142 } // namespace mp
143 144 #endif // BITCOIN_IPC_CAPNP_COMMON_TYPES_H
145