From 6c42cf41be4c288507eb9ed1d355f93708a2f26e Mon Sep 17 00:00:00 2001 From: Russell Yanofsky Date: Tue, 5 Dec 2017 15:57:12 -0500 Subject: [PATCH] Add capnp wrapper for Chain interface --- src/interfaces/chain.h | 5 +- src/ipc/CMakeLists.txt | 2 + src/ipc/capnp/chain-types.h | 91 ++++++++++++++++ src/ipc/capnp/chain.capnp | 196 +++++++++++++++++++++++++++++++++ src/ipc/capnp/chain.cpp | 205 +++++++++++++++++++++++++++++++++++ src/ipc/capnp/common-types.h | 37 +++++++ src/ipc/capnp/common.capnp | 5 + src/node/interfaces.cpp | 2 +- src/rpc/request.h | 2 + 9 files changed, 542 insertions(+), 3 deletions(-) create mode 100644 src/ipc/capnp/chain-types.h create mode 100644 src/ipc/capnp/chain.capnp create mode 100644 src/ipc/capnp/chain.cpp diff --git a/src/interfaces/chain.h b/src/interfaces/chain.h index c9ef46243c4..f2cc3bb7dc1 100644 --- a/src/interfaces/chain.h +++ b/src/interfaces/chain.h @@ -357,7 +357,7 @@ public: //! support for writing null values to settings.json. //! Depending on the action returned by the update function, this will either //! update the setting in memory or write the updated settings to disk. - virtual bool updateRwSetting(const std::string& name, const SettingsUpdate& update_function) = 0; + virtual bool updateRwSetting(const std::string& name, SettingsUpdate update_function) = 0; //! Replace a setting in /settings.json with a new value. //! Null can be passed to erase the setting. @@ -404,7 +404,8 @@ public: //! Load saved state. virtual bool load() = 0; - //! Start client execution and provide a scheduler. + //! Start client execution and provide a scheduler. (Scheduler is + //! ignored if client is out-of-process). virtual void start(CScheduler& scheduler) = 0; //! Save state to disk. diff --git a/src/ipc/CMakeLists.txt b/src/ipc/CMakeLists.txt index 05cd6e69e57..40154811124 100644 --- a/src/ipc/CMakeLists.txt +++ b/src/ipc/CMakeLists.txt @@ -3,6 +3,7 @@ # file COPYING or https://opensource.org/license/mit/. add_library(bitcoin_ipc STATIC EXCLUDE_FROM_ALL + capnp/chain.cpp capnp/mining.cpp capnp/protocol.cpp interfaces.cpp @@ -10,6 +11,7 @@ add_library(bitcoin_ipc STATIC EXCLUDE_FROM_ALL ) target_capnp_sources(bitcoin_ipc ${PROJECT_SOURCE_DIR} + capnp/chain.capnp capnp/common.capnp capnp/echo.capnp capnp/handler.capnp diff --git a/src/ipc/capnp/chain-types.h b/src/ipc/capnp/chain-types.h new file mode 100644 index 00000000000..2287f154426 --- /dev/null +++ b/src/ipc/capnp/chain-types.h @@ -0,0 +1,91 @@ +// Copyright (c) 2024 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_IPC_CAPNP_CHAIN_TYPES_H +#define BITCOIN_IPC_CAPNP_CHAIN_TYPES_H + +#include +#include +#include +#include +#include +#include + +//! Specialization of handleRpc needed because it takes a CRPCCommand& reference +//! argument, so a manual cleanup callback is needed to free the passed +//! CRPCCommand struct and proxy ActorCallback object. +template <> +struct mp::ProxyServerMethodTraits +{ + using Context = ServerContext; + static ::capnp::Void invoke(Context& context); +}; + +//! Specialization of start method needed to provide CScheduler& reference +//! argument. +template <> +struct mp::ProxyServerMethodTraits +{ + using ChainContext = ServerContext; + static void invoke(ChainContext& context); +}; + +namespace mp { +//! Overload CustomBuildMessage, CustomPassMessage, and CustomReadMessage to +//! serialize interfaces::FoundBlock parameters. Custom conversion functions are +//! needed because there is not a 1:1 correspondence between members of the C++ +//! FoundBlock class and the Cap'n Proto FoundBlockParam and FoundBlockResult +//! structs. Separate param and result structs are needed because Cap'n Proto +//! only has input and output parameters, not in/out parameters like C++. +void CustomBuildMessage(InvokeContext& invoke_context, + const interfaces::FoundBlock& dest, + ipc::capnp::messages::FoundBlockParam::Builder&& builder); +void CustomPassMessage(InvokeContext& invoke_context, + const ipc::capnp::messages::FoundBlockParam::Reader& reader, + ipc::capnp::messages::FoundBlockResult::Builder&& builder, + std::function&& fn); +void CustomReadMessage(InvokeContext& invoke_context, + const ipc::capnp::messages::FoundBlockResult::Reader& reader, + const interfaces::FoundBlock& dest); + +//! Overload CustomBuildMessage and CustomPassMessage to serialize +//! interfaces::BlockInfo parameters. Custom conversion functions are needed +//! because BlockInfo structs contain pointer and reference members, and custom +//! code is needed to deal with their lifetimes. Specifics are described in the +//! implementation of these functions. +void CustomBuildMessage(InvokeContext& invoke_context, + const interfaces::BlockInfo& block, + ipc::capnp::messages::BlockInfo::Builder&& builder); +void CustomPassMessage(InvokeContext& invoke_context, + const ipc::capnp::messages::BlockInfo::Reader& reader, + ::capnp::Void builder, + std::function&& fn); + +//! CScheduler& server-side argument handling. Skips argument so it can +//! be handled by ProxyServerMethodTraits ChainClient::Start code. +template +void CustomPassField(TypeList, ServerContext& server_context, const Fn& fn, Args&&... args) +{ + fn.invoke(server_context, std::forward(args)...); +} + +//! CRPCCommand& server-side argument handling. Skips argument so it can +//! be handled by ProxyServerMethodTraits Chain::HandleRpc code. +template +void CustomPassField(TypeList, ServerContext& server_context, const Fn& fn, Args&&... args) +{ + fn.invoke(server_context, std::forward(args)...); +} + +//! Override to avoid assert failures that would happen trying to serialize +//! spent coins. Probably it would be best for Coin serialization code not +//! to assert, but avoiding serialization in this case is harmless. +bool CustomHasValue(InvokeContext& invoke_context, const Coin& coin); +} // namespace mp + +#endif // BITCOIN_IPC_CAPNP_CHAIN_TYPES_H diff --git a/src/ipc/capnp/chain.capnp b/src/ipc/capnp/chain.capnp new file mode 100644 index 00000000000..2511b8f72b7 --- /dev/null +++ b/src/ipc/capnp/chain.capnp @@ -0,0 +1,196 @@ +# Copyright (c) 2024 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +@0x94f21a4864bd2c65; + +using Cxx = import "/capnp/c++.capnp"; +$Cxx.namespace("ipc::capnp::messages"); + +using Proxy = import "/mp/proxy.capnp"; +$Proxy.include("interfaces/chain.h"); +$Proxy.include("rpc/server.h"); +$Proxy.includeTypes("ipc/capnp/chain-types.h"); + +using Common = import "common.capnp"; +using Handler = import "handler.capnp"; + +interface Chain $Proxy.wrap("interfaces::Chain") { + destroy @0 (context :Proxy.Context) -> (); + getHeight @1 (context :Proxy.Context) -> (result :Int32, hasResult :Bool); + getBlockHash @2 (context :Proxy.Context, height :Int32) -> (result :Data); + haveBlockOnDisk @3 (context :Proxy.Context, height :Int32) -> (result :Bool); + getTipLocator @4 (context :Proxy.Context) -> (result :Data); + getActiveChainLocator @5 (context :Proxy.Context, blockHash :Data) -> (result :Data); + findLocatorFork @6 (context :Proxy.Context, locator :Data) -> (result :Int32, hasResult :Bool); + hasBlockFilterIndex @7 (context :Proxy.Context, filterType :UInt8) -> (result :Bool); + blockFilterMatchesAny @8 (context :Proxy.Context, filterType :UInt8, blockHash :Data, filterSet :List(Data)) -> (result :Bool, hasResult :Bool); + findBlock @9 (context :Proxy.Context, hash :Data, block :FoundBlockParam) -> (block :FoundBlockResult, result :Bool); + findFirstBlockWithTimeAndHeight @10 (context :Proxy.Context, minTime :Int64, minHeight :Int32, block :FoundBlockParam) -> (block :FoundBlockResult, result :Bool); + findAncestorByHeight @11 (context :Proxy.Context, blockHash :Data, ancestorHeight :Int32, ancestor :FoundBlockParam) -> (ancestor :FoundBlockResult, result :Bool); + findAncestorByHash @12 (context :Proxy.Context, blockHash :Data, ancestorHash :Data, ancestor :FoundBlockParam) -> (ancestor :FoundBlockResult, result :Bool); + findCommonAncestor @13 (context :Proxy.Context, blockHash1 :Data, blockHash2 :Data, ancestor :FoundBlockParam, block1 :FoundBlockParam, block2 :FoundBlockParam) -> (ancestor :FoundBlockResult, block1 :FoundBlockResult, block2 :FoundBlockResult, result :Bool); + findCoins @14 (context :Proxy.Context, coins :List(Common.Pair(Data, Data))) -> (coins :List(Common.Pair(Data, Data))); + guessVerificationProgress @15 (context :Proxy.Context, blockHash :Data) -> (result :Float64); + hasBlocks @16 (context :Proxy.Context, blockHash :Data, minHeight :Int32, maxHeight: Int32, hasMaxHeight :Bool) -> (result :Bool); + isRBFOptIn @17 (context :Proxy.Context, tx :Data) -> (result :Int32); + isInMempool @18 (context :Proxy.Context, txid :Data) -> (result :Bool); + hasDescendantsInMempool @19 (context :Proxy.Context, txid :Data) -> (result :Bool); + broadcastTransaction @20 (context :Proxy.Context, tx: Data, maxTxFee :Int64, relay :Bool) -> (error: Text, result :Bool); + getTransactionAncestry @21 (context :Proxy.Context, txid :Data) -> (ancestors :UInt64, descendants :UInt64, ancestorsize :UInt64, ancestorfees :Int64); + calculateIndividualBumpFees @22 (context :Proxy.Context, outpoints :List(Data), targetFeerate :Data) -> (result: List(Common.PairInt64(Data))); + calculateCombinedBumpFee @23 (context :Proxy.Context, outpoints :List(Data), targetFeerate :Data) -> (result :Int64, hasResult :Bool); + getPackageLimits @24 (context :Proxy.Context) -> (ancestors :UInt64, descendants :UInt64); + checkChainLimits @25 (context :Proxy.Context, tx :Data) -> (result :Common.ResultVoid); + estimateSmartFee @26 (context :Proxy.Context, numBlocks :Int32, conservative :Bool, wantCalc :Bool) -> (calc :FeeCalculation, result :Data); + estimateMaxBlocks @27 (context :Proxy.Context) -> (result :UInt32); + mempoolMinFee @28 (context :Proxy.Context) -> (result :Data); + relayMinFee @29 (context :Proxy.Context) -> (result :Data); + relayIncrementalFee @30 (context :Proxy.Context) -> (result :Data); + relayDustFee @31 (context :Proxy.Context) -> (result :Data); + havePruned @32 (context :Proxy.Context) -> (result :Bool); + getPruneHeight @33 (context :Proxy.Context) -> (result: Int32, hasResult: Bool); + isReadyToBroadcast @34 (context :Proxy.Context) -> (result :Bool); + isInitialBlockDownload @35 (context :Proxy.Context) -> (result :Bool); + shutdownRequested @36 (context :Proxy.Context) -> (result :Bool); + initMessage @37 (context :Proxy.Context, message :Text) -> (); + initWarning @38 (context :Proxy.Context, message :Common.BilingualStr) -> (); + initError @39 (context :Proxy.Context, message :Common.BilingualStr) -> (); + showProgress @40 (context :Proxy.Context, title :Text, progress :Int32, resumePossible :Bool) -> (); + handleNotifications @41 (context :Proxy.Context, notifications :ChainNotifications) -> (result :Handler.Handler); + waitForNotificationsIfTipChanged @42 (context :Proxy.Context, oldTip :Data) -> (); + handleRpc @43 (context :Proxy.Context, command :RPCCommand) -> (result :Handler.Handler); + rpcEnableDeprecated @44 (context :Proxy.Context, method :Text) -> (result :Bool); + rpcRunLater @45 (context :Proxy.Context, name :Text, fn: RunLaterCallback, seconds: Int64) -> (); + getSetting @46 (context :Proxy.Context, name :Text) -> (result :Text); + getSettingsList @47 (context :Proxy.Context, name :Text) -> (result :List(Text)); + getRwSetting @48 (context :Proxy.Context, name :Text) -> (result :Text); + updateRwSetting @49 (context :Proxy.Context, name :Text, update: SettingsUpdateCallback) -> (result :Bool); + overwriteRwSetting @50 (context :Proxy.Context, name :Text, value :Text, action :Int32) -> (result :Bool); + deleteRwSettings @51 (context :Proxy.Context, name :Text, action: Int32) -> (result :Bool); + requestMempoolTransactions @52 (context :Proxy.Context, notifications :ChainNotifications) -> (); + hasAssumedValidChain @53 (context :Proxy.Context) -> (result :Bool); +} + +interface ChainNotifications $Proxy.wrap("interfaces::Chain::Notifications") { + destroy @0 (context :Proxy.Context) -> (); + transactionAddedToMempool @1 (context :Proxy.Context, tx :Data) -> (); + transactionRemovedFromMempool @2 (context :Proxy.Context, tx :Data, reason :Int32) -> (); + blockConnected @3 (context :Proxy.Context, role: UInt32, block :BlockInfo) -> (); + blockDisconnected @4 (context :Proxy.Context, block :BlockInfo) -> (); + updatedBlockTip @5 (context :Proxy.Context) -> (); + chainStateFlushed @6 (context :Proxy.Context, role: UInt32, locator :Data) -> (); +} + +interface ChainClient $Proxy.wrap("interfaces::ChainClient") { + destroy @0 (context :Proxy.Context) -> (); + registerRpcs @1 (context :Proxy.Context) -> (); + verify @2 (context :Proxy.Context) -> (result :Bool); + load @3 (context :Proxy.Context) -> (result :Bool); + start @4 (context :Proxy.Context, scheduler :Void) -> (); + flush @5 (context :Proxy.Context) -> (); + stop @6 (context :Proxy.Context) -> (); + setMockTime @7 (context :Proxy.Context, time :Int64) -> (); + schedulerMockForward @8 (context :Proxy.Context, time :Int64) -> (); +} + +struct FeeCalculation $Proxy.wrap("FeeCalculation") { + est @0 :EstimationResult; + reason @1 :Int32; + desiredTarget @2 :Int32; + returnedTarget @3 :Int32; +} + +struct EstimationResult $Proxy.wrap("EstimationResult") +{ + pass @0 :EstimatorBucket; + fail @1 :EstimatorBucket; + decay @2 :Float64; + scale @3 :UInt32; +} + +struct EstimatorBucket $Proxy.wrap("EstimatorBucket") +{ + start @0 :Float64; + end @1 :Float64; + withinTarget @2 :Float64; + totalConfirmed @3 :Float64; + inMempool @4 :Float64; + leftMempool @5 :Float64; +} + +struct RPCCommand $Proxy.wrap("CRPCCommand") { + category @0 :Text; + name @1 :Text; + actor @2 :ActorCallback; + argNames @3 :List(RPCArg); + uniqueId @4 :Int64 $Proxy.name("unique_id"); +} + +struct RPCArg { + name @0 :Text; + namedOnly @1: Bool; +} + +interface ActorCallback $Proxy.wrap("ProxyCallback") { + call @0 (context :Proxy.Context, request :JSONRPCRequest, response :Text, lastCallback :Bool) -> (error :Text $Proxy.exception("std::exception"), rpcError :Text $Proxy.exception("UniValue"), typeError :Text $Proxy.exception("UniValue::type_error"), response :Text, result: Bool); +} + +struct JSONRPCRequest $Proxy.wrap("JSONRPCRequest") { + id @0 :Text; + method @1 :Text $Proxy.name("strMethod"); + params @2 :Text; + mode @3 :UInt32; + uri @4 :Text $Proxy.name("URI"); + authUser @5 :Text; + peerAddr @6 :Text; + version @7: Int32 $Proxy.name("m_json_version"); +} + +interface RunLaterCallback $Proxy.wrap("ProxyCallback>") { + destroy @0 (context :Proxy.Context) -> (); + call @1 (context :Proxy.Context) -> (); +} + +struct FoundBlockParam { + wantHash @0 :Bool; + wantHeight @1 :Bool; + wantTime @2 :Bool; + wantMaxTime @3 :Bool; + wantMtpTime @4 :Bool; + wantInActiveChain @5 :Bool; + wantLocator @6 :Bool; + nextBlock @7: FoundBlockParam; + wantData @8 :Bool; +} + +struct FoundBlockResult { + hash @0 :Data; + height @1 :Int32; + time @2 :Int64; + maxTime @3 :Int64; + mtpTime @4 :Int64; + inActiveChain @5 :Int64; + locator @6 :Data; + nextBlock @7: FoundBlockResult; + data @8 :Data; + found @9 :Bool; +} + +struct BlockInfo $Proxy.wrap("interfaces::BlockInfo") { + # Fields skipped below with Proxy.skip are pointer fields manually handled + # by CustomBuildMessage / CustomPassMessage overloads. + hash @0 :Data $Proxy.skip; + prevHash @1 :Data $Proxy.skip; + height @2 :Int32 = -1; + fileNumber @3 :Int32 = -1 $Proxy.name("file_number"); + dataPos @4 :UInt32 = 0 $Proxy.name("data_pos"); + data @5 :Data $Proxy.skip; + undoData @6 :Data $Proxy.skip; + chainTimeMax @7 :UInt32 = 0 $Proxy.name("chain_time_max"); +} + +interface SettingsUpdateCallback $Proxy.wrap("ProxyCallback") { + destroy @0 (context :Proxy.Context) -> (); + call @1 (context :Proxy.Context, value :Text) -> (value :Text, result: Int32, hasResult: Bool); +} diff --git a/src/ipc/capnp/chain.cpp b/src/ipc/capnp/chain.cpp new file mode 100644 index 00000000000..8a24b00a1e9 --- /dev/null +++ b/src/ipc/capnp/chain.cpp @@ -0,0 +1,205 @@ +// Copyright (c) 2024 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace mp { +void CustomBuildMessage(InvokeContext& invoke_context, + const interfaces::FoundBlock& dest, + ipc::capnp::messages::FoundBlockParam::Builder&& builder) +{ + if (dest.m_hash) builder.setWantHash(true); + if (dest.m_height) builder.setWantHeight(true); + if (dest.m_time) builder.setWantTime(true); + if (dest.m_max_time) builder.setWantMaxTime(true); + if (dest.m_mtp_time) builder.setWantMtpTime(true); + if (dest.m_in_active_chain) builder.setWantInActiveChain(true); + if (dest.m_locator) builder.setWantLocator(true); + if (dest.m_next_block) CustomBuildMessage(invoke_context, *dest.m_next_block, builder.initNextBlock()); + if (dest.m_data) builder.setWantData(true); +} + +void FindBlock(const std::function& find, + const ipc::capnp::messages::FoundBlockParam::Reader& reader, + ipc::capnp::messages::FoundBlockResult::Builder&& builder, + interfaces::FoundBlock& found_block) +{ + uint256 hash; + int height = -1; + int64_t time = -1; + int64_t max_time = -1; + int64_t mtp_time = -1; + bool in_active_chain = -1; + CBlockLocator locator; + CBlock data; + if (reader.getWantHash()) found_block.hash(hash); + if (reader.getWantHeight()) found_block.height(height); + if (reader.getWantTime()) found_block.time(time); + if (reader.getWantMaxTime()) found_block.maxTime(max_time); + if (reader.getWantMtpTime()) found_block.mtpTime(mtp_time); + if (reader.getWantInActiveChain()) found_block.inActiveChain(in_active_chain); + if (reader.getWantLocator()) found_block.locator(locator); + if (reader.getWantData()) found_block.data(data); + if (reader.hasNextBlock()) { + interfaces::FoundBlock next_block; + found_block.nextBlock(next_block); + FindBlock(find, reader.getNextBlock(), builder.initNextBlock(), next_block); + } else { + find(); + } + if (!found_block.found) return; + if (reader.getWantHash()) builder.setHash(ipc::capnp::ToArray(ipc::capnp::Serialize(hash))); + if (reader.getWantHeight()) builder.setHeight(height); + if (reader.getWantTime()) builder.setTime(time); + if (reader.getWantMaxTime()) builder.setMaxTime(max_time); + if (reader.getWantMtpTime()) builder.setMtpTime(mtp_time); + if (reader.getWantInActiveChain()) builder.setInActiveChain(in_active_chain); + if (reader.getWantLocator()) builder.setLocator(ipc::capnp::ToArray(ipc::capnp::Serialize(locator))); + if (reader.getWantData()) builder.setData(ipc::capnp::ToArray(ipc::capnp::Serialize(data))); + builder.setFound(true); +} + +void CustomPassMessage(InvokeContext& invoke_context, + const ipc::capnp::messages::FoundBlockParam::Reader& reader, + ipc::capnp::messages::FoundBlockResult::Builder&& builder, + std::function&& fn) +{ + interfaces::FoundBlock found_block; + FindBlock([&] { fn(found_block); }, reader, std::move(builder), found_block); +} + +void CustomReadMessage(InvokeContext& invoke_context, + const ipc::capnp::messages::FoundBlockResult::Reader& reader, + const interfaces::FoundBlock& dest) +{ + if (!reader.getFound()) return; + if (dest.m_hash) *dest.m_hash = ipc::capnp::Unserialize(reader.getHash()); + if (dest.m_height) *dest.m_height = reader.getHeight(); + if (dest.m_time) *dest.m_time = reader.getTime(); + if (dest.m_max_time) *dest.m_max_time = reader.getMaxTime(); + if (dest.m_mtp_time) *dest.m_mtp_time = reader.getMtpTime(); + if (dest.m_in_active_chain) *dest.m_in_active_chain = reader.getInActiveChain(); + if (dest.m_locator) *dest.m_locator = ipc::capnp::Unserialize(reader.getLocator()); + if (dest.m_next_block) CustomReadMessage(invoke_context, reader.getNextBlock(), *dest.m_next_block); + if (dest.m_data) *dest.m_data = ipc::capnp::Unserialize(reader.getData()); + dest.found = true; +} + +void CustomBuildMessage(InvokeContext& invoke_context, + const interfaces::BlockInfo& block, + ipc::capnp::messages::BlockInfo::Builder&& builder) +{ + // Pointer fields are annotated with Proxy.skip so need to be filled + // manually. Generated code would actually work correctly, though, so this + // could be dropped if there were a Proxy.skip(read_only=True) half-skip + // option. + builder.setHash(ipc::capnp::ToArray(block.hash)); + if (block.prev_hash) builder.setPrevHash(ipc::capnp::ToArray(*block.prev_hash)); + if (block.data) builder.setData(ipc::capnp::ToArray(ipc::capnp::Serialize(*block.data))); + if (block.undo_data) builder.setUndoData(ipc::capnp::ToArray(ipc::capnp::Serialize(*block.undo_data))); + // Copy the remaining fields using the code generated by Proxy.wrap. + mp::BuildOne<0>(mp::TypeList(), invoke_context, builder, block); +} + +void CustomPassMessage(InvokeContext& invoke_context, + const ipc::capnp::messages::BlockInfo::Reader& reader, + ::capnp::Void builder, + std::function&& fn) +{ + // Pointer fields are annotated with Proxy.skip because code generator can't + // figure out pointer lifetimes to be able to implementat a ReadField + // implementation for the BlockInfo struct, and the default PassField + // function calls readfield. In theory though, code generator could create a + // PassField specialization for the struct which could allocate pointers for + // the lifetime of the call, like the code below is doing manually. This + // would take care of all pointer fields, though the BlockInfo.hash + // reference field would still need to be handled specially. Or even + // BlockInfo.hash field could be handled automatically if the generated code + // used C++20 designated member initialization. + const uint256 hash = ipc::capnp::ToBlob(reader.getHash()); + std::optional prev_hash; + std::optional data; + std::optional undo_data; + interfaces::BlockInfo block{hash}; + if (reader.hasPrevHash()) { + prev_hash.emplace(ipc::capnp::ToBlob(reader.getPrevHash())); + block.prev_hash = &*prev_hash; + } + if (reader.hasData()) { + data.emplace(ipc::capnp::Unserialize(reader.getData())); + block.data = &*data; + } + if (reader.hasUndoData()) { + undo_data.emplace(ipc::capnp::Unserialize(reader.getUndoData())); + block.undo_data = &*undo_data; + } + mp::ReadField(mp::TypeList(), invoke_context, + mp::Make(reader), mp::ReadDestUpdate(block)); + fn(block); +} + +::capnp::Void ProxyServerMethodTraits::invoke( + Context& context) +{ + auto params = context.call_context.getParams(); + auto command = params.getCommand(); + + CRPCCommand::Actor actor; + ReadField(TypeList(), context, Make(command.getActor()), ReadDestUpdate(actor)); + std::vector> args; + ReadField(TypeList(), context, Make(command.getArgNames()), ReadDestUpdate(args)); + + auto rpc_command = std::make_unique(command.getCategory(), command.getName(), std::move(actor), + std::move(args), command.getUniqueId()); + auto handler = context.proxy_server.m_impl->handleRpc(*rpc_command); + auto results = context.call_context.getResults(); + auto result = kj::heap>(std::shared_ptr(handler.release()), *context.proxy_server.m_context.connection); + result->m_context.cleanup_fns.emplace_back([rpc_command = rpc_command.release()] { delete rpc_command; }); + results.setResult(kj::mv(result)); + return {}; +} + +void ProxyServerMethodTraits::invoke(ChainContext& context) +{ + // This method is never called because ChainClient::Start is overridden by + // WalletLoader::Start. The custom implementation is needed just because + // the CScheduler& argument this is supposed to pass is not serializable. + assert(0); +} + +bool CustomHasValue(InvokeContext& invoke_context, const Coin& coin) +{ + // Spent coins cannot be serialized due to an assert in Coin::Serialize. + return !coin.IsSpent(); +} +} // namespace mp diff --git a/src/ipc/capnp/common-types.h b/src/ipc/capnp/common-types.h index d507b5dd65d..333f90b5b25 100644 --- a/src/ipc/capnp/common-types.h +++ b/src/ipc/capnp/common-types.h @@ -21,7 +21,10 @@ #include #include #include +#include +#include #include +#include #include #include #include @@ -30,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -223,6 +227,39 @@ decltype(auto) CustomReadField(TypeList>, Priority<1>, I } } } + +// libmultiprocess only provides read/build functions for std::set, not +// std::unordered_set, so copy and paste those functions here. +// TODO: Move these to libmultiprocess and dedup std::set, std::unordered_set, +// and std::vector implementations. +template +decltype(auto) CustomReadField(TypeList>, Priority<1>, + InvokeContext& invoke_context, Input&& input, ReadDest&& read_dest) +{ + return read_dest.update([&](auto& value) { + auto data = input.get(); + value.clear(); + for (auto item : data) { + ReadField(TypeList(), invoke_context, Make(item), + ReadDestEmplace( + TypeList(), [&](auto&&... args) -> auto& { + return *value.emplace(std::forward(args)...).first; + })); + } + }); +} + +template +void CustomBuildField(TypeList>, Priority<1>, InvokeContext& invoke_context, + Value&& value, Output&& output) +{ + auto list = output.init(value.size()); + size_t i = 0; + for (const auto& elem : value) { + BuildField(TypeList(), invoke_context, ListOutput(list, i), elem); + ++i; + } +} } // namespace mp #endif // BITCOIN_IPC_CAPNP_COMMON_TYPES_H diff --git a/src/ipc/capnp/common.capnp b/src/ipc/capnp/common.capnp index 691fa80f4d5..2c14da5b845 100644 --- a/src/ipc/capnp/common.capnp +++ b/src/ipc/capnp/common.capnp @@ -34,3 +34,8 @@ struct Pair(Key, Value) { key @0 :Key; value @1 :Value; } + +struct PairInt64(Key) { + key @0 :Key; + value @1 :Int64; +} diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp index 73ce927f712..34916c3e1f8 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -826,7 +826,7 @@ public: return result; } bool updateRwSetting(const std::string& name, - const interfaces::SettingsUpdate& update_settings_func) override + interfaces::SettingsUpdate update_settings_func) override { std::optional action; args().LockSettings([&](common::Settings& settings) { diff --git a/src/rpc/request.h b/src/rpc/request.h index 24887e8691c..036a00803d9 100644 --- a/src/rpc/request.h +++ b/src/rpc/request.h @@ -44,6 +44,8 @@ public: std::string peerAddr; std::any context; JSONRPCVersion m_json_version = JSONRPCVersion::V1_LEGACY; + // Note: If you add fields to this struct, you should also update the + // JSONRPCRequest struct in ipc/capnp/chain.capnp. void parse(const UniValue& valRequest); [[nodiscard]] bool IsNotification() const { return !id.has_value() && m_json_version == JSONRPCVersion::V2; };