wip: refactor request.rs to allow type safety

This commit is contained in:
binarybaron 2024-08-07 22:51:18 +02:00
parent 757183e857
commit 472e3a57b3
No known key found for this signature in database
GPG key ID: 99B75D3E1476A26E
16 changed files with 955 additions and 743 deletions

15
.zed/settings.json Normal file
View file

@ -0,0 +1,15 @@
// Folder-specific settings
//
// For a full list of overridable settings, and general information on folder-specific settings,
// see the documentation: https://zed.dev/docs/configuring-zed#folder-specific-settings
{
"lsp": {
"rust-analyzer": {
"initialization_options": {
"rust": {
"analyzerTargetDir": "./src-xmr-btc-swap"
}
}
}
}
}

View file

@ -6376,6 +6376,7 @@ dependencies = [
"directories-next", "directories-next",
"ecdsa_fun", "ecdsa_fun",
"ed25519-dalek", "ed25519-dalek",
"erased-serde",
"futures", "futures",
"get-port", "get-port",
"hex", "hex",
@ -7629,6 +7630,7 @@ dependencies = [
name = "unstoppableswap-gui-rs" name = "unstoppableswap-gui-rs"
version = "0.0.0" version = "0.0.0"
dependencies = [ dependencies = [
"anyhow",
"once_cell", "once_cell",
"serde", "serde",
"serde_json", "serde_json",

View file

@ -1,24 +1,24 @@
[package] [package]
name = "unstoppableswap-gui-rs" name = "unstoppableswap-gui-rs"
version = "0.0.0" version = "0.0.0"
description = "A Tauri App" authors = [ "you" ]
authors = ["you"]
edition = "2021" edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
description = "A Tauri App"
[lib] [lib]
name = "unstoppableswap_gui_rs_lib" name = "unstoppableswap_gui_rs_lib"
crate-type = ["lib", "cdylib", "staticlib"] crate-type = [ "lib", "cdylib", "staticlib" ]
[build-dependencies] [build-dependencies]
tauri-build = { version = "2.0.0-beta", features = [] } tauri-build = { version = "2.0.0-beta", features = [ ] }
[dependencies] [dependencies]
tauri = { version = "2.0.0-beta", features = [] } anyhow = "1"
tauri-plugin-shell = "2.0.0-beta"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
once_cell = "1" once_cell = "1"
swap = { path= "../swap" } serde = { version = "1", features = [ "derive" ] }
serde_json = "1"
swap = { path = "../swap" }
tauri = { version = "2.0.0-beta", features = [ ] }
tauri-plugin-shell = "2.0.0-beta"

View file

@ -1,17 +1,29 @@
use std::sync::Arc; use std::sync::Arc;
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use swap::{api::{request::{Method, Request}, Context}, cli::command::{Bitcoin, Monero}}; use swap::{
api::{
request::{get_balance, BalanceArgs, BalanceResponse},
Context,
},
cli::command::{Bitcoin, Monero},
};
// Lazy load the Context // Lazy load the Context
static CONTEXT: OnceCell<Arc<Context>> = OnceCell::new(); static CONTEXT: OnceCell<Arc<Context>> = OnceCell::new();
#[tauri::command] #[tauri::command]
async fn balance() -> String { async fn balance() -> Result<BalanceResponse, String> {
let context = CONTEXT.get().unwrap(); let context = CONTEXT.get().unwrap();
let request = Request::new(Method::Balance { force_refresh: true });
let response = request.call(context.clone()).await.unwrap(); get_balance(
response.to_string() BalanceArgs {
force_refresh: true,
},
context.clone(),
)
.await
.map_err(|e| e.to_string())
} }
fn setup<'a>(app: &'a mut tauri::App) -> Result<(), Box<dyn std::error::Error>> { fn setup<'a>(app: &'a mut tauri::App) -> Result<(), Box<dyn std::error::Error>> {
@ -34,7 +46,9 @@ fn setup<'a>(app: &'a mut tauri::App) -> Result<(), Box<dyn std::error::Error>>
.await .await
.unwrap(); .unwrap();
CONTEXT.set(Arc::new(context)).expect("Failed to initialize cli context"); CONTEXT
.set(Arc::new(context))
.expect("Failed to initialize cli context");
}); });
Ok(()) Ok(())
} }

View file

@ -27,17 +27,31 @@ data-encoding = "2.6"
dialoguer = "0.11" dialoguer = "0.11"
digest = "0.10.7" digest = "0.10.7"
directories-next = "2" directories-next = "2"
ecdsa_fun = { version = "0.10", default-features = false, features = [ "libsecp_compat", "serde", "adaptor" ] } ecdsa_fun = { version = "0.10", default-features = false, features = [
"libsecp_compat",
"serde",
"adaptor",
] }
ed25519-dalek = "1" ed25519-dalek = "1"
erased-serde = "0.4.5"
futures = { version = "0.3", default-features = false } futures = { version = "0.3", default-features = false }
hex = "0.4" hex = "0.4"
hyper = "0.14.20"
itertools = "0.13" itertools = "0.13"
jsonrpsee = { version = "0.16.2", features = [ "server" ] } jsonrpsee = { version = "0.16.2", features = [ "server" ] }
tower-http = { version = "0.3.4", features = ["full"] }
tower = { version = "0.4.13", features = ["full"] }
hyper = "0.14.20"
jsonrpsee-core = "0.16.2" jsonrpsee-core = "0.16.2"
libp2p = { version = "0.42.2", default-features = false, features = [ "tcp-tokio", "yamux", "mplex", "dns-tokio", "noise", "request-response", "websocket", "ping", "rendezvous", "identify" ] } libp2p = { version = "0.42.2", default-features = false, features = [
"tcp-tokio",
"yamux",
"mplex",
"dns-tokio",
"noise",
"request-response",
"websocket",
"ping",
"rendezvous",
"identify",
] }
monero = { version = "0.12", features = [ "serde_support" ] } monero = { version = "0.12", features = [ "serde_support" ] }
monero-rpc = { path = "../monero-rpc" } monero-rpc = { path = "../monero-rpc" }
pem = "3.0" pem = "3.0"
@ -45,7 +59,12 @@ proptest = "1"
qrcode = "0.14" qrcode = "0.14"
rand = "0.8" rand = "0.8"
rand_chacha = "0.3" rand_chacha = "0.3"
reqwest = { version = "0.12", features = [ "http2", "rustls-tls", "stream", "socks" ], default-features = false } reqwest = { version = "0.12", features = [
"http2",
"rustls-tls",
"stream",
"socks",
], default-features = false }
rust_decimal = { version = "1", features = [ "serde-float" ] } rust_decimal = { version = "1", features = [ "serde-float" ] }
rust_decimal_macros = "1" rust_decimal_macros = "1"
serde = { version = "1", features = [ "derive" ] } serde = { version = "1", features = [ "derive" ] }
@ -53,22 +72,52 @@ serde_cbor = "0.11"
serde_json = "1" serde_json = "1"
serde_with = { version = "1", features = [ "macros" ] } serde_with = { version = "1", features = [ "macros" ] }
sha2 = "0.10" sha2 = "0.10"
sigma_fun = { version = "0.7", default-features = false, features = [ "ed25519", "serde", "secp256k1", "alloc" ] } sigma_fun = { version = "0.7", default-features = false, features = [
sqlx = { version = "0.6.3", features = [ "sqlite", "runtime-tokio-rustls", "offline" ] } "ed25519",
"serde",
"secp256k1",
"alloc",
] }
sqlx = { version = "0.6.3", features = [
"sqlite",
"runtime-tokio-rustls",
"offline",
] }
structopt = "0.3" structopt = "0.3"
strum = { version = "0.26", features = [ "derive" ] } strum = { version = "0.26", features = [ "derive" ] }
thiserror = "1" thiserror = "1"
time = "0.3" time = "0.3"
tokio = { version = "1", features = [ "rt-multi-thread", "time", "macros", "sync", "process", "fs", "net", "parking_lot" ] } tokio = { version = "1", features = [
"rt-multi-thread",
"time",
"macros",
"sync",
"process",
"fs",
"net",
"parking_lot",
] }
tokio-socks = "0.5" tokio-socks = "0.5"
tokio-tungstenite = { version = "0.15", features = [ "rustls-tls" ] } tokio-tungstenite = { version = "0.15", features = [ "rustls-tls" ] }
tokio-util = { version = "0.7", features = [ "io", "codec" ] } tokio-util = { version = "0.7", features = [ "io", "codec" ] }
toml = "0.8" toml = "0.8"
torut = { version = "0.2", default-features = false, features = [ "v3", "control" ] } torut = { version = "0.2", default-features = false, features = [
"v3",
"control",
] }
tower = { version = "0.4.13", features = [ "full" ] }
tower-http = { version = "0.3.4", features = [ "full" ] }
tracing = { version = "0.1", features = [ "attributes" ] } tracing = { version = "0.1", features = [ "attributes" ] }
tracing-appender = "0.2" tracing-appender = "0.2"
tracing-futures = { version = "0.2", features = [ "std-future", "futures-03" ] } tracing-futures = { version = "0.2", features = [ "std-future", "futures-03" ] }
tracing-subscriber = { version = "0.3", default-features = false, features = [ "fmt", "ansi", "env-filter", "time", "tracing-log", "json" ] } tracing-subscriber = { version = "0.3", default-features = false, features = [
"fmt",
"ansi",
"env-filter",
"time",
"tracing-log",
"json",
] }
url = { version = "2", features = [ "serde" ] } url = { version = "2", features = [ "serde" ] }
uuid = { version = "1.9", features = [ "serde", "v4" ] } uuid = { version = "1.9", features = [ "serde", "v4" ] }
void = "1" void = "1"
@ -96,4 +145,8 @@ testcontainers = "0.15"
[build-dependencies] [build-dependencies]
anyhow = "1" anyhow = "1"
vergen = { version = "8.3", default-features = false, features = [ "build", "git", "git2" ] } vergen = { version = "8.3", default-features = false, features = [
"build",
"git",
"git2",
] }

View file

@ -375,6 +375,7 @@ pub mod api_test {
use crate::api::request::{Method, Request}; use crate::api::request::{Method, Request};
use libp2p::Multiaddr; use libp2p::Multiaddr;
use request::BuyXmrArgs;
use std::str::FromStr; use std::str::FromStr;
use uuid::Uuid; use uuid::Uuid;
@ -431,12 +432,12 @@ pub mod api_test {
} }
}; };
Request::new(Method::BuyXmr { Request::new(Method::BuyXmr(BuyXmrArgs {
seller, seller,
bitcoin_change_address, bitcoin_change_address,
monero_receive_address, monero_receive_address,
swap_id: Uuid::new_v4(), swap_id: Uuid::new_v4(),
}) }))
} }
pub fn resume() -> Request { pub fn resume() -> Request {

View file

@ -1,6 +1,6 @@
use crate::api::Context; use crate::api::Context;
use crate::bitcoin::{Amount, ExpiredTimelocks, TxLock}; use crate::bitcoin::{Amount, ExpiredTimelocks, TxLock};
use crate::cli::{list_sellers, EventLoop, SellerStatus}; use crate::cli::{list_sellers as list_sellers_impl, EventLoop, SellerStatus};
use crate::libp2p_ext::MultiAddrExt; use crate::libp2p_ext::MultiAddrExt;
use crate::network::quote::{BidQuote, ZeroQuoteReceived}; use crate::network::quote::{BidQuote, ZeroQuoteReceived};
use crate::network::swarm; use crate::network::swarm;
@ -11,7 +11,9 @@ use anyhow::{bail, Context as AnyContext, Result};
use libp2p::core::Multiaddr; use libp2p::core::Multiaddr;
use qrcode::render::unicode; use qrcode::render::unicode;
use qrcode::QrCode; use qrcode::QrCode;
use serde::{Deserialize, Serialize};
use serde_json::json; use serde_json::json;
use serde_json::Value as JsonValue;
use std::cmp::min; use std::cmp::min;
use std::convert::TryInto; use std::convert::TryInto;
use std::future::Future; use std::future::Future;
@ -27,44 +29,87 @@ pub struct Request {
pub log_reference: Option<String>, pub log_reference: Option<String>,
} }
#[derive(Debug, Eq, PartialEq)]
pub struct BuyXmrArgs {
pub seller: Multiaddr,
pub bitcoin_change_address: bitcoin::Address,
pub monero_receive_address: monero::Address,
pub swap_id: Uuid,
}
#[derive(Debug, Eq, PartialEq)]
pub struct ResumeArgs {
pub swap_id: Uuid,
}
#[derive(Debug, Eq, PartialEq)]
pub struct CancelAndRefundArgs {
pub swap_id: Uuid,
}
#[derive(Debug, Eq, PartialEq)]
pub struct MoneroRecoveryArgs {
pub swap_id: Uuid,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct ResumeSwapResponse {
pub result: String,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct BalanceResponse {
pub balance: u64, // in satoshis
}
#[derive(Serialize, Deserialize, Debug)]
pub struct BuyXmrResponse {
pub swap_id: String,
pub quote: BidQuote, // You'll need to import or define BidQuote
}
#[derive(Debug, Eq, PartialEq)]
pub struct WithdrawBtcArgs {
pub amount: Option<Amount>,
pub address: bitcoin::Address,
}
#[derive(Debug, Eq, PartialEq)]
pub struct BalanceArgs {
pub force_refresh: bool,
}
#[derive(Debug, Eq, PartialEq)]
pub struct ListSellersArgs {
pub rendezvous_point: Multiaddr,
}
#[derive(Debug, Eq, PartialEq)]
pub struct StartDaemonArgs {
pub server_address: Option<SocketAddr>,
}
#[derive(Debug, Eq, PartialEq)]
pub struct GetSwapInfoArgs {
pub swap_id: Uuid,
}
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum Method { pub enum Method {
BuyXmr { BuyXmr(BuyXmrArgs),
seller: Multiaddr, Resume(ResumeArgs),
bitcoin_change_address: bitcoin::Address, CancelAndRefund(CancelAndRefundArgs),
monero_receive_address: monero::Address, MoneroRecovery(MoneroRecoveryArgs),
swap_id: Uuid,
},
Resume {
swap_id: Uuid,
},
CancelAndRefund {
swap_id: Uuid,
},
MoneroRecovery {
swap_id: Uuid,
},
History, History,
Config, Config,
WithdrawBtc { WithdrawBtc(WithdrawBtcArgs),
amount: Option<Amount>, Balance(BalanceArgs),
address: bitcoin::Address, ListSellers(ListSellersArgs),
},
Balance {
force_refresh: bool,
},
ListSellers {
rendezvous_point: Multiaddr,
},
ExportBitcoinWallet, ExportBitcoinWallet,
SuspendCurrentSwap, SuspendCurrentSwap,
StartDaemon { StartDaemon(StartDaemonArgs),
server_address: Option<SocketAddr>,
},
GetCurrentSwap, GetCurrentSwap,
GetSwapInfo { GetSwapInfo(GetSwapInfoArgs),
swap_id: Uuid,
},
GetRawStates, GetRawStates,
} }
@ -78,13 +123,13 @@ impl Method {
log_reference_id = field::Empty log_reference_id = field::Empty
) )
} }
Method::BuyXmr { swap_id, .. } => { Method::BuyXmr(BuyXmrArgs { swap_id, .. }) => {
debug_span!("method", method_name="BuyXmr", swap_id=%swap_id, log_reference_id=field::Empty) debug_span!("method", method_name="BuyXmr", swap_id=%swap_id, log_reference_id=field::Empty)
} }
Method::CancelAndRefund { swap_id } => { Method::CancelAndRefund(CancelAndRefundArgs { swap_id }) => {
debug_span!("method", method_name="CancelAndRefund", swap_id=%swap_id, log_reference_id=field::Empty) debug_span!("method", method_name="CancelAndRefund", swap_id=%swap_id, log_reference_id=field::Empty)
} }
Method::Resume { swap_id } => { Method::Resume(ResumeArgs { swap_id }) => {
debug_span!("method", method_name="Resume", swap_id=%swap_id, log_reference_id=field::Empty) debug_span!("method", method_name="Resume", swap_id=%swap_id, log_reference_id=field::Empty)
} }
Method::Config => { Method::Config => {
@ -170,24 +215,7 @@ impl Method {
} }
} }
impl Request { async fn suspend_current_swap(context: Arc<Context>) -> Result<serde_json::Value> {
pub fn new(cmd: Method) -> Request {
Request {
cmd,
log_reference: None,
}
}
pub fn with_id(cmd: Method, id: Option<String>) -> Request {
Request {
cmd,
log_reference: id,
}
}
async fn handle_cmd(self, context: Arc<Context>) -> Result<serde_json::Value> {
match self.cmd {
Method::SuspendCurrentSwap => {
let swap_id = context.swap_lock.get_current_swap_id().await; let swap_id = context.swap_lock.get_current_swap_id().await;
if let Some(id_value) = swap_id { if let Some(id_value) = swap_id {
@ -197,8 +225,13 @@ impl Request {
} else { } else {
bail!("No swap is currently running") bail!("No swap is currently running")
} }
} }
Method::GetSwapInfo { swap_id } => {
async fn get_swap_info(
get_swap_info: GetSwapInfoArgs,
context: Arc<Context>,
) -> Result<serde_json::Value> {
let GetSwapInfoArgs { swap_id } = get_swap_info;
let bitcoin_wallet = context let bitcoin_wallet = context
.bitcoin_wallet .bitcoin_wallet
.as_ref() .as_ref()
@ -272,9 +305,9 @@ impl Request {
.with_context(|| "Did not find SwapSetupCompleted state for swap")?; .with_context(|| "Did not find SwapSetupCompleted state for swap")?;
let timelock = match swap_state { let timelock = match swap_state {
BobState::Started { .. } BobState::Started { .. } | BobState::SafelyAborted | BobState::SwapSetupCompleted(_) => {
| BobState::SafelyAborted None
| BobState::SwapSetupCompleted(_) => None, }
BobState::BtcLocked { state3: state, .. } BobState::BtcLocked { state3: state, .. }
| BobState::XmrLockProofReceived { state, .. } => { | BobState::XmrLockProofReceived { state, .. } => {
Some(state.expired_timelock(bitcoin_wallet).await) Some(state.expired_timelock(bitcoin_wallet).await)
@ -286,9 +319,7 @@ impl Request {
Some(state.expired_timelock(bitcoin_wallet).await) Some(state.expired_timelock(bitcoin_wallet).await)
} }
BobState::BtcPunished { .. } => Some(Ok(ExpiredTimelocks::Punish)), BobState::BtcPunished { .. } => Some(Ok(ExpiredTimelocks::Punish)),
BobState::BtcRefunded(_) BobState::BtcRefunded(_) | BobState::BtcRedeemed(_) | BobState::XmrRedeemed { .. } => None,
| BobState::BtcRedeemed(_)
| BobState::XmrRedeemed { .. } => None,
}; };
Ok(json!({ Ok(json!({
@ -309,17 +340,17 @@ impl Request {
"btcRefundAddress": btc_refund_address.to_string(), "btcRefundAddress": btc_refund_address.to_string(),
"cancelTimelock": cancel_timelock, "cancelTimelock": cancel_timelock,
"punishTimelock": punish_timelock, "punishTimelock": punish_timelock,
// If the timelock is None, it means that the swap is in a state where the timelock is not accessible to us.
// If that is the case, we return null. Otherwise, we return the timelock.
"timelock": timelock.map(|tl| tl.map(|tl| json!(tl)).unwrap_or(json!(null))).unwrap_or(json!(null)), "timelock": timelock.map(|tl| tl.map(|tl| json!(tl)).unwrap_or(json!(null))).unwrap_or(json!(null)),
})) }))
} }
Method::BuyXmr {
async fn buy_xmr(buy_xmr: BuyXmrArgs, context: Arc<Context>) -> Result<serde_json::Value> {
let BuyXmrArgs {
seller, seller,
bitcoin_change_address, bitcoin_change_address,
monero_receive_address, monero_receive_address,
swap_id, swap_id,
} => { } = buy_xmr;
let bitcoin_wallet = Arc::clone( let bitcoin_wallet = Arc::clone(
context context
.bitcoin_wallet .bitcoin_wallet
@ -484,8 +515,10 @@ impl Request {
"swapId": swap_id.to_string(), "swapId": swap_id.to_string(),
"quote": bid_quote, "quote": bid_quote,
})) }))
} }
Method::Resume { swap_id } => {
async fn resume_swap(resume: ResumeArgs, context: Arc<Context>) -> Result<serde_json::Value> {
let ResumeArgs { swap_id } = resume;
context.swap_lock.acquire_swap_lock(swap_id).await?; context.swap_lock.acquire_swap_lock(swap_id).await?;
let seller_peer_id = context.db.get_peer_id(swap_id).await?; let seller_peer_id = context.db.get_peer_id(swap_id).await?;
@ -509,8 +542,7 @@ impl Request {
), ),
(seed.clone(), context.config.namespace), (seed.clone(), context.config.namespace),
); );
let mut swarm = let mut swarm = swarm::cli(seed.clone(), context.config.tor_socks5_port, behaviour).await?;
swarm::cli(seed.clone(), context.config.tor_socks5_port, behaviour).await?;
let our_peer_id = swarm.local_peer_id(); let our_peer_id = swarm.local_peer_id();
tracing::debug!(peer_id = %our_peer_id, "Network layer initialized"); tracing::debug!(peer_id = %our_peer_id, "Network layer initialized");
@ -590,8 +622,13 @@ impl Request {
Ok(json!({ Ok(json!({
"result": "ok", "result": "ok",
})) }))
} }
Method::CancelAndRefund { swap_id } => {
async fn cancel_and_refund(
cancel_and_refund: CancelAndRefundArgs,
context: Arc<Context>,
) -> Result<serde_json::Value> {
let CancelAndRefundArgs { swap_id } = cancel_and_refund;
let bitcoin_wallet = context let bitcoin_wallet = context
.bitcoin_wallet .bitcoin_wallet
.as_ref() .as_ref()
@ -599,12 +636,8 @@ impl Request {
context.swap_lock.acquire_swap_lock(swap_id).await?; context.swap_lock.acquire_swap_lock(swap_id).await?;
let state = cli::cancel_and_refund( let state =
swap_id, cli::cancel_and_refund(swap_id, Arc::clone(bitcoin_wallet), Arc::clone(&context.db)).await;
Arc::clone(bitcoin_wallet),
Arc::clone(&context.db),
)
.await;
context context
.swap_lock .swap_lock
@ -617,8 +650,9 @@ impl Request {
"result": state, "result": state,
}) })
}) })
} }
Method::History => {
async fn get_history(context: Arc<Context>) -> Result<serde_json::Value> {
let swaps = context.db.all().await?; let swaps = context.db.all().await?;
let mut vec: Vec<(Uuid, String)> = Vec::new(); let mut vec: Vec<(Uuid, String)> = Vec::new();
for (swap_id, state) in swaps { for (swap_id, state) in swaps {
@ -627,13 +661,15 @@ impl Request {
} }
Ok(json!({ "swaps": vec })) Ok(json!({ "swaps": vec }))
} }
Method::GetRawStates => {
async fn get_raw_states(context: Arc<Context>) -> Result<serde_json::Value> {
let raw_history = context.db.raw_all().await?; let raw_history = context.db.raw_all().await?;
Ok(json!({ "raw_states": raw_history })) Ok(json!({ "raw_states": raw_history }))
} }
Method::Config => {
async fn get_config(context: Arc<Context>) -> Result<serde_json::Value> {
let data_dir_display = context.config.data_dir.display(); let data_dir_display = context.config.data_dir.display();
tracing::info!(path=%data_dir_display, "Data directory"); tracing::info!(path=%data_dir_display, "Data directory");
tracing::info!(path=%format!("{}/logs", data_dir_display), "Log files directory"); tracing::info!(path=%format!("{}/logs", data_dir_display), "Log files directory");
@ -649,8 +685,13 @@ impl Request {
"monero-wallet-rpc": format!("{}/monero", data_dir_display), "monero-wallet-rpc": format!("{}/monero", data_dir_display),
"bitcoin_wallet": format!("{}/wallet", data_dir_display), "bitcoin_wallet": format!("{}/wallet", data_dir_display),
})) }))
} }
Method::WithdrawBtc { address, amount } => {
async fn withdraw_btc(
withdraw_btc: WithdrawBtcArgs,
context: Arc<Context>,
) -> Result<serde_json::Value> {
let WithdrawBtcArgs { address, amount } = withdraw_btc;
let bitcoin_wallet = context let bitcoin_wallet = context
.bitcoin_wallet .bitcoin_wallet
.as_ref() .as_ref()
@ -678,13 +719,17 @@ impl Request {
"amount": amount.to_sat(), "amount": amount.to_sat(),
"txid": signed_tx.txid(), "txid": signed_tx.txid(),
})) }))
} }
Method::StartDaemon { server_address } => {
async fn start_daemon(
start_daemon: StartDaemonArgs,
context: Arc<Context>,
) -> Result<serde_json::Value> {
let StartDaemonArgs { server_address } = start_daemon;
// Default to 127.0.0.1:1234 // Default to 127.0.0.1:1234
let server_address = server_address.unwrap_or("127.0.0.1:1234".parse()?); let server_address = server_address.unwrap_or("127.0.0.1:1234".parse()?);
let (addr, server_handle) = let (addr, server_handle) = rpc::run_server(server_address, Arc::clone(&context)).await?;
rpc::run_server(server_address, Arc::clone(&context)).await?;
tracing::info!(%addr, "Started RPC server"); tracing::info!(%addr, "Started RPC server");
@ -693,8 +738,10 @@ impl Request {
tracing::info!("Stopped RPC server"); tracing::info!("Stopped RPC server");
Ok(json!({})) Ok(json!({}))
} }
Method::Balance { force_refresh } => {
pub async fn get_balance(balance: BalanceArgs, context: Arc<Context>) -> Result<BalanceResponse> {
let BalanceArgs { force_refresh } = balance;
let bitcoin_wallet = context let bitcoin_wallet = context
.bitcoin_wallet .bitcoin_wallet
.as_ref() .as_ref()
@ -718,11 +765,16 @@ impl Request {
); );
} }
Ok(json!({ Ok(BalanceResponse {
"balance": bitcoin_balance.to_sat() balance: bitcoin_balance.to_sat(),
})) })
} }
Method::ListSellers { rendezvous_point } => {
async fn list_sellers(
list_sellers: ListSellersArgs,
context: Arc<Context>,
) -> Result<serde_json::Value> {
let ListSellersArgs { rendezvous_point } = list_sellers;
let rendezvous_node_peer_id = rendezvous_point let rendezvous_node_peer_id = rendezvous_point
.extract_peer_id() .extract_peer_id()
.context("Rendezvous node address must contain peer ID")?; .context("Rendezvous node address must contain peer ID")?;
@ -734,7 +786,7 @@ impl Request {
.context("Cannot extract seed")? .context("Cannot extract seed")?
.derive_libp2p_identity(); .derive_libp2p_identity();
let sellers = list_sellers( let sellers = list_sellers_impl(
rendezvous_node_peer_id, rendezvous_node_peer_id,
rendezvous_point, rendezvous_point,
context.config.namespace, context.config.namespace,
@ -766,8 +818,9 @@ impl Request {
} }
Ok(json!({ "sellers": sellers })) Ok(json!({ "sellers": sellers }))
} }
Method::ExportBitcoinWallet => {
async fn export_bitcoin_wallet(context: Arc<Context>) -> Result<serde_json::Value> {
let bitcoin_wallet = context let bitcoin_wallet = context
.bitcoin_wallet .bitcoin_wallet
.as_ref() .as_ref()
@ -778,8 +831,13 @@ impl Request {
Ok(json!({ Ok(json!({
"descriptor": wallet_export.to_string(), "descriptor": wallet_export.to_string(),
})) }))
} }
Method::MoneroRecovery { swap_id } => {
async fn monero_recovery(
monero_recovery: MoneroRecoveryArgs,
context: Arc<Context>,
) -> Result<serde_json::Value> {
let MoneroRecoveryArgs { swap_id } = monero_recovery;
let swap_state: BobState = context.db.get_state(swap_id).await?.try_into()?; let swap_state: BobState = context.db.get_state(swap_id).await?.try_into()?;
if let BobState::BtcRedeemed(state5) = swap_state { if let BobState::BtcRedeemed(state5) = swap_state {
@ -806,14 +864,40 @@ impl Request {
swap_state swap_state
) )
} }
} }
Method::GetCurrentSwap => Ok(json!({
async fn get_current_swap(context: Arc<Context>) -> Result<serde_json::Value> {
Ok(json!({
"swap_id": context.swap_lock.get_current_swap_id().await "swap_id": context.swap_lock.get_current_swap_id().await
})), }))
}
impl Request {
pub fn new(cmd: Method) -> Request {
Request {
cmd,
log_reference: None,
} }
} }
pub async fn call(self, context: Arc<Context>) -> Result<serde_json::Value> { pub fn with_id(cmd: Method, id: Option<String>) -> Request {
Request {
cmd,
log_reference: id,
}
}
async fn handle_cmd(self, context: Arc<Context>) -> Result<Box<dyn erased_serde::Serialize>> {
match self.cmd {
Method::Balance(args) => {
let response = get_balance(args, context).await?;
Ok(Box::new(response) as Box<dyn erased_serde::Serialize>)
}
_ => todo!(),
}
}
pub async fn call(self, context: Arc<Context>) -> Result<JsonValue> {
let method_span = self.cmd.get_tracing_span(self.log_reference.clone()); let method_span = self.cmd.get_tracing_span(self.log_reference.clone());
self.handle_cmd(context) self.handle_cmd(context)
@ -826,6 +910,7 @@ impl Request {
}); });
err err
}) })
.map(|result| json!(result))
} }
} }

View file

@ -12,9 +12,12 @@
#![forbid(unsafe_code)] #![forbid(unsafe_code)]
#![allow(non_snake_case)] #![allow(non_snake_case)]
use crate::{
cli::command::{parse_args_and_apply_defaults, ParseResult},
common::check_latest_version,
};
use anyhow::Result; use anyhow::Result;
use std::env; use std::env;
use crate::{cli::command::{parse_args_and_apply_defaults, ParseResult}, common::check_latest_version};
#[tokio::main] #[tokio::main]
pub async fn main() -> Result<()> { pub async fn main() -> Result<()> {

View file

@ -1,4 +1,7 @@
use crate::api::request::{Method, Request}; use crate::api::request::{
BalanceArgs, BuyXmrArgs, CancelAndRefundArgs, ListSellersArgs, Method, MoneroRecoveryArgs,
Request, ResumeArgs, StartDaemonArgs, WithdrawBtcArgs,
};
use crate::api::Context; use crate::api::Context;
use crate::bitcoin::{bitcoin_address, Amount}; use crate::bitcoin::{bitcoin_address, Amount};
use crate::monero; use crate::monero;
@ -74,12 +77,12 @@ where
let bitcoin_change_address = let bitcoin_change_address =
bitcoin_address::validate_is_testnet(bitcoin_change_address, is_testnet)?; bitcoin_address::validate_is_testnet(bitcoin_change_address, is_testnet)?;
let request = Request::new(Method::BuyXmr { let request = Request::new(Method::BuyXmr(BuyXmrArgs {
seller, seller,
bitcoin_change_address, bitcoin_change_address,
monero_receive_address, monero_receive_address,
swap_id: Uuid::new_v4(), swap_id: Uuid::new_v4(),
}); }));
let context = Context::build( let context = Context::build(
Some(bitcoin), Some(bitcoin),
@ -109,9 +112,9 @@ where
(context, request) (context, request)
} }
CliCommand::Balance { bitcoin } => { CliCommand::Balance { bitcoin } => {
let request = Request::new(Method::Balance { let request = Request::new(Method::Balance(BalanceArgs {
force_refresh: true, force_refresh: true,
}); }));
let context = Context::build( let context = Context::build(
Some(bitcoin), Some(bitcoin),
@ -132,7 +135,7 @@ where
monero, monero,
tor, tor,
} => { } => {
let request = Request::new(Method::StartDaemon { server_address }); let request = Request::new(Method::StartDaemon(StartDaemonArgs { server_address }));
let context = Context::build( let context = Context::build(
Some(bitcoin), Some(bitcoin),
@ -153,7 +156,7 @@ where
address, address,
} => { } => {
let address = bitcoin_address::validate_is_testnet(address, is_testnet)?; let address = bitcoin_address::validate_is_testnet(address, is_testnet)?;
let request = Request::new(Method::WithdrawBtc { amount, address }); let request = Request::new(Method::WithdrawBtc(WithdrawBtcArgs { amount, address }));
let context = Context::build( let context = Context::build(
Some(bitcoin), Some(bitcoin),
@ -174,7 +177,7 @@ where
monero, monero,
tor, tor,
} => { } => {
let request = Request::new(Method::Resume { swap_id }); let request = Request::new(Method::Resume(ResumeArgs { swap_id }));
let context = Context::build( let context = Context::build(
Some(bitcoin), Some(bitcoin),
@ -194,7 +197,7 @@ where
bitcoin, bitcoin,
tor, tor,
} => { } => {
let request = Request::new(Method::CancelAndRefund { swap_id }); let request = Request::new(Method::CancelAndRefund(CancelAndRefundArgs { swap_id }));
let context = Context::build( let context = Context::build(
Some(bitcoin), Some(bitcoin),
@ -213,7 +216,7 @@ where
rendezvous_point, rendezvous_point,
tor, tor,
} => { } => {
let request = Request::new(Method::ListSellers { rendezvous_point }); let request = Request::new(Method::ListSellers(ListSellersArgs { rendezvous_point }));
let context = let context =
Context::build(None, None, Some(tor), data, is_testnet, debug, json, None).await?; Context::build(None, None, Some(tor), data, is_testnet, debug, json, None).await?;
@ -239,7 +242,7 @@ where
CliCommand::MoneroRecovery { CliCommand::MoneroRecovery {
swap_id: SwapId { swap_id }, swap_id: SwapId { swap_id },
} => { } => {
let request = Request::new(Method::MoneroRecovery { swap_id }); let request = Request::new(Method::MoneroRecovery(MoneroRecoveryArgs { swap_id }));
let context = let context =
Context::build(None, None, None, data, is_testnet, debug, json, None).await?; Context::build(None, None, None, data, is_testnet, debug, json, None).await?;

View file

@ -18,6 +18,7 @@
pub mod api; pub mod api;
pub mod asb; pub mod asb;
pub mod bin;
pub mod bitcoin; pub mod bitcoin;
pub mod cli; pub mod cli;
pub mod common; pub mod common;
@ -27,14 +28,13 @@ pub mod fs;
pub mod kraken; pub mod kraken;
pub mod libp2p_ext; pub mod libp2p_ext;
pub mod monero; pub mod monero;
mod monero_ext;
pub mod network; pub mod network;
pub mod protocol; pub mod protocol;
pub mod rpc; pub mod rpc;
pub mod seed; pub mod seed;
pub mod tor; pub mod tor;
pub mod tracing_ext; pub mod tracing_ext;
pub mod bin;
mod monero_ext;
#[cfg(test)] #[cfg(test)]
mod proptest; mod proptest;

View file

@ -338,12 +338,18 @@ async fn next_state(
} }
} }
Ok(Rejected { reason, .. }) => { Ok(Rejected { reason, .. }) => {
tracing::error!(?reason, "Alice rejected our request for cooperative XMR redeem"); tracing::error!(
?reason,
"Alice rejected our request for cooperative XMR redeem"
);
return Err(reason) return Err(reason)
.context("Alice rejected our request for cooperative XMR redeem"); .context("Alice rejected our request for cooperative XMR redeem");
} }
Err(error) => { Err(error) => {
tracing::error!(?error, "Failed to request cooperative XMR redeem from Alice"); tracing::error!(
?error,
"Failed to request cooperative XMR redeem from Alice"
);
return Err(error) return Err(error)
.context("Failed to request cooperative XMR redeem from Alice"); .context("Failed to request cooperative XMR redeem from Alice");
} }

View file

@ -1,7 +1,7 @@
use crate::api::Context; use crate::api::Context;
use std::{net::SocketAddr, sync::Arc}; use std::{net::SocketAddr, sync::Arc};
use thiserror::Error; use thiserror::Error;
use tower_http::cors::{Any, CorsLayer}; use tower_http::cors::CorsLayer;
use jsonrpsee::{ use jsonrpsee::{
core::server::host_filtering::AllowHosts, core::server::host_filtering::AllowHosts,
@ -23,8 +23,11 @@ pub async fn run_server(
let cors = CorsLayer::permissive(); let cors = CorsLayer::permissive();
let middleware = tower::ServiceBuilder::new().layer(cors); let middleware = tower::ServiceBuilder::new().layer(cors);
let server = ServerBuilder::default().set_host_filtering(AllowHosts::Any) let server = ServerBuilder::default()
.set_middleware(middleware).build(server_address).await?; .set_host_filtering(AllowHosts::Any)
.set_middleware(middleware)
.build(server_address)
.await?;
let mut modules = RpcModule::new(()); let mut modules = RpcModule::new(());
{ {
modules modules

View file

@ -1,4 +1,7 @@
use crate::api::request::{Method, Request}; use crate::api::request::{
BalanceArgs, BuyXmrArgs, CancelAndRefundArgs, GetSwapInfoArgs, ListSellersArgs, Method,
MoneroRecoveryArgs, Request, ResumeArgs, WithdrawBtcArgs,
};
use crate::api::Context; use crate::api::Context;
use crate::bitcoin::bitcoin_address; use crate::bitcoin::bitcoin_address;
use crate::monero::monero_address; use crate::monero::monero_address;
@ -29,7 +32,12 @@ pub fn register_modules(context: Arc<Context>) -> Result<RpcModule<Arc<Context>>
let swap_id = as_uuid(swap_id) let swap_id = as_uuid(swap_id)
.ok_or_else(|| jsonrpsee_core::Error::Custom("Could not parse swap_id".to_string()))?; .ok_or_else(|| jsonrpsee_core::Error::Custom("Could not parse swap_id".to_string()))?;
execute_request(params_raw, Method::GetSwapInfo { swap_id }, &context).await execute_request(
params_raw,
Method::GetSwapInfo(GetSwapInfoArgs { swap_id }),
&context,
)
.await
})?; })?;
module.register_async_method("get_bitcoin_balance", |params_raw, context| async move { module.register_async_method("get_bitcoin_balance", |params_raw, context| async move {
@ -45,7 +53,12 @@ pub fn register_modules(context: Arc<Context>) -> Result<RpcModule<Arc<Context>>
jsonrpsee_core::Error::Custom("force_refesh is not a boolean".to_string()) jsonrpsee_core::Error::Custom("force_refesh is not a boolean".to_string())
})?; })?;
execute_request(params_raw, Method::Balance { force_refresh }, &context).await execute_request(
params_raw,
Method::Balance(BalanceArgs { force_refresh }),
&context,
)
.await
})?; })?;
module.register_async_method("get_history", |params, context| async move { module.register_async_method("get_history", |params, context| async move {
@ -66,7 +79,7 @@ pub fn register_modules(context: Arc<Context>) -> Result<RpcModule<Arc<Context>>
let swap_id = as_uuid(swap_id) let swap_id = as_uuid(swap_id)
.ok_or_else(|| jsonrpsee_core::Error::Custom("Could not parse swap_id".to_string()))?; .ok_or_else(|| jsonrpsee_core::Error::Custom("Could not parse swap_id".to_string()))?;
execute_request(params_raw, Method::Resume { swap_id }, &context).await execute_request(params_raw, Method::Resume(ResumeArgs { swap_id }), &context).await
})?; })?;
module.register_async_method("cancel_refund_swap", |params_raw, context| async move { module.register_async_method("cancel_refund_swap", |params_raw, context| async move {
@ -79,7 +92,12 @@ pub fn register_modules(context: Arc<Context>) -> Result<RpcModule<Arc<Context>>
let swap_id = as_uuid(swap_id) let swap_id = as_uuid(swap_id)
.ok_or_else(|| jsonrpsee_core::Error::Custom("Could not parse swap_id".to_string()))?; .ok_or_else(|| jsonrpsee_core::Error::Custom("Could not parse swap_id".to_string()))?;
execute_request(params_raw, Method::CancelAndRefund { swap_id }, &context).await execute_request(
params_raw,
Method::CancelAndRefund(CancelAndRefundArgs { swap_id }),
&context,
)
.await
})?; })?;
module.register_async_method( module.register_async_method(
@ -95,7 +113,12 @@ pub fn register_modules(context: Arc<Context>) -> Result<RpcModule<Arc<Context>>
jsonrpsee_core::Error::Custom("Could not parse swap_id".to_string()) jsonrpsee_core::Error::Custom("Could not parse swap_id".to_string())
})?; })?;
execute_request(params_raw, Method::MoneroRecovery { swap_id }, &context).await execute_request(
params_raw,
Method::MoneroRecovery(MoneroRecoveryArgs { swap_id }),
&context,
)
.await
}, },
)?; )?;
@ -123,10 +146,10 @@ pub fn register_modules(context: Arc<Context>) -> Result<RpcModule<Arc<Context>>
execute_request( execute_request(
params_raw, params_raw,
Method::WithdrawBtc { Method::WithdrawBtc(WithdrawBtcArgs {
amount, amount,
address: withdraw_address, address: withdraw_address,
}, }),
&context, &context,
) )
.await .await
@ -165,12 +188,12 @@ pub fn register_modules(context: Arc<Context>) -> Result<RpcModule<Arc<Context>>
execute_request( execute_request(
params_raw, params_raw,
Method::BuyXmr { Method::BuyXmr(BuyXmrArgs {
bitcoin_change_address, bitcoin_change_address,
monero_receive_address, monero_receive_address,
seller, seller,
swap_id: Uuid::new_v4(), swap_id: Uuid::new_v4(),
}, }),
&context, &context,
) )
.await .await
@ -192,9 +215,9 @@ pub fn register_modules(context: Arc<Context>) -> Result<RpcModule<Arc<Context>>
execute_request( execute_request(
params_raw, params_raw,
Method::ListSellers { Method::ListSellers(ListSellersArgs {
rendezvous_point: rendezvous_point.clone(), rendezvous_point: rendezvous_point.clone(),
}, }),
&context, &context,
) )
.await .await

View file

@ -1,12 +1,12 @@
import { Box, DialogContentText } from '@material-ui/core'; import { Box, DialogContentText } from "@material-ui/core";
import { useActiveSwapInfo, useAppSelector } from 'store/hooks'; import { useActiveSwapInfo, useAppSelector } from "store/hooks";
import CliLogsBox from '../../../other/RenderedCliLog'; import CliLogsBox from "../../../other/RenderedCliLog";
import JsonTreeView from '../../../other/JSONViewTree'; import JsonTreeView from "../../../other/JSONViewTree";
export default function DebugPage() { export default function DebugPage() {
const torStdOut = useAppSelector((s) => s.tor.stdOut); const torStdOut = useAppSelector((s) => s.tor.stdOut);
const logs = useAppSelector((s) => s.swap.logs); const logs = useAppSelector((s) => s.swap.logs);
const guiState = useAppSelector((s) => s.swap); const guiState = useAppSelector((s) => s);
const cliState = useActiveSwapInfo(); const cliState = useActiveSwapInfo();
return ( return (
@ -14,9 +14,9 @@ export default function DebugPage() {
<DialogContentText> <DialogContentText>
<Box <Box
style={{ style={{
display: 'flex', display: "flex",
flexDirection: 'column', flexDirection: "column",
gap: '8px', gap: "8px",
}} }}
> >
<CliLogsBox logs={logs} label="Logs relevant to the swap" /> <CliLogsBox logs={logs} label="Logs relevant to the swap" />
@ -28,7 +28,7 @@ export default function DebugPage() {
data={cliState} data={cliState}
label="Swap Daemon State (exposed via API)" label="Swap Daemon State (exposed via API)"
/> />
<CliLogsBox label="Tor Daemon Logs" logs={torStdOut.split('\n')} /> <CliLogsBox label="Tor Daemon Logs" logs={torStdOut.split("\n")} />
</Box> </Box>
</DialogContentText> </DialogContentText>
</Box> </Box>

View file

@ -1,6 +1,6 @@
import { piconerosToXmr, satsToBtc } from 'utils/conversionUtils'; import { piconerosToXmr, satsToBtc } from "utils/conversionUtils";
import { Tooltip } from '@material-ui/core'; import { Tooltip } from "@material-ui/core";
import { useAppSelector } from 'store/hooks'; import { useAppSelector } from "store/hooks";
type Amount = number | null | undefined; type Amount = number | null | undefined;
@ -21,13 +21,13 @@ export function AmountWithUnit({
title={ title={
dollarRate != null && amount != null dollarRate != null && amount != null
? `$${(dollarRate * amount).toFixed(2)}` ? `$${(dollarRate * amount).toFixed(2)}`
: '' : ""
} }
> >
<span> <span>
{amount != null {amount != null
? Number.parseFloat(amount.toFixed(fixedPrecision)) ? Number.parseFloat(amount.toFixed(fixedPrecision))
: '?'}{' '} : "?"}{" "}
{unit} {unit}
</span> </span>
</Tooltip> </Tooltip>

View file

@ -4,11 +4,15 @@ import { store } from "./store/storeRenderer";
import { rpcSetBalance } from "store/features/rpcSlice"; import { rpcSetBalance } from "store/features/rpcSlice";
export async function checkBitcoinBalance() { export async function checkBitcoinBalance() {
const response = await invoke('balance') as BalanceBitcoinResponse; // TODO: use tauri-bindgen here
const response = (await invoke("balance")) as {
balance: number;
};
store.dispatch(rpcSetBalance(response.balance)); store.dispatch(rpcSetBalance(response.balance));
} }
export async function getRawSwapInfos() { export async function getRawSwapInfos() {
const response = await invoke('swap_infos'); const response = await invoke("swap_infos");
console.log(response); console.log(response);
} }