Skip to content

Commit 6839ca4

Browse files
committed
Add Rpc interface
bitcoin/bitcoin#32297 introduced the RPC interface for `bitcoin-cli`, allowing it to execute RPC commands over IPC. This commit adds the corresponding Cap'n Proto schema and includes a test that queries `uptime`.
1 parent 6f811c8 commit 6839ca4

6 files changed

Lines changed: 77 additions & 0 deletions

File tree

build.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ fn main() {
66
.file("capnp/echo.capnp")
77
.file("capnp/init.capnp")
88
.file("capnp/mining.capnp")
9+
.file("capnp/rpc.capnp")
910
.file("capnp/proxy.capnp")
1011
.run()
1112
.unwrap();

capnp/init.capnp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@ $Cxx.namespace("ipc::capnp::messages");
1010
using Proxy = import "proxy.capnp";
1111
using Echo = import "echo.capnp";
1212
using Mining = import "mining.capnp";
13+
using Rpc = import "rpc.capnp";
1314

1415
interface Init $Proxy.wrap("interfaces::Init") {
1516
construct @0 (threadMap: Proxy.ThreadMap) -> (threadMap :Proxy.ThreadMap);
1617
makeEcho @1 (context :Proxy.Context) -> (result :Echo.Echo);
1718
makeMiningOld2 @2 () -> ();
1819
makeMining @3 (context :Proxy.Context) -> (result :Mining.Mining);
20+
makeRpc @4 (context :Proxy.Context) -> (result :Rpc.Rpc);
1921
}

capnp/rpc.capnp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Copyright (c) 2025 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+
@0x9c3505dc45e146ac;
6+
7+
using Cxx = import "/capnp/c++.capnp";
8+
$Cxx.namespace("ipc::capnp::messages");
9+
10+
using Common = import "common.capnp";
11+
using Proxy = import "proxy.capnp";
12+
13+
interface Rpc $Proxy.wrap("interfaces::Rpc") {
14+
executeRpc @0 (context :Proxy.Context, request :Text, uri :Text, user :Text) -> (result :Text);
15+
}

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ capnp::generated_code!(pub mod common_capnp);
33
capnp::generated_code!(pub mod echo_capnp);
44
capnp::generated_code!(pub mod proxy_capnp);
55
capnp::generated_code!(pub mod mining_capnp);
6+
capnp::generated_code!(pub mod rpc_capnp);
67

78
pub extern crate capnp;
89
pub extern crate capnp_rpc;

tests/test.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ use bitcoin_capnp_types::{mining_capnp, proxy_capnp::thread};
22

33
mod util;
44

5+
use serde_json::{Value, json};
56
use util::bitcoin_core::{
67
destroy_template, make_block_template, mempool_tx_count, with_init_client, with_mining_client,
8+
with_rpc_client,
79
};
810
use util::bitcoin_core_wallet::{
911
bitcoin_test_wallet, create_mempool_self_transfer, ensure_wallet_loaded_and_funded,
@@ -71,6 +73,41 @@ async fn integration() {
7173
.await;
7274
}
7375

76+
/// Test the RPC interface by calling `uptime`
77+
#[tokio::test]
78+
#[serial_test::parallel]
79+
async fn rpc_query_uptime() {
80+
with_rpc_client(|_client, thread, rpc| async move {
81+
let mut execute_rpc_request = rpc.execute_rpc_request();
82+
execute_rpc_request
83+
.get()
84+
.get_context()
85+
.unwrap()
86+
.set_thread(thread.clone());
87+
let j: Value = json!({
88+
"jsonrpc": "2.0",
89+
"id": "test",
90+
"method": "uptime",
91+
"params": [],
92+
});
93+
execute_rpc_request.get().set_request(j.to_string());
94+
let exec_rpc_response = execute_rpc_request.send().promise.await.unwrap();
95+
let result = exec_rpc_response
96+
.get()
97+
.unwrap()
98+
.get_result()
99+
.unwrap()
100+
.to_string()
101+
.unwrap();
102+
let v: Value = serde_json::from_str(&result)
103+
.map_err(|e| format!("failed to parse rpc response as JSON: {e}"))
104+
.unwrap();
105+
let uptime = v["result"].as_i64().unwrap();
106+
assert!(uptime > 0);
107+
})
108+
.await;
109+
}
110+
74111
/// Calling the deprecated makeMiningOld2 (@2) should return an error from the
75112
/// server. Cap'n Proto requires sequential ordinals so this placeholder cannot
76113
/// be removed, but the server intentionally rejects it.

tests/util/bitcoin_core.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use bitcoin_capnp_types::{
1111
init_capnp::init,
1212
mining_capnp::{block_template, mining},
1313
proxy_capnp::{thread, thread_map},
14+
rpc_capnp::rpc,
1415
};
1516
use capnp_rpc::{RpcSystem, rpc_twoparty_capnp::Side, twoparty::VatNetwork};
1617
use futures::io::BufReader;
@@ -88,6 +89,18 @@ where
8889
.await;
8990
}
9091

92+
pub async fn with_rpc_client<F, Fut>(f: F)
93+
where
94+
F: FnOnce(init::Client, thread::Client, rpc::Client) -> Fut,
95+
Fut: Future<Output = ()>,
96+
{
97+
with_init_client(|client, thread| async move {
98+
let rpc = make_rpc(&client, &thread).await;
99+
f(client, thread, rpc).await;
100+
})
101+
.await;
102+
}
103+
91104
pub async fn connect_unix_stream(
92105
path: impl AsRef<Path>,
93106
) -> VatNetwork<BufReader<Compat<OwnedReadHalf>>> {
@@ -151,6 +164,14 @@ pub async fn make_mining(init: &init::Client, thread: &thread::Client) -> mining
151164
resp.get().unwrap().get_result().unwrap()
152165
}
153166

167+
/// Obtain a Rpc client from an Init client.
168+
pub async fn make_rpc(init: &init::Client, thread: &thread::Client) -> rpc::Client {
169+
let mut req = init.make_rpc_request();
170+
req.get().get_context().unwrap().set_thread(thread.clone());
171+
let resp = req.send().promise.await.unwrap();
172+
resp.get().unwrap().get_result().unwrap()
173+
}
174+
154175
/// Create a new block template with default options and no cooldown.
155176
///
156177
/// The node must have height > 16. At height <= 16 the BIP34 height push

0 commit comments

Comments
 (0)