From 095286f790acda4a32f04c77aa86106007e2a0d8 Mon Sep 17 00:00:00 2001 From: Russell Yanofsky Date: Tue, 5 Dec 2017 15:57:12 -0500 Subject: [PATCH] multiprocess: Add serialization code for CTransaction Add support for passing CTransaction and CTransactionRef types to IPC functions. These types can't be passed currently because IPC serialization code currently only supports deserializing types that have an Unserialize() method, which CTransaction does not, because it is supposed to represent immutable transactions. Work around this by adding a CustomReadField overload that will call CTransaction's deserialize_type constructor. These types also can't be passed currently because serializing transactions requires TransactionSerParams to be set. Fix this by setting TX_WITH_WITNESS as default serialization parameters for IPC code. --- src/ipc/capnp/common-types.h | 42 +++++++++++++++++++++++++++++++++--- src/test/ipc_test.capnp | 1 + src/test/ipc_test.cpp | 9 ++++++++ src/test/ipc_test.h | 1 + 4 files changed, 50 insertions(+), 3 deletions(-) diff --git a/src/ipc/capnp/common-types.h b/src/ipc/capnp/common-types.h index 3e238648cfc..302da76c055 100644 --- a/src/ipc/capnp/common-types.h +++ b/src/ipc/capnp/common-types.h @@ -6,6 +6,7 @@ #define BITCOIN_IPC_CAPNP_COMMON_TYPES_H #include +#include #include #include #include @@ -17,6 +18,24 @@ namespace ipc { namespace capnp { +//! Construct a ParamStream wrapping a data stream with serialization parameters +//! needed to pass transaction 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 +//! with the specified parameters produces equivalent objects. It's also +//! harmless to specify serialization parameters here that are not used. +template +auto Wrap(S& s) +{ + return ParamsStream{s, TX_WITH_WITNESS}; +} + +//! Detect if type has a deserialize_type constructor, which is +//! used to deserialize types like CTransaction that can't be unserialized into +//! existing objects because they are immutable. +template +concept Deserializable = std::is_constructible_v; } // namespace capnp } // namespace ipc @@ -36,7 +55,8 @@ void CustomBuildField(TypeList, Priority<1>, InvokeContext& invoke_co requires Serializable && std::is_same_v>> { DataStream stream; - value.Serialize(stream); + auto wrapper{ipc::capnp::Wrap(stream)}; + value.Serialize(wrapper); auto result = output.init(stream.size()); memcpy(result.begin(), stream.data(), stream.size()); } @@ -47,16 +67,32 @@ requires Serializable && std::is_same_v decltype(auto) CustomReadField(TypeList, Priority<1>, InvokeContext& invoke_context, Input&& input, ReadDest&& read_dest) -requires Unserializable +requires Unserializable && (!ipc::capnp::Deserializable) { return read_dest.update([&](auto& value) { if (!input.has()) return; auto data = input.get(); SpanReader stream({data.begin(), data.end()}); - value.Unserialize(stream); + auto wrapper{ipc::capnp::Wrap(stream)}; + value.Unserialize(wrapper); }); } +//! Overload multiprocess library's CustomReadField hook to allow any object +//! with a deserialize constructor to be read from a capnproto Data field or +//! returned from capnproto interface. Use Priority<1> so this hook has medium +//! priority, and higher priority hooks could take precedence over this one. +template +decltype(auto) CustomReadField(TypeList, Priority<1>, InvokeContext& invoke_context, Input&& input, ReadDest&& read_dest) +requires ipc::capnp::Deserializable +{ + assert(input.has()); + auto data = input.get(); + SpanReader stream({data.begin(), data.end()}); + auto wrapper{ipc::capnp::Wrap(stream)}; + return read_dest.construct(::deserialize, wrapper); +} + //! Overload CustomBuildField and CustomReadField to serialize UniValue //! parameters and return values as JSON strings. template diff --git a/src/test/ipc_test.capnp b/src/test/ipc_test.capnp index 55a3dc26839..00cfeb53215 100644 --- a/src/test/ipc_test.capnp +++ b/src/test/ipc_test.capnp @@ -15,4 +15,5 @@ interface FooInterface $Proxy.wrap("FooImplementation") { add @0 (a :Int32, b :Int32) -> (result :Int32); passOutPoint @1 (arg :Data) -> (result :Data); passUniValue @2 (arg :Text) -> (result :Text); + passTransaction @3 (arg :Data) -> (result :Data); } diff --git a/src/test/ipc_test.cpp b/src/test/ipc_test.cpp index e6de6e3e477..59083beabc6 100644 --- a/src/test/ipc_test.cpp +++ b/src/test/ipc_test.cpp @@ -88,6 +88,15 @@ void IpcPipeTest() UniValue uni2{foo->passUniValue(uni1)}; BOOST_CHECK_EQUAL(uni1.write(), uni2.write()); + CMutableTransaction mtx; + mtx.version = 2; + mtx.nLockTime = 3; + mtx.vin.emplace_back(txout1); + mtx.vout.emplace_back(COIN, CScript()); + CTransactionRef tx1{MakeTransactionRef(mtx)}; + CTransactionRef tx2{foo->passTransaction(tx1)}; + BOOST_CHECK(*Assert(tx1) == *Assert(tx2)); + // Test cleanup: disconnect pipe and join thread disconnect_client(); thread.join(); diff --git a/src/test/ipc_test.h b/src/test/ipc_test.h index 2453bfa23c7..22fe96b6bac 100644 --- a/src/test/ipc_test.h +++ b/src/test/ipc_test.h @@ -15,6 +15,7 @@ public: int add(int a, int b) { return a + b; } COutPoint passOutPoint(COutPoint o) { return o; } UniValue passUniValue(UniValue v) { return v; } + CTransactionRef passTransaction(CTransactionRef t) { return t; } }; void IpcPipeTest();