diff --git a/src/init/bitcoin-node.cpp b/src/init/bitcoin-node.cpp index 84fbb55ea822..0004367739e7 100644 --- a/src/init/bitcoin-node.cpp +++ b/src/init/bitcoin-node.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -30,6 +31,7 @@ class BitcoinNodeInit : public interfaces::Init std::unique_ptr makeNode() override { return interfaces::MakeNode(m_node); } std::unique_ptr makeChain() override { return interfaces::MakeChain(m_node); } std::unique_ptr makeMining() override { return interfaces::MakeMining(m_node); } + std::unique_ptr makeNodeRpc() override { return interfaces::MakeNodeRpc(m_node); } std::unique_ptr makeEcho() override { return interfaces::MakeEcho(); } void stop() override { makeNode()->startShutdown(); } interfaces::Ipc* ipc() override { return m_ipc.get(); } diff --git a/src/interfaces/init.h b/src/interfaces/init.h index a0e534577189..1f43b2b6b253 100644 --- a/src/interfaces/init.h +++ b/src/interfaces/init.h @@ -9,6 +9,7 @@ #include #include #include +#include #include @@ -33,6 +34,7 @@ class Init virtual std::unique_ptr makeNode() { return nullptr; } virtual std::unique_ptr makeChain() { return nullptr; } virtual std::unique_ptr makeMining() { return nullptr; } + virtual std::unique_ptr makeNodeRpc() { return nullptr; } virtual std::unique_ptr makeEcho() { return nullptr; } virtual void stop() {} virtual Ipc* ipc() { return nullptr; } diff --git a/src/interfaces/noderpc.h b/src/interfaces/noderpc.h new file mode 100644 index 000000000000..11579c39b4cf --- /dev/null +++ b/src/interfaces/noderpc.h @@ -0,0 +1,284 @@ +// Copyright (c) 2025-present 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_INTERFACES_NODERPC_H +#define BITCOIN_INTERFACES_NODERPC_H + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace node { +struct NodeContext; +} // namespace node + +namespace interfaces { + +struct BlockchainPruneInfo { + int height{0}; + bool automatic{false}; + uint64_t target_size{0}; +}; + +struct BlockchainInfo { + std::string chain; + int blocks{0}; + int headers{0}; + std::string bestblockhash; + std::string bits; + std::string target; + double difficulty{0}; + int64_t time{0}; + int64_t mediantime{0}; + double verificationprogress{0}; + bool initialblockdownload{false}; + std::string chainwork; + uint64_t size_on_disk{0}; + bool pruned{false}; + std::optional prune; + std::optional signet_challenge; + std::vector warnings; +}; + +struct BlockHeaderInfo { + std::string hash; + int confirmations{0}; + int height{0}; + int version{0}; + std::string version_hex; + std::string merkleroot; + int64_t time{0}; + int64_t mediantime{0}; + uint32_t nonce{0}; + std::string bits; + std::string target; + double difficulty{0}; + std::string chainwork; + int n_tx{0}; + std::optional previousblockhash; + std::optional nextblockhash; +}; + +struct SmartFeeEstimate { + int64_t feerate{0}; + std::vector errors; + int blocks{0}; +}; + +struct TxOutScriptPubKey { + std::string script_asm; + std::string desc; + std::string hex; + std::string type; + std::optional address; +}; + +struct TxOutInfo { + std::string bestblock; + int confirmations{0}; + int64_t value{0}; + TxOutScriptPubKey script_pub_key; + bool coinbase{false}; +}; + +struct TxScriptSig { + std::string script_asm; + std::string hex; +}; + +struct TxInput { + std::optional coinbase; + std::optional txid; + uint32_t vout{0}; + std::optional script_sig; + std::vector txinwitness; + uint32_t sequence{0}; +}; + +struct TxOutput { + int64_t value{0}; + int n{0}; + TxOutScriptPubKey script_pub_key; +}; + +struct TxBlockContext { + std::string blockhash; + int confirmations{0}; + int64_t time{0}; + int64_t blocktime{0}; +}; + +struct TransactionDetails { + std::string txid; + std::string hash; + int64_t version{0}; + int size{0}; + int vsize{0}; + int weight{0}; + int64_t locktime{0}; + std::vector vin; + std::vector vout; + bool in_active_chain{false}; + std::optional block; +}; + +struct RawTransactionResult { + CTransactionRef tx; + std::optional details; +}; + +struct TestMempoolAcceptFees { + int64_t base{0}; + int64_t effective_feerate{0}; + std::vector effective_includes; +}; + +struct TestMempoolAcceptResult { + std::string txid; + std::string wtxid; + std::optional package_error; + bool validated{false}; + bool allowed{false}; + std::optional reject_reason; + std::optional reject_details; + int64_t vsize{0}; + std::optional fees; +}; + +struct NetworkInfoNetwork { + std::string name; + bool limited{false}; + bool reachable{false}; + std::string proxy; + bool proxy_randomize_credentials{false}; +}; + +struct NetworkInfoLocalAddress { + std::string address; + uint16_t port{0}; + int score{0}; +}; + +struct NetworkInfo { + int version{0}; + std::string subversion; + int protocolversion{0}; + std::string localservices; + std::vector localservicesnames; + bool localrelay{false}; + int64_t timeoffset{0}; + size_t connections{0}; + size_t connections_in{0}; + size_t connections_out{0}; + bool networkactive{false}; + std::vector networks; + int64_t relayfee{0}; + int64_t incrementalfee{0}; + std::vector localaddresses; + std::vector warnings; +}; + +struct BIP9Statistics { + int period{0}; + int threshold{0}; + int elapsed{0}; + int count{0}; + bool possible{false}; +}; + +struct BIP9DeploymentInfo { + int bit{-1}; + int64_t start_time{0}; + int64_t timeout{0}; + int min_activation_height{0}; + std::string status; + int since{0}; + std::string status_next; + std::optional statistics; + std::string signalling; +}; + +struct DeploymentInfo { + std::string name; + std::string type; + bool active{false}; + int height{-1}; + std::optional bip9; +}; + +struct DeploymentsInfo { + std::string hash; + int height{-1}; + std::vector script_flags; + std::vector deployments; +}; + +class NodeRpc +{ +public: + virtual ~NodeRpc() = default; + + virtual BlockchainInfo getBlockchainInfo() = 0; + + //! Hash of the active chain tip, hex-encoded. Mirrors getbestblockhash. + virtual std::string getBestBlockHash() = 0; + + //! Hash of the active-chain block at the given height, hex-encoded. Throws + //! if the height is out of range. Mirrors getblockhash. + virtual std::string getBlockHash(int height) = 0; + + //! Verbose header info for the given block. Throws if the block is not + //! found. Mirrors the verbose getblockheader. + virtual BlockHeaderInfo getBlockHeader(const uint256& block_hash) = 0; + + //! Full block for the given hash, read from disk. Throws if the block is + //! not found or its data is unavailable (pruned). Mirrors getblock with + //! verbosity 0 (the block is serialized over the wire). + virtual CBlock getBlock(const uint256& block_hash) = 0; + + //! Details about an unspent transaction output, or nullopt if the output is + //! not found (spent or never existed). When include_mempool is set, outputs + //! spent in the mempool are treated as unavailable. The value is in + //! satoshis. Mirrors gettxout. + virtual std::optional getTxOut(const uint256& txid, uint32_t n, bool include_mempool) = 0; + + //! Transaction for the given txid, from the mempool or a given block (no tx + //! index, so confirmed txs need block_hash). Throws if not found. result.tx + //! is the raw tx (verbosity 0); result.details is set when verbose (verbosity 1). + virtual RawTransactionResult getRawTransaction(const uint256& txid, bool verbose, const std::optional& block_hash) = 0; + + //! Test whether the given transactions (1..MAX_PACKAGE_COUNT, as a package + //! when >1) would be accepted to the mempool, without submitting them. + //! max_fee_rate is in sat/kvB; 0 disables the max-fee check. One result per + //! input tx, in order. Mirrors testmempoolaccept. + virtual std::vector testMempoolAccept(const std::vector& txns, int64_t max_fee_rate) = 0; + + //! Submit a transaction to the mempool and broadcast it (or, with + //! -privatebroadcast, send it only over privacy networks). max_fee_rate is + //! in sat/kvB (0 accepts any fee rate); max_burn_amount is in sat (outputs + //! provably unspendable above this are rejected). Returns the txid (hex) on + //! success, throws on failure. Mirrors the sendrawtransaction RPC. + virtual std::string sendRawTransaction(const CTransactionRef& tx, int64_t max_fee_rate, int64_t max_burn_amount) = 0; + + //! Estimate the fee rate (sat/kvB) needed for confirmation within + //! conf_target blocks, mirroring the estimatesmartfee RPC (applies the + //! mempool/relay fee floor and reports the target actually used). + virtual SmartFeeEstimate estimateSmartFee(int conf_target, bool conservative) = 0; + + virtual NetworkInfo getNetworkInfo() = 0; + + virtual DeploymentsInfo getDeploymentInfo(const std::optional& block_hash) = 0; +}; + +std::unique_ptr MakeNodeRpc(node::NodeContext& context); + +} // namespace interfaces + +#endif // BITCOIN_INTERFACES_NODERPC_H diff --git a/src/ipc/CMakeLists.txt b/src/ipc/CMakeLists.txt index 5378ef1924bf..970d423ba95f 100644 --- a/src/ipc/CMakeLists.txt +++ b/src/ipc/CMakeLists.txt @@ -14,6 +14,7 @@ target_capnp_sources(bitcoin_ipc ${CMAKE_CURRENT_SOURCE_DIR} capnp/echo.capnp capnp/init.capnp capnp/mining.capnp + capnp/noderpc.capnp ) target_link_libraries(bitcoin_ipc diff --git a/src/ipc/capnp/init-types.h b/src/ipc/capnp/init-types.h index 2abd7b211e17..ab804c32d9aa 100644 --- a/src/ipc/capnp/init-types.h +++ b/src/ipc/capnp/init-types.h @@ -7,5 +7,6 @@ #include #include +#include #endif // BITCOIN_IPC_CAPNP_INIT_TYPES_H diff --git a/src/ipc/capnp/init.capnp b/src/ipc/capnp/init.capnp index 094305b44341..6e419b931e30 100644 --- a/src/ipc/capnp/init.capnp +++ b/src/ipc/capnp/init.capnp @@ -11,14 +11,17 @@ using Proxy = import "/mp/proxy.capnp"; $Proxy.include("interfaces/echo.h"); $Proxy.include("interfaces/init.h"); $Proxy.include("interfaces/mining.h"); +$Proxy.include("interfaces/noderpc.h"); $Proxy.includeTypes("ipc/capnp/init-types.h"); using Echo = import "echo.capnp"; using Mining = import "mining.capnp"; +using NodeRpc = import "noderpc.capnp"; interface Init $Proxy.wrap("interfaces::Init") { construct @0 (threadMap: Proxy.ThreadMap) -> (threadMap :Proxy.ThreadMap); makeEcho @1 (context :Proxy.Context) -> (result :Echo.Echo); makeMining @2 (context :Proxy.Context) -> (result :Mining.Mining); stop @3 (context :Proxy.Context) -> (); + makeNodeRpc @4 (context :Proxy.Context) -> (result :NodeRpc.NodeRpc); } diff --git a/src/ipc/capnp/noderpc-types.h b/src/ipc/capnp/noderpc-types.h new file mode 100644 index 000000000000..650bb7d41eb2 --- /dev/null +++ b/src/ipc/capnp/noderpc-types.h @@ -0,0 +1,22 @@ +// Copyright (c) 2025-present 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_NODERPC_TYPES_H +#define BITCOIN_IPC_CAPNP_NODERPC_TYPES_H + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#endif // BITCOIN_IPC_CAPNP_NODERPC_TYPES_H diff --git a/src/ipc/capnp/noderpc.capnp b/src/ipc/capnp/noderpc.capnp new file mode 100644 index 000000000000..60f9e163211d --- /dev/null +++ b/src/ipc/capnp/noderpc.capnp @@ -0,0 +1,228 @@ +# Copyright (c) 2025-present The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +@0xb1a2c3d4e5f60718; + +using Cxx = import "/capnp/c++.capnp"; +$Cxx.namespace("ipc::capnp::messages"); + +using Proxy = import "/mp/proxy.capnp"; +$Proxy.include("interfaces/noderpc.h"); +$Proxy.includeTypes("ipc/capnp/noderpc-types.h"); + +interface NodeRpc $Proxy.wrap("interfaces::NodeRpc") { + destroy @0 (context :Proxy.Context) -> (); + getNetworkInfo @1 (context :Proxy.Context) -> (result :NetworkInfo); + getDeploymentInfo @2 (context :Proxy.Context, blockHash :Data) -> (result :DeploymentsInfo); + getBlockchainInfo @3 (context :Proxy.Context) -> (result :BlockchainInfo); + estimateSmartFee @4 (context :Proxy.Context, confTarget :Int32, conservative :Bool) -> (result :SmartFeeEstimate); + getBestBlockHash @5 (context :Proxy.Context) -> (result :Text); + getBlockHash @6 (context :Proxy.Context, height :Int32) -> (result :Text); + getBlockHeader @7 (context :Proxy.Context, blockHash :Data) -> (result :BlockHeaderInfo); + getBlock @8 (context :Proxy.Context, blockHash :Data) -> (result :Data); + getTxOut @9 (context :Proxy.Context, txid :Data, n :UInt32, includeMempool :Bool) -> (result :TxOutInfo); + getRawTransaction @10 (context :Proxy.Context, txid :Data, verbose :Bool, blockHash :Data) -> (result :RawTransactionResult); + testMempoolAccept @11 (context :Proxy.Context, txns :List(Data), maxFeeRate :Int64) -> (result :List(TestMempoolAcceptResult)); + sendRawTransaction @12 (context :Proxy.Context, tx :Data, maxFeeRate :Int64, maxBurnAmount :Int64) -> (result :Text); +} + +struct TestMempoolAcceptFees $Proxy.wrap("interfaces::TestMempoolAcceptFees") { + base @0 :Int64; + effectiveFeerate @1 :Int64 $Proxy.name("effective_feerate"); + effectiveIncludes @2 :List(Text) $Proxy.name("effective_includes"); +} + +struct TestMempoolAcceptResult $Proxy.wrap("interfaces::TestMempoolAcceptResult") { + txid @0 :Text; + wtxid @1 :Text; + packageError @2 :Text $Proxy.name("package_error"); + validated @3 :Bool; + allowed @4 :Bool; + rejectReason @5 :Text $Proxy.name("reject_reason"); + rejectDetails @6 :Text $Proxy.name("reject_details"); + vsize @7 :Int64; + fees @8 :TestMempoolAcceptFees; +} + +struct TxScriptSig $Proxy.wrap("interfaces::TxScriptSig") { + scriptAsm @0 :Text $Proxy.name("script_asm"); + hex @1 :Text; +} + +struct TxInput $Proxy.wrap("interfaces::TxInput") { + coinbase @0 :Text; + txid @1 :Text; + vout @2 :UInt32; + scriptSig @3 :TxScriptSig $Proxy.name("script_sig"); + txinwitness @4 :List(Text); + sequence @5 :UInt32; +} + +struct TxOutput $Proxy.wrap("interfaces::TxOutput") { + value @0 :Int64; + n @1 :Int32; + scriptPubKey @2 :TxOutScriptPubKey $Proxy.name("script_pub_key"); +} + +struct TxBlockContext $Proxy.wrap("interfaces::TxBlockContext") { + blockhash @0 :Text; + confirmations @1 :Int32; + time @2 :Int64; + blocktime @3 :Int64; +} + +struct TransactionDetails $Proxy.wrap("interfaces::TransactionDetails") { + txid @0 :Text; + hash @1 :Text; + version @2 :Int64; + size @3 :Int32; + vsize @4 :Int32; + weight @5 :Int32; + locktime @6 :Int64; + vin @7 :List(TxInput); + vout @8 :List(TxOutput); + inActiveChain @9 :Bool $Proxy.name("in_active_chain"); + block @10 :TxBlockContext; +} + +struct RawTransactionResult $Proxy.wrap("interfaces::RawTransactionResult") { + tx @0 :Data; + details @1 :TransactionDetails; +} + +struct TxOutScriptPubKey $Proxy.wrap("interfaces::TxOutScriptPubKey") { + scriptAsm @0 :Text $Proxy.name("script_asm"); + desc @1 :Text; + hex @2 :Text; + type @3 :Text; + address @4 :Text; +} + +struct TxOutInfo $Proxy.wrap("interfaces::TxOutInfo") { + bestblock @0 :Text; + confirmations @1 :Int32; + value @2 :Int64; + scriptPubKey @3 :TxOutScriptPubKey $Proxy.name("script_pub_key"); + coinbase @4 :Bool; +} + +struct SmartFeeEstimate $Proxy.wrap("interfaces::SmartFeeEstimate") { + feerate @0 :Int64; + errors @1 :List(Text); + blocks @2 :Int32; +} + +struct BlockHeaderInfo $Proxy.wrap("interfaces::BlockHeaderInfo") { + hash @0 :Text; + confirmations @1 :Int32; + height @2 :Int32; + version @3 :Int32; + versionHex @4 :Text $Proxy.name("version_hex"); + merkleroot @5 :Text; + time @6 :Int64; + mediantime @7 :Int64; + nonce @8 :UInt32; + bits @9 :Text; + target @10 :Text; + difficulty @11 :Float64; + chainwork @12 :Text; + nTx @13 :Int32 $Proxy.name("n_tx"); + previousblockhash @14 :Text; + nextblockhash @15 :Text; +} + +struct BlockchainPruneInfo $Proxy.wrap("interfaces::BlockchainPruneInfo") { + height @0 :Int32; + automatic @1 :Bool; + targetSize @2 :UInt64 $Proxy.name("target_size"); +} + +struct BlockchainInfo $Proxy.wrap("interfaces::BlockchainInfo") { + chain @0 :Text; + blocks @1 :Int32; + headers @2 :Int32; + bestblockhash @3 :Text; + bits @4 :Text; + target @5 :Text; + difficulty @6 :Float64; + time @7 :Int64; + mediantime @8 :Int64; + verificationprogress @9 :Float64; + initialblockdownload @10 :Bool; + chainwork @11 :Text; + sizeOnDisk @12 :UInt64 $Proxy.name("size_on_disk"); + pruned @13 :Bool; + prune @14 :BlockchainPruneInfo; + signetChallenge @15 :Text $Proxy.name("signet_challenge"); + warnings @16 :List(Text); +} + +struct NetworkInfoNetwork $Proxy.wrap("interfaces::NetworkInfoNetwork") { + name @0 :Text; + limited @1 :Bool; + reachable @2 :Bool; + proxy @3 :Text; + proxyRandomizeCredentials @4 :Bool $Proxy.name("proxy_randomize_credentials"); +} + +struct NetworkInfoLocalAddress $Proxy.wrap("interfaces::NetworkInfoLocalAddress") { + address @0 :Text; + port @1 :UInt16; + score @2 :Int32; +} + +struct NetworkInfo $Proxy.wrap("interfaces::NetworkInfo") { + version @0 :Int32; + subversion @1 :Text; + protocolversion @2 :Int32; + localservices @3 :Text; + localservicesnames @4 :List(Text); + localrelay @5 :Bool; + timeoffset @6 :Int64; + connections @7 :UInt64; + connectionsIn @8 :UInt64 $Proxy.name("connections_in"); + connectionsOut @9 :UInt64 $Proxy.name("connections_out"); + networkactive @10 :Bool; + networks @11 :List(NetworkInfoNetwork); + relayfee @12 :Int64; + incrementalfee @13 :Int64; + localaddresses @14 :List(NetworkInfoLocalAddress); + warnings @15 :List(Text); +} + +struct BIP9Statistics $Proxy.wrap("interfaces::BIP9Statistics") { + period @0 :Int32; + threshold @1 :Int32; + elapsed @2 :Int32; + count @3 :Int32; + possible @4 :Bool; +} + +struct BIP9DeploymentInfo $Proxy.wrap("interfaces::BIP9DeploymentInfo") { + bit @0 :Int32; + startTime @1 :Int64 $Proxy.name("start_time"); + timeout @2 :Int64; + minActivationHeight @3 :Int32 $Proxy.name("min_activation_height"); + status @4 :Text; + since @5 :Int32; + statusNext @6 :Text $Proxy.name("status_next"); + statistics @7 :BIP9Statistics; + signalling @8 :Text; +} + +struct DeploymentInfo $Proxy.wrap("interfaces::DeploymentInfo") { + name @0 :Text; + type @1 :Text; + active @2 :Bool; + height @3 :Int32; + bip9 @4 :BIP9DeploymentInfo; +} + +struct DeploymentsInfo $Proxy.wrap("interfaces::DeploymentsInfo") { + hash @0 :Text; + height @1 :Int32; + scriptFlags @2 :List(Text) $Proxy.name("script_flags"); + deployments @3 :List(DeploymentInfo); +} + diff --git a/src/ipc/libmultiprocess/include/mp/type-number.h b/src/ipc/libmultiprocess/include/mp/type-number.h index 5c997f54bc8a..ddff5cdd467d 100644 --- a/src/ipc/libmultiprocess/include/mp/type-number.h +++ b/src/ipc/libmultiprocess/include/mp/type-number.h @@ -82,7 +82,7 @@ decltype(auto) CustomReadField(TypeList, InvokeContext& invoke_context, Input&& input, ReadDest&& read_dest, - typename std::enable_if::value>::type* enable = 0) + typename std::enable_if::value>::type* enable = nullptr) { auto value = input.get(); static_assert(std::is_same::value, "floating point type mismatch"); diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp index 50b70fafcb0f..07ae1d8ea5ae 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -2,28 +2,38 @@ // 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 #include #include +#include #include #include #include @@ -38,19 +48,27 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include +#include #include #include +#include +#include