diff --git a/CHANGELOG.md b/CHANGELOG.md index 98341c36..3f3ded05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- ASB + CONTROLLER: Add a `registration-status` command to the controller shell. You can use it to get the registration status of the ASB at the configured rendezvous points. - ASB + GUI + CLI + SWAP: Split high-verbosity tracing into separate hourly-rotating JSON log files per subsystem to reduce noise and aid debugging: `tracing*.log` (core things), `tracing-tor*.log` (purely tor related), `tracing-libp2p*.log` (low level networking), `tracing-monero-wallet*.log` (low level Monero wallet related). `swap-all.log` remains for non-verbose logs. - ASB: Fix an issue where we would not redeem the Bitcoin and force a refund even though it was still possible to do so. diff --git a/justfile b/justfile index 1fca23cf..ea747be8 100644 --- a/justfile +++ b/justfile @@ -80,7 +80,11 @@ swap: # Run the asb on testnet asb-testnet: - ASB_DEV_ADDR_OUTPUT_PATH="$(pwd)/src-gui/.env.development" cargo run -p swap-asb --bin asb -- --trace --testnet start --rpc-bind-port 9944 --rpc-bind-host 0.0.0.0 + ASB_DEV_ADDR_OUTPUT_PATH="$(pwd)/src-gui/.env.development" cargo run -p swap-asb --bin asb -- --testnet start --rpc-bind-port 9944 --rpc-bind-host 0.0.0.0 + +# Launch the ASB controller REPL against a local testnet ASB instance +asb-testnet-controller: + cargo run -p swap-controller --bin asb-controller -- --url http://127.0.0.1:9944 # Updates our submodules (currently only Monero C++ codebase) update_submodules: diff --git a/swap-controller-api/src/lib.rs b/swap-controller-api/src/lib.rs index 2e4523d6..4d9b8152 100644 --- a/swap-controller-api/src/lib.rs +++ b/swap-controller-api/src/lib.rs @@ -33,6 +33,32 @@ pub struct ActiveConnectionsResponse { pub connections: usize, } +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum RendezvousConnectionStatus { + Disconnected, + Dialling, + Connected, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum RendezvousRegistrationStatus { + RegisterOnNextConnection, + Pending, + Registered, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct RegistrationStatusItem { + pub address: String, + pub connection: RendezvousConnectionStatus, + pub registration: RendezvousRegistrationStatus, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct RegistrationStatusResponse { + pub registrations: Vec, +} + #[derive(Serialize, Deserialize, Debug, Clone)] pub struct Swap { pub id: String, @@ -65,4 +91,6 @@ pub trait AsbApi { async fn active_connections(&self) -> Result; #[method(name = "get_swaps")] async fn get_swaps(&self) -> Result, ErrorObjectOwned>; + #[method(name = "registration_status")] + async fn registration_status(&self) -> Result; } diff --git a/swap-controller/src/cli.rs b/swap-controller/src/cli.rs index 5aca1128..9f8a8e91 100644 --- a/swap-controller/src/cli.rs +++ b/swap-controller/src/cli.rs @@ -33,4 +33,6 @@ pub enum Cmd { ActiveConnections, /// Get list of swaps GetSwaps, + /// Show rendezvous registration status + RegistrationStatus, } diff --git a/swap-controller/src/main.rs b/swap-controller/src/main.rs index dd527d2b..89d87c26 100644 --- a/swap-controller/src/main.rs +++ b/swap-controller/src/main.rs @@ -84,6 +84,20 @@ async fn dispatch(cmd: Cmd, client: impl AsbApiClient) -> anyhow::Result<()> { let response = client.bitcoin_seed().await?; println!("Descriptor (BIP-0382) containing the private keys of the internal Bitcoin wallet: \n{}", response.descriptor); } + Cmd::RegistrationStatus => { + let response = client.registration_status().await?; + println!("Your asb registers at rendezvous to make itself discoverable to takers.\n"); + if response.registrations.is_empty() { + println!("No rendezvous points configured"); + } else { + for item in response.registrations { + println!( + "Connection status to rendezvous point at \"{}\" is \"{:?}\". Registration status is \"{:?}\"", + item.address, item.connection, item.registration + ); + } + } + } } Ok(()) } diff --git a/swap-p2p/src/protocols/rendezvous.rs b/swap-p2p/src/protocols/rendezvous.rs index e4451a9e..007d3daa 100644 --- a/swap-p2p/src/protocols/rendezvous.rs +++ b/swap-p2p/src/protocols/rendezvous.rs @@ -62,8 +62,8 @@ pub mod register { use std::task::{Context, Poll}; use std::time::Duration; - #[derive(Clone, PartialEq)] - enum ConnectionStatus { + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + pub enum ConnectionStatus { Disconnected, Dialling, Connected, @@ -86,6 +86,45 @@ pub mod register { backoffs: HashMap, } + // Provide a read-only snapshot of rendezvous registrations + impl Behaviour { + /// Returns a snapshot of registration and connection status for all configured rendezvous nodes. + pub fn registrations(&self) -> Vec { + self.rendezvous_nodes + .iter() + .map(|n| RegistrationReport { + address: n.address.clone(), + connection: n.connection_status, + registration: match &n.registration_status { + RegistrationStatus::RegisterOnNextConnection => { + RegistrationStatusReport::RegisterOnNextConnection + } + RegistrationStatus::Pending => RegistrationStatusReport::Pending, + RegistrationStatus::Registered { .. } => { + RegistrationStatusReport::Registered + } + }, + }) + .collect() + } + } + + /// Public representation of a rendezvous node registration status + /// The raw `RegistrationStatus` cannot be exposed because it is not serializable + #[derive(Debug, Clone)] + pub struct RegistrationReport { + pub address: Multiaddr, + pub connection: ConnectionStatus, + pub registration: RegistrationStatusReport, + } + + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + pub enum RegistrationStatusReport { + RegisterOnNextConnection, + Pending, + Registered, + } + /// A node running the rendezvous server protocol. pub struct RendezvousNode { pub address: Multiaddr, diff --git a/swap/src/asb/event_loop.rs b/swap/src/asb/event_loop.rs index 7c34c0ca..f601873e 100644 --- a/swap/src/asb/event_loop.rs +++ b/swap/src/asb/event_loop.rs @@ -523,6 +523,17 @@ where let count = self.swarm.connected_peers().count(); let _ = respond_to.send(count); } + EventLoopRequest::GetRegistrationStatus { respond_to } => { + let registrations = self + .swarm + .behaviour() + .rendezvous + .as_ref() + .map(|b| b.registrations()) + .unwrap_or_default(); // If rendezvous behaviour is disabled we report empty list + + let _ = respond_to.send(registrations); + } } } } @@ -829,6 +840,9 @@ mod service { GetActiveConnections { respond_to: oneshot::Sender, }, + GetRegistrationStatus { + respond_to: oneshot::Sender>, + }, } /// Tower service for communicating with the EventLoop @@ -861,6 +875,18 @@ mod service { rx.await .map_err(|_| anyhow::anyhow!("EventLoop service did not respond")) } + + /// Get the registration status at configured rendezvous points + pub async fn get_registration_status( + &self, + ) -> anyhow::Result> { + let (tx, rx) = oneshot::channel(); + self.sender + .send(EventLoopRequest::GetRegistrationStatus { respond_to: tx }) + .map_err(|_| anyhow::anyhow!("EventLoop service is down"))?; + rx.await + .map_err(|_| anyhow::anyhow!("EventLoop service did not respond")) + } } } diff --git a/swap/src/asb/rpc/server.rs b/swap/src/asb/rpc/server.rs index 8e9237f1..d64c19e4 100644 --- a/swap/src/asb/rpc/server.rs +++ b/swap/src/asb/rpc/server.rs @@ -9,7 +9,9 @@ use jsonrpsee::types::ErrorObjectOwned; use std::sync::Arc; use swap_controller_api::{ ActiveConnectionsResponse, AsbApiServer, BitcoinBalanceResponse, BitcoinSeedResponse, - MoneroAddressResponse, MoneroBalanceResponse, MoneroSeedResponse, MultiaddressesResponse, Swap, + MoneroAddressResponse, MoneroBalanceResponse, MoneroSeedResponse, MultiaddressesResponse, + RegistrationStatusItem, RegistrationStatusResponse, RendezvousConnectionStatus, + RendezvousRegistrationStatus, Swap, }; use tokio_util::task::AbortOnDropHandle; @@ -159,6 +161,45 @@ impl AsbApiServer for RpcImpl { Ok(swaps) } + + async fn registration_status(&self) -> Result { + let regs = self + .event_loop_service + .get_registration_status() + .await + .into_json_rpc_result()?; + + let registrations = regs + .into_iter() + .map(|r| RegistrationStatusItem { + address: r.address.to_string(), + connection: match r.connection { + crate::asb::register::ConnectionStatus::Disconnected => { + RendezvousConnectionStatus::Disconnected + } + crate::asb::register::ConnectionStatus::Dialling => { + RendezvousConnectionStatus::Dialling + } + crate::asb::register::ConnectionStatus::Connected => { + RendezvousConnectionStatus::Connected + } + }, + registration: match r.registration { + crate::asb::register::RegistrationStatusReport::RegisterOnNextConnection => { + RendezvousRegistrationStatus::RegisterOnNextConnection + } + crate::asb::register::RegistrationStatusReport::Pending => { + RendezvousRegistrationStatus::Pending + } + crate::asb::register::RegistrationStatusReport::Registered => { + RendezvousRegistrationStatus::Registered + } + }, + }) + .collect(); + + Ok(RegistrationStatusResponse { registrations }) + } } trait IntoJsonRpcResult {