refactor(cli): Remove obsolete RPC server (#346)

This commit is contained in:
Mohan 2025-05-21 18:43:02 +02:00 committed by GitHub
parent 7ef8b1c643
commit 8d0af2f8b7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 19 additions and 924 deletions

View file

@ -262,29 +262,6 @@ jobs:
- name: Run test ${{ matrix.test_name }}
run: cargo test --package swap --test ${{ matrix.test_name }} -- --nocapture
rpc_tests:
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v4.1.7
- uses: Swatinem/rust-cache@v2.7.3
- name: Install dependencies required by Tauri v2 (ubuntu only)
run: |
sudo apt update
sudo apt install libwebkit2gtk-4.1-dev \
build-essential \
curl \
wget \
file \
libxdo-dev \
libssl-dev \
libayatana-appindicator3-dev \
librsvg2-dev
- name: Run RPC server tests
run: cargo test --package swap --test rpc -- --nocapture
check_stable:
runs-on: ubuntu-latest

View file

@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Docs: Document `external_bitcoin_address` option for using a specific
Bitcoin address when redeeming or punishing swaps.
- Removed the JSON-RPC daemon and the `start-daemon` CLI command.
## [1.1.1] - 2025-05-20

233
Cargo.lock generated
View file

@ -428,7 +428,7 @@ version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532"
dependencies = [
"event-listener 5.4.0",
"event-listener",
"event-listener-strategy",
"futures-core",
"pin-project-lite",
@ -494,7 +494,7 @@ version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059"
dependencies = [
"async-lock 3.4.0",
"async-lock",
"cfg-if",
"concurrent-queue",
"futures-io",
@ -507,22 +507,13 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "async-lock"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b"
dependencies = [
"event-listener 2.5.3",
]
[[package]]
name = "async-lock"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18"
dependencies = [
"event-listener 5.4.0",
"event-listener",
"event-listener-strategy",
"pin-project-lite",
]
@ -535,12 +526,12 @@ checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb"
dependencies = [
"async-channel",
"async-io",
"async-lock 3.4.0",
"async-lock",
"async-signal",
"async-task",
"blocking",
"cfg-if",
"event-listener 5.4.0",
"event-listener",
"futures-lite",
"rustix 0.38.44",
"tracing",
@ -564,7 +555,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3"
dependencies = [
"async-io",
"async-lock 3.4.0",
"async-lock",
"atomic-waker",
"cfg-if",
"futures-core",
@ -892,15 +883,6 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d"
[[package]]
name = "beef"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1"
dependencies = [
"serde",
]
[[package]]
name = "big-bytes"
version = "1.0.0"
@ -2844,12 +2826,6 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "event-listener"
version = "2.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
[[package]]
name = "event-listener"
version = "5.4.0"
@ -2867,7 +2843,7 @@ version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93"
dependencies = [
"event-listener 5.4.0",
"event-listener",
"pin-project-lite",
]
@ -3585,19 +3561,6 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9985c9503b412198aa4197559e9a318524ebc4519c229bfa05a535828c950b9d"
[[package]]
name = "globset"
version = "0.4.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5"
dependencies = [
"aho-corasick",
"bstr",
"log",
"regex-automata 0.4.9",
"regex-syntax 0.8.5",
]
[[package]]
name = "gobject-sys"
version = "0.18.0"
@ -4632,115 +4595,6 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "jsonrpsee"
version = "0.16.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "367a292944c07385839818bb71c8d76611138e2dedb0677d035b8da21d29c78b"
dependencies = [
"jsonrpsee-core",
"jsonrpsee-server",
"jsonrpsee-types",
"jsonrpsee-ws-client",
]
[[package]]
name = "jsonrpsee-client-transport"
version = "0.16.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8b3815d9f5d5de348e5f162b316dc9cdf4548305ebb15b4eb9328e66cf27d7a"
dependencies = [
"futures-util",
"http 0.2.12",
"jsonrpsee-core",
"jsonrpsee-types",
"pin-project",
"rustls-native-certs 0.6.3",
"soketto",
"thiserror 1.0.69",
"tokio",
"tokio-rustls 0.24.1",
"tokio-util",
"tracing",
"webpki-roots 0.25.4",
]
[[package]]
name = "jsonrpsee-core"
version = "0.16.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b5dde66c53d6dcdc8caea1874a45632ec0fcf5b437789f1e45766a1512ce803"
dependencies = [
"anyhow",
"arrayvec",
"async-lock 2.8.0",
"async-trait",
"beef",
"futures-channel",
"futures-timer",
"futures-util",
"globset",
"hyper 0.14.32",
"jsonrpsee-types",
"parking_lot 0.12.3",
"rand 0.8.5",
"rustc-hash 1.1.0",
"serde",
"serde_json",
"soketto",
"thiserror 1.0.69",
"tokio",
"tracing",
]
[[package]]
name = "jsonrpsee-server"
version = "0.16.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf4d945a6008c9b03db3354fb3c83ee02d2faa9f2e755ec1dfb69c3551b8f4ba"
dependencies = [
"futures-channel",
"futures-util",
"http 0.2.12",
"hyper 0.14.32",
"jsonrpsee-core",
"jsonrpsee-types",
"serde",
"serde_json",
"soketto",
"tokio",
"tokio-stream",
"tokio-util",
"tower 0.4.13",
"tracing",
]
[[package]]
name = "jsonrpsee-types"
version = "0.16.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "245ba8e5aa633dd1c1e4fae72bce06e71f42d34c14a2767c6b4d173b57bee5e5"
dependencies = [
"anyhow",
"beef",
"serde",
"serde_json",
"thiserror 1.0.69",
"tracing",
]
[[package]]
name = "jsonrpsee-ws-client"
version = "0.16.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e1b3975ed5d73f456478681a417128597acd6a2487855fdb7b4a3d4d195bf5e"
dependencies = [
"http 0.2.12",
"jsonrpsee-client-transport",
"jsonrpsee-core",
"jsonrpsee-types",
]
[[package]]
name = "k12"
version = "0.3.0"
@ -5681,11 +5535,11 @@ version = "0.12.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9321642ca94a4282428e6ea4af8cc2ca4eac48ac7a6a4ea8f33f76d0ce70926"
dependencies = [
"async-lock 3.4.0",
"async-lock",
"crossbeam-channel",
"crossbeam-epoch",
"crossbeam-utils",
"event-listener 5.4.0",
"event-listener",
"futures-util",
"loom",
"parking_lot 0.12.3",
@ -7279,7 +7133,7 @@ dependencies = [
"pin-project-lite",
"quinn-proto",
"quinn-udp",
"rustc-hash 2.1.1",
"rustc-hash",
"rustls 0.23.27",
"socket2",
"thiserror 2.0.12",
@ -7299,7 +7153,7 @@ dependencies = [
"lru-slab",
"rand 0.9.1",
"ring 0.17.14",
"rustc-hash 2.1.1",
"rustc-hash",
"rustls 0.23.27",
"rustls-pki-types",
"slab",
@ -7630,7 +7484,7 @@ dependencies = [
"quinn",
"rustls 0.23.27",
"rustls-native-certs 0.8.1",
"rustls-pemfile 2.2.0",
"rustls-pemfile",
"rustls-pki-types",
"serde",
"serde_json",
@ -7817,12 +7671,6 @@ version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustc-hash"
version = "2.1.1"
@ -7943,18 +7791,6 @@ dependencies = [
"security-framework 2.11.1",
]
[[package]]
name = "rustls-native-certs"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00"
dependencies = [
"openssl-probe",
"rustls-pemfile 1.0.4",
"schannel",
"security-framework 2.11.1",
]
[[package]]
name = "rustls-native-certs"
version = "0.8.1"
@ -7967,15 +7803,6 @@ dependencies = [
"security-framework 3.2.0",
]
[[package]]
name = "rustls-pemfile"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c"
dependencies = [
"base64 0.21.7",
]
[[package]]
name = "rustls-pemfile"
version = "2.2.0"
@ -8873,22 +8700,6 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "soketto"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d1c5305e39e09653383c2c7244f2f78b3bcae37cf50c64cb4789c9f5096ec2"
dependencies = [
"base64 0.13.1",
"bytes",
"futures",
"http 0.2.12",
"httparse",
"log",
"rand 0.8.5",
"sha-1",
]
[[package]]
name = "soup3"
version = "0.5.0"
@ -8964,7 +8775,7 @@ dependencies = [
"crc",
"crossbeam-queue",
"either",
"event-listener 5.4.0",
"event-listener",
"futures-core",
"futures-intrusive",
"futures-io",
@ -9351,8 +9162,6 @@ dependencies = [
"futures",
"get-port",
"hex",
"jsonrpsee",
"jsonrpsee-core",
"libp2p",
"libp2p-community-tor",
"mockito",
@ -10209,16 +10018,6 @@ dependencies = [
"webpki 0.21.4",
]
[[package]]
name = "tokio-rustls"
version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081"
dependencies = [
"rustls 0.21.12",
"tokio",
]
[[package]]
name = "tokio-rustls"
version = "0.26.2"
@ -10653,7 +10452,7 @@ dependencies = [
"derive_more 1.0.0",
"digest 0.10.7",
"educe",
"event-listener 5.4.0",
"event-listener",
"fs-mistrust",
"fslock",
"futures",
@ -13303,14 +13102,14 @@ dependencies = [
"async-broadcast",
"async-executor",
"async-io",
"async-lock 3.4.0",
"async-lock",
"async-process",
"async-recursion",
"async-task",
"async-trait",
"blocking",
"enumflags2",
"event-listener 5.4.0",
"event-listener",
"futures-core",
"futures-lite",
"hex",

View file

@ -43,8 +43,6 @@ ecdsa_fun = { version = "0.10", default-features = false, features = [
ed25519-dalek = "1"
futures = { version = "0.3", default-features = false }
hex = "0.4"
jsonrpsee = { version = "0.16.2", features = [ "server" ] }
jsonrpsee-core = "0.16.2"
libp2p = { version = "0.53.2", features = [ "tcp", "yamux", "dns", "noise", "request-response", "ping", "rendezvous", "identify", "macros", "cbor", "json", "tokio", "serde", "rsa" ] }
libp2p-community-tor = { git = "https://github.com/umgefahren/libp2p-tor", branch = "main", features = [ "listen-onion-service" ] }
moka = { version = "0.12", features = [ "sync", "future" ] }
@ -129,7 +127,6 @@ zip = "0.5"
[dev-dependencies]
bitcoin-harness = { git = "https://github.com/UnstoppableSwap/bitcoin-harness-rs", branch = "master" }
get-port = "3"
jsonrpsee = { version = "0.16.2", features = [ "ws-client" ] }
mockito = "1.4"
monero-harness = { path = "../monero-harness" }
proptest = "1"

View file

@ -10,7 +10,7 @@ use crate::network::quote::{BidQuote, ZeroQuoteReceived};
use crate::network::swarm;
use crate::protocol::bob::{BobState, Swap};
use crate::protocol::{bob, State};
use crate::{bitcoin, cli, monero, rpc};
use crate::{bitcoin, cli, monero};
use ::bitcoin::address::NetworkUnchecked;
use ::bitcoin::Txid;
use ::monero::Network;
@ -25,7 +25,6 @@ use serde_json::json;
use std::cmp::min;
use std::convert::TryInto;
use std::future::Future;
use std::net::SocketAddr;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;
@ -191,22 +190,6 @@ impl Request for ListSellersArgs {
}
}
// StartDaemon
#[typeshare]
#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct StartDaemonArgs {
#[typeshare(serialized_as = "string")]
pub server_address: Option<SocketAddr>,
}
impl Request for StartDaemonArgs {
type Response = serde_json::Value;
async fn request(self, ctx: Arc<Context>) -> Result<Self::Response> {
start_daemon(self, (*ctx).clone()).await
}
}
// GetSwapInfo
#[typeshare]
#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
@ -1044,26 +1027,6 @@ pub async fn withdraw_btc(
})
}
#[tracing::instrument(fields(method = "start_daemon"), skip(context))]
pub async fn start_daemon(
start_daemon: StartDaemonArgs,
context: Context,
) -> Result<serde_json::Value> {
let StartDaemonArgs { server_address } = start_daemon;
// Default to 127.0.0.1:1234
let server_address = server_address.unwrap_or("127.0.0.1:1234".parse()?);
let (addr, server_handle) = rpc::run_server(server_address, context).await?;
tracing::info!(%addr, "Started RPC server");
server_handle.stopped().await;
tracing::info!("Stopped RPC server");
Ok(json!({}))
}
#[tracing::instrument(fields(method = "get_balance"), skip(context))]
pub async fn get_balance(balance: BalanceArgs, context: Arc<Context>) -> Result<BalanceResponse> {
let BalanceArgs { force_refresh } = balance;

View file

@ -1,8 +1,7 @@
use crate::bitcoin::{bitcoin_address, Amount};
use crate::cli::api::request::{
BalanceArgs, BuyXmrArgs, CancelAndRefundArgs, ExportBitcoinWalletArgs, GetConfigArgs,
GetHistoryArgs, ListSellersArgs, MoneroRecoveryArgs, Request, ResumeSwapArgs, StartDaemonArgs,
WithdrawBtcArgs,
GetHistoryArgs, ListSellersArgs, MoneroRecoveryArgs, Request, ResumeSwapArgs, WithdrawBtcArgs,
};
use crate::cli::api::Context;
use crate::monero;
@ -11,7 +10,6 @@ use anyhow::Result;
use bitcoin::address::NetworkUnchecked;
use libp2p::core::Multiaddr;
use std::ffi::OsString;
use std::net::SocketAddr;
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::Arc;
@ -172,30 +170,6 @@ where
Ok(context)
}
CliCommand::StartDaemon {
server_address,
bitcoin,
monero,
tor,
} => {
let context = Arc::new(
ContextBuilder::new(is_testnet)
.with_tor(tor.enable_tor)
.with_bitcoin(bitcoin)
.with_monero(monero)
.with_data_dir(data)
.with_debug(debug)
.with_json(json)
.build()
.await?,
);
StartDaemonArgs { server_address }
.request(context.clone())
.await?;
Ok(context)
}
CliCommand::WithdrawBtc {
bitcoin,
amount,
@ -430,23 +404,6 @@ enum CliCommand {
#[structopt(flatten)]
bitcoin: Bitcoin,
},
#[structopt(about = "Starts a JSON-RPC server")]
StartDaemon {
#[structopt(flatten)]
bitcoin: Bitcoin,
#[structopt(flatten)]
monero: Monero,
#[structopt(
long = "server-address",
help = "The socket address the server should use"
)]
server_address: Option<SocketAddr>,
#[structopt(flatten)]
tor: Tor,
},
/// Resume a swap
Resume {
#[structopt(flatten)]

View file

@ -29,7 +29,6 @@ pub mod monero;
mod monero_ext;
pub mod network;
pub mod protocol;
pub mod rpc;
pub mod seed;
pub mod tracing_ext;

View file

@ -1,38 +0,0 @@
use crate::cli::api::Context;
use std::net::SocketAddr;
use thiserror::Error;
use tower_http::cors::CorsLayer;
use jsonrpsee::{
core::server::host_filtering::AllowHosts,
server::{ServerBuilder, ServerHandle},
};
pub mod methods;
#[derive(Debug, Error)]
pub enum Error {
#[error("Could not parse key value from params")]
ParseError,
}
pub async fn run_server(
server_address: SocketAddr,
context: Context,
) -> anyhow::Result<(SocketAddr, ServerHandle)> {
let cors = CorsLayer::permissive();
let middleware = tower::ServiceBuilder::new().layer(cors);
let server = ServerBuilder::default()
.set_host_filtering(AllowHosts::Any)
.set_middleware(middleware)
.build(server_address)
.await?;
let modules = methods::register_modules(context)?;
let addr = server.local_addr()?;
let server_handle = server.start(modules)?;
Ok((addr, server_handle))
}

View file

@ -1,130 +0,0 @@
use crate::bitcoin::bitcoin_address;
use crate::cli::api::request::{
BalanceArgs, BuyXmrArgs, CancelAndRefundArgs, GetCurrentSwapArgs, GetHistoryArgs, GetLogsArgs,
GetSwapInfoArgs, ListSellersArgs, MoneroRecoveryArgs, Request, ResumeSwapArgs,
SuspendCurrentSwapArgs, WithdrawBtcArgs,
};
use crate::cli::api::Context;
use crate::monero::monero_address;
use anyhow::Result;
use jsonrpsee::server::RpcModule;
trait ConvertToJsonRpseeError<T> {
fn to_jsonrpsee_result(self) -> Result<T, jsonrpsee_core::Error>;
}
impl<T> ConvertToJsonRpseeError<T> for Result<T, anyhow::Error> {
fn to_jsonrpsee_result(self) -> Result<T, jsonrpsee_core::Error> {
self.map_err(|e| jsonrpsee_core::Error::Custom(e.to_string()))
}
}
pub fn register_modules(outer_context: Context) -> Result<RpcModule<Context>> {
let mut module = RpcModule::new(outer_context);
module.register_async_method("suspend_current_swap", |_, context| async move {
SuspendCurrentSwapArgs {}
.request(context)
.await
.to_jsonrpsee_result()
})?;
module.register_async_method("get_swap_info", |params_raw, context| async move {
let params: GetSwapInfoArgs = params_raw.parse()?;
params.request(context).await.to_jsonrpsee_result()
})?;
module.register_async_method("get_bitcoin_balance", |params_raw, context| async move {
let params: BalanceArgs = params_raw.parse()?;
params.request(context).await.to_jsonrpsee_result()
})?;
module.register_async_method("get_history", |_, context| async move {
GetHistoryArgs {}
.request(context)
.await
.to_jsonrpsee_result()
})?;
module.register_async_method("get_logs", |params_raw, context| async move {
let params: GetLogsArgs = params_raw.parse()?;
let logs = params.request(context).await?;
Ok(logs)
})?;
module.register_async_method("resume_swap", |params_raw, context| async move {
let params: ResumeSwapArgs = params_raw.parse()?;
params.request(context).await.to_jsonrpsee_result()
})?;
module.register_async_method("cancel_refund_swap", |params_raw, context| async move {
let params: CancelAndRefundArgs = params_raw.parse()?;
params.request(context).await.to_jsonrpsee_result()
})?;
module.register_async_method(
"get_monero_recovery_info",
|params_raw, context| async move {
let params: MoneroRecoveryArgs = params_raw.parse()?;
params.request(context).await.to_jsonrpsee_result()
},
)?;
module.register_async_method("withdraw_btc", |params_raw, context| async move {
let mut params: WithdrawBtcArgs = params_raw.parse()?;
params.address = bitcoin_address::revalidate_network(
params.address,
context.config.env_config.bitcoin_network,
)
.to_jsonrpsee_result()?;
params.request(context).await.to_jsonrpsee_result()
})?;
module.register_async_method("buy_xmr", |params_raw, context| async move {
let mut params: BuyXmrArgs = params_raw.parse()?;
params.bitcoin_change_address = params
.bitcoin_change_address
.map(|address| {
bitcoin_address::validate_network(
address,
context.config.env_config.bitcoin_network,
)
.map(|a| a.into_unchecked())
})
.transpose()
.to_jsonrpsee_result()?;
params.monero_receive_address = monero_address::validate(
params.monero_receive_address,
context.config.env_config.monero_network,
)
.to_jsonrpsee_result()?;
params.request(context).await.to_jsonrpsee_result()
})?;
module.register_async_method("list_sellers", |params_raw, context| async move {
let params: ListSellersArgs = params_raw.parse()?;
params.request(context).await.to_jsonrpsee_result()
})?;
module.register_async_method("get_current_swap", |_, context| async move {
GetCurrentSwapArgs {}
.request(context)
.await
.to_jsonrpsee_result()
})?;
Ok(module)
}

View file

@ -1,430 +0,0 @@
pub mod harness;
#[cfg(test)]
mod test {
use anyhow::Result;
use jsonrpsee::ws_client::WsClientBuilder;
use jsonrpsee_core::client::{Client, ClientT};
use jsonrpsee_core::params::ObjectParams;
use serial_test::serial;
use serde_json::Value;
use std::collections::HashMap;
use std::net::SocketAddr;
use std::sync::Arc;
use std::time::Duration;
use swap::cli::api::request::{Request, StartDaemonArgs};
use swap::cli::api::Context;
use crate::harness::alice_run_until::is_xmr_lock_transaction_sent;
use crate::harness::bob_run_until::is_btc_locked;
use crate::harness::{setup_test, SlowCancelConfig, TestContext};
use swap::asb::FixedRate;
use swap::protocol::{alice, bob};
use swap::tracing_ext::{capture_logs, MakeCapturingWriter};
use tracing_subscriber::filter::LevelFilter;
use uuid::Uuid;
const SERVER_ADDRESS: &str = "127.0.0.1:1234";
const SERVER_START_TIMEOUT_SECS: u64 = 50;
const BITCOIN_ADDR: &str = "bcrt1qahvhjfc7vx5857zf8knxs8yp5lkm26jgyt0k76";
const MONERO_ADDR: &str = "53gEuGZUhP9JMEBZoGaFNzhwEgiG7hwQdMCqFxiyiTeFPmkbt1mAoNybEUvYBKHcnrSgxnVWgZsTvRBaHBNXPa8tHiCU51a";
const SELLER: &str =
"/ip4/127.0.0.1/tcp/9939/p2p/12D3KooWCdMKjesXMJz1SiZ7HgotrxuqhQJbP5sgBm2BwP1cqThi";
const SWAP_ID: &str = "ea030832-3be9-454f-bb98-5ea9a788406b";
pub async fn setup_daemon(
harness_ctx: TestContext,
) -> (Client, MakeCapturingWriter, Arc<Context>) {
let writer = capture_logs(LevelFilter::DEBUG);
let server_address: Option<SocketAddr> =
SERVER_ADDRESS.parse::<SocketAddr>().unwrap().into();
let context = Arc::new(harness_ctx.get_bob_context().await);
let context_clone = context.clone();
tokio::spawn(async move {
let args = StartDaemonArgs { server_address };
if let Err(err) = args.request(context_clone).await {
println!("Failed to initialize daemon for testing: {}", err);
}
});
for _ in 0..SERVER_START_TIMEOUT_SECS {
if writer.captured().contains("Started RPC server") {
let url = format!("ws://{}", SERVER_ADDRESS);
let client = WsClientBuilder::default().build(&url).await.unwrap();
return (client, writer, context);
}
tokio::time::sleep(Duration::from_secs(1)).await;
}
panic!(
"Failed to start RPC server after {} seconds",
SERVER_START_TIMEOUT_SECS
);
}
fn assert_has_keys_serde(map: &serde_json::Map<String, Value>, keys: &[&str]) {
for &key in keys {
assert!(map.contains_key(key), "Key {} is missing", key);
}
}
// Helper function for HashMap
fn assert_has_keys_hashmap<T>(map: &HashMap<String, T>, keys: &[&str]) {
for &key in keys {
assert!(map.contains_key(key), "Key {} is missing", key);
}
}
#[tokio::test]
#[serial]
pub async fn get_swap_info() {
setup_test(SlowCancelConfig, |mut harness_ctx| async move {
// Start a swap and wait for xmr lock transaction to be published (XmrLockTransactionSent)
let (bob_swap, _) = harness_ctx.bob_swap().await;
let bob_swap_id = bob_swap.id;
tokio::spawn(bob::run_until(bob_swap, is_btc_locked));
let alice_swap = harness_ctx.alice_next_swap().await;
alice::run_until(
alice_swap,
is_xmr_lock_transaction_sent,
FixedRate::default(),
)
.await?;
let (client, _, _) = setup_daemon(harness_ctx).await;
let response: HashMap<String, Vec<HashMap<String, String>>> = client
.request("get_history", ObjectParams::new())
.await
.unwrap();
let swaps: Vec<HashMap<String, String>> = vec![HashMap::from([
("swap_id".to_string(), bob_swap_id.to_string()),
("state".to_string(), "btc is locked".to_string()),
])];
assert_eq!(response.get("swaps").unwrap(), &swaps);
let mut params = ObjectParams::new();
params.insert("swap_id", bob_swap_id).unwrap();
let response: HashMap<String, Value> =
client.request("get_swap_info", params).await.unwrap();
// Check primary keys in response
assert_has_keys_hashmap(
&response,
&[
"tx_refund_fee",
"swap_id",
"cancel_timelock",
"timelock",
"punish_timelock",
"state_name",
"btc_amount",
"start_date",
"btc_refund_address",
"tx_cancel_fee",
"xmr_amount",
"completed",
"tx_lock_id",
"seller",
],
);
// Assert specific fields
assert_eq!(response.get("swap_id").unwrap(), &bob_swap_id.to_string());
assert_eq!(
response.get("state_name").unwrap(),
&"btc is locked".to_string()
);
assert_eq!(response.get("completed").unwrap(), &Value::Bool(false));
// Check seller object and its keys
let seller = response
.get("seller")
.expect("Field 'seller' is missing from response")
.as_object()
.expect("'seller' is not an object");
assert_has_keys_serde(seller, &["peer_id"]);
// Check timelock object, nested 'None' object, and blocks_left
let timelock = response
.get("timelock")
.expect("Field 'timelock' is missing from response")
.as_object()
.expect("'timelock' is not an object");
let timelock_type = timelock
.get("type")
.expect("Field 'type' is missing from 'timelock'")
.as_str()
.expect("'type' is not a string");
assert_eq!(timelock_type, "None");
let timelock_content = timelock
.get("content")
.expect("Field 'content' is missing from 'None'")
.as_object()
.expect("'content' is not an object");
let blocks_left = timelock_content
.get("blocks_left")
.expect("Field 'blocks_left' is missing from 'None'")
.as_i64()
.expect("'blocks_left' is not an integer");
// Validate blocks_left
assert!(
blocks_left > 0 && blocks_left <= 180,
"Field 'blocks_left' should be > 0 and <= 180 but got {}",
blocks_left
);
Ok(())
})
.await;
}
#[tokio::test]
#[serial]
pub async fn test_rpc_calls() {
setup_test(SlowCancelConfig, |harness_ctx| async move {
let alice_addr = harness_ctx.bob_params.get_concentenated_alice_address();
let (change_address, receive_address) =
harness_ctx.bob_params.get_change_receive_addresses().await;
let (client, _, _) = setup_daemon(harness_ctx).await;
assert!(client.is_connected());
let mut params = ObjectParams::new();
params.insert("force_refresh", false).unwrap();
let response: HashMap<String, i32> =
client.request("get_bitcoin_balance", params).await.unwrap();
assert_eq!(response, HashMap::from([("balance".to_string(), 10000000)]));
// TODO: Renable this test once the "log reference id" feature has been added again. The feature was removed as part of this PR:
// https://github.com/UnstoppableSwap/xmr-btc-swap/pull/10
//
// let mut params = ObjectParams::new();
// params.insert("log_reference_id", "test_ref_id").unwrap();
// params.insert("force_refresh", false).unwrap();
// let _: HashMap<String, i32> = client.request("get_bitcoin_balance", params).await.unwrap();
// assert!(writer.captured().contains(
// r#"method{method_name="Balance" log_reference_id="\"test_ref_id\""}: swap::api::request: Current Bitcoin balance as of last sync balance=0.1 BTC"#
// ));
for method in ["get_swap_info", "resume_swap", "cancel_refund_swap"].iter() {
let mut params = ObjectParams::new();
params.insert("swap_id", "invalid_swap").unwrap();
let response: Result<HashMap<String, String>, _> =
client.request(method, params).await;
response.expect_err(&format!(
"Expected an error when swap_id is invalid for method {}",
method
));
let params = ObjectParams::new();
let response: Result<HashMap<String, String>, _> =
client.request(method, params).await;
response.expect_err(&format!(
"Expected an error when swap_id is missing for method {}",
method
));
}
let params = ObjectParams::new();
let result: Result<HashMap<String, String>, _> =
client.request("list_sellers", params).await;
result.expect_err("Expected an error when rendezvous_point is missing");
let params = ObjectParams::new();
let result: Result<HashMap<String, String>, _> =
client.request("list_sellers", params).await;
result.expect_err("Expected an error when rendezvous_point is missing");
let params = ObjectParams::new();
let response: Result<HashMap<String, String>, _> =
client.request("withdraw_btc", params).await;
response.expect_err("Expected an error when withdraw_address is missing");
let mut params = ObjectParams::new();
params.insert("address", "invalid_address").unwrap();
let response: Result<HashMap<String, String>, _> =
client.request("withdraw_btc", params).await;
response.expect_err("Expected an error when withdraw_address is malformed");
let mut params = ObjectParams::new();
params.insert("address", BITCOIN_ADDR).unwrap();
params.insert("amount", "0").unwrap();
let response: Result<HashMap<String, String>, _> =
client.request("withdraw_btc", params).await;
response.expect_err("Expected an error when amount is 0");
let mut params = ObjectParams::new();
params.insert("address", BITCOIN_ADDR).unwrap();
params.insert("amount", 1000000).unwrap();
let response: HashMap<String, Value> = client
.request("withdraw_btc", params)
.await
.expect("Expected a valid response");
assert_has_keys_hashmap(&response, &["amount", "txid"]);
assert_eq!(response.get("amount").unwrap().as_u64().unwrap(), 1_000_000);
let params = ObjectParams::new();
let response: Result<HashMap<String, String>, _> =
client.request("buy_xmr", params).await;
response.expect_err("Expected an error when no params are given");
let mut params = ObjectParams::new();
params
.insert("bitcoin_change_address", BITCOIN_ADDR)
.unwrap();
params
.insert("monero_receive_address", MONERO_ADDR)
.unwrap();
let response: Result<HashMap<String, String>, _> =
client.request("buy_xmr", params).await;
response.expect_err("Expected an error when seller is missing");
let mut params = ObjectParams::new();
params
.insert("bitcoin_change_address", BITCOIN_ADDR)
.unwrap();
params.insert("seller", SELLER).unwrap();
let response: Result<HashMap<String, String>, _> =
client.request("buy_xmr", params).await;
response.expect_err("Expected an error when monero_receive_address is missing");
let mut params = ObjectParams::new();
params
.insert("monero_receive_address", MONERO_ADDR)
.unwrap();
params.insert("seller", SELLER).unwrap();
let response: Result<HashMap<String, String>, _> =
client.request("buy_xmr", params).await;
response.expect_err("Expected an error when bitcoin_change_address is missing");
let mut params = ObjectParams::new();
params
.insert("bitcoin_change_address", "invalid_address")
.unwrap();
params
.insert("monero_receive_address", MONERO_ADDR)
.unwrap();
params.insert("seller", SELLER).unwrap();
let response: Result<HashMap<String, String>, _> =
client.request("buy_xmr", params).await;
response.expect_err("Expected an error when bitcoin_change_address is malformed");
let mut params = ObjectParams::new();
params
.insert("bitcoin_change_address", BITCOIN_ADDR)
.unwrap();
params
.insert("monero_receive_address", "invalid_address")
.unwrap();
params.insert("seller", SELLER).unwrap();
let response: Result<HashMap<String, String>, _> =
client.request("buy_xmr", params).await;
response.expect_err("Expected an error when monero_receive_address is malformed");
let mut params = ObjectParams::new();
params
.insert("bitcoin_change_address", BITCOIN_ADDR)
.unwrap();
params
.insert("monero_receive_address", MONERO_ADDR)
.unwrap();
params.insert("seller", "invalid_seller").unwrap();
let response: Result<HashMap<String, String>, _> =
client.request("buy_xmr", params).await;
response.expect_err("Expected an error when seller is malformed");
let response: Result<HashMap<String, String>, _> = client
.request("suspend_current_swap", ObjectParams::new())
.await;
response.expect_err("Expected an error when no swap is running");
let mut params = ObjectParams::new();
params
.insert("bitcoin_change_address", change_address)
.unwrap();
params
.insert("monero_receive_address", receive_address)
.unwrap();
params.insert("seller", alice_addr).unwrap();
let response: HashMap<String, Value> = client
.request("buy_xmr", params)
.await
.expect("Expected a HashMap, got an error");
assert_has_keys_hashmap(&response, &["swap_id"]);
Ok(())
})
.await;
}
#[tokio::test]
#[serial]
pub async fn suspend_current_swap_swap_running() {
setup_test(SlowCancelConfig, |harness_ctx| async move {
let (client, _, ctx) = setup_daemon(harness_ctx).await;
ctx.swap_lock
.acquire_swap_lock(Uuid::parse_str(SWAP_ID).unwrap())
.await
.unwrap();
let cloned_ctx = ctx.clone();
tokio::spawn(async move {
// Immediately release lock when suspend signal is received. Mocks a running swap that is then cancelled.
ctx.swap_lock
.listen_for_swap_force_suspension()
.await
.unwrap();
ctx.swap_lock.release_swap_lock().await.unwrap();
});
let response: HashMap<String, String> = client
.request("suspend_current_swap", ObjectParams::new())
.await
.unwrap();
assert_eq!(
response,
HashMap::from([("swap_id".to_string(), SWAP_ID.to_string())])
);
cloned_ctx
.swap_lock
.acquire_swap_lock(Uuid::parse_str(SWAP_ID).unwrap())
.await
.unwrap();
let response: Result<HashMap<String, String>, _> = client
.request("suspend_current_swap", ObjectParams::new())
.await;
response.expect_err("Expected an error when suspend signal times out");
Ok(())
})
.await;
}
}