Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ fn main() {
.file("capnp/echo.capnp")
.file("capnp/init.capnp")
.file("capnp/mining.capnp")
.file("capnp/rpc.capnp")
.file("capnp/proxy.capnp")
.run()
.unwrap();
Expand Down
2 changes: 2 additions & 0 deletions capnp/init.capnp
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ $Cxx.namespace("ipc::capnp::messages");
using Proxy = import "proxy.capnp";
using Echo = import "echo.capnp";
using Mining = import "mining.capnp";
using Rpc = import "rpc.capnp";

interface Init $Proxy.wrap("interfaces::Init") {
construct @0 (threadMap: Proxy.ThreadMap) -> (threadMap :Proxy.ThreadMap);
makeEcho @1 (context :Proxy.Context) -> (result :Echo.Echo);
makeMiningOld2 @2 () -> ();
makeMining @3 (context :Proxy.Context) -> (result :Mining.Mining);
makeRpc @4 (context :Proxy.Context) -> (result :Rpc.Rpc);
}
15 changes: 15 additions & 0 deletions capnp/rpc.capnp
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Copyright (c) 2025 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.

@0x9c3505dc45e146ac;

using Cxx = import "/capnp/c++.capnp";
$Cxx.namespace("ipc::capnp::messages");

using Common = import "common.capnp";
using Proxy = import "proxy.capnp";

interface Rpc $Proxy.wrap("interfaces::Rpc") {
executeRpc @0 (context :Proxy.Context, request :Text, uri :Text, user :Text) -> (result :Text);
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ capnp::generated_code!(pub mod common_capnp);
capnp::generated_code!(pub mod echo_capnp);
capnp::generated_code!(pub mod proxy_capnp);
capnp::generated_code!(pub mod mining_capnp);
capnp::generated_code!(pub mod rpc_capnp);

pub extern crate capnp;
pub extern crate capnp_rpc;
37 changes: 37 additions & 0 deletions tests/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ use bitcoin_capnp_types::{mining_capnp, proxy_capnp::thread};

mod util;

use serde_json::{Value, json};
use util::bitcoin_core::{
destroy_template, make_block_template, mempool_tx_count, with_init_client, with_mining_client,
with_rpc_client,
};
use util::bitcoin_core_wallet::{
bitcoin_test_wallet, create_mempool_self_transfer, ensure_wallet_loaded_and_funded,
Expand Down Expand Up @@ -71,6 +73,41 @@ async fn integration() {
.await;
}

/// Test the RPC interface by calling `uptime`
#[tokio::test]
#[serial_test::parallel]
async fn rpc_query_uptime() {
with_rpc_client(|_client, thread, rpc| async move {
let mut execute_rpc_request = rpc.execute_rpc_request();
execute_rpc_request
.get()
.get_context()
.unwrap()
.set_thread(thread.clone());
let j: Value = json!({
"jsonrpc": "2.0",
"id": "test",
"method": "uptime",
"params": [],
});
execute_rpc_request.get().set_request(j.to_string());
let exec_rpc_response = execute_rpc_request.send().promise.await.unwrap();
let result = exec_rpc_response
.get()
.unwrap()
.get_result()
.unwrap()
.to_string()
.unwrap();
let v: Value = serde_json::from_str(&result)
.map_err(|e| format!("failed to parse rpc response as JSON: {e}"))
.unwrap();
let uptime = v["result"].as_i64().unwrap();
assert!(uptime > 0, "Uptime must be greater than zero");
})
.await;
}

/// Calling the deprecated makeMiningOld2 (@2) should return an error from the
/// server. Cap'n Proto requires sequential ordinals so this placeholder cannot
/// be removed, but the server intentionally rejects it.
Expand Down
21 changes: 21 additions & 0 deletions tests/util/bitcoin_core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use bitcoin_capnp_types::{
init_capnp::init,
mining_capnp::{block_template, mining},
proxy_capnp::{thread, thread_map},
rpc_capnp::rpc,
};
use capnp_rpc::{RpcSystem, rpc_twoparty_capnp::Side, twoparty::VatNetwork};
use futures::io::BufReader;
Expand Down Expand Up @@ -88,6 +89,18 @@ where
.await;
}

pub async fn with_rpc_client<F, Fut>(f: F)
where
F: FnOnce(init::Client, thread::Client, rpc::Client) -> Fut,
Fut: Future<Output = ()>,
{
with_init_client(|client, thread| async move {
let rpc = make_rpc(&client, &thread).await;
f(client, thread, rpc).await;
})
.await;
}

pub async fn connect_unix_stream(
path: impl AsRef<Path>,
) -> VatNetwork<BufReader<Compat<OwnedReadHalf>>> {
Expand Down Expand Up @@ -151,6 +164,14 @@ pub async fn make_mining(init: &init::Client, thread: &thread::Client) -> mining
resp.get().unwrap().get_result().unwrap()
}

/// Obtain a Rpc client from an Init client.
pub async fn make_rpc(init: &init::Client, thread: &thread::Client) -> rpc::Client {
let mut req = init.make_rpc_request();
req.get().get_context().unwrap().set_thread(thread.clone());
let resp = req.send().promise.await.unwrap();
resp.get().unwrap().get_result().unwrap()
}

/// Create a new block template with default options and no cooldown.
///
/// The node must have height > 16. At height <= 16 the BIP34 height push
Expand Down
Loading