diff --git a/CHANGELOG.md b/CHANGELOG.md index 762edc83..63689bed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,11 +21,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 Now, after sending a spot price the ASB will wait for one minute for the CLI's to trigger the execution setup, and three minutes to see the BTC lock transaction of the CLI in mempool after the swap started. If the first timeout is triggered the execution setup will be aborted, if the second timeout is triggered the swap will be safely aborted. +### Changed + +- The commandline interface of the CLI to combine `--seller-addr` and `--seller-peer-id`. + These two parameters have been merged into a parameter `--seller` that accepts a single [multiaddress](https://docs.libp2p.io/concepts/addressing/). + The multiaddress must end with a `/p2p` protocol defining the seller's peer ID. + ### Removed - The websocket transport from the CLI. Websockets were only ever intended to be used for the ASB side to allow websites to retrieve quotes. The CLI can use regular TCP connections and having both - TCP and websockets - causes problems and unnecessary overhead. +- The `--seller-addr` parameter from the CLI's `resume` command. + This information is now loaded from the database. ## [0.7.0] - 2021-05-28 diff --git a/swap/src/bin/swap.rs b/swap/src/bin/swap.rs index 1d1327ae..a8129429 100644 --- a/swap/src/bin/swap.rs +++ b/swap/src/bin/swap.rs @@ -27,6 +27,7 @@ use swap::cli::command::{parse_args_and_apply_defaults, Arguments, Command, Pars use swap::cli::EventLoop; use swap::database::Database; use swap::env::Config; +use swap::libp2p_ext::MultiAddrExt; use swap::network::quote::BidQuote; use swap::network::swarm; use swap::protocol::bob; @@ -58,8 +59,7 @@ async fn main() -> Result<()> { match cmd { Command::BuyXmr { - seller_peer_id, - seller_addr, + seller, bitcoin_electrum_rpc_url, bitcoin_target_block, monero_receive_address, @@ -86,6 +86,11 @@ async fn main() -> Result<()> { init_monero_wallet(data_dir, monero_daemon_address, env_config).await?; let bitcoin_wallet = Arc::new(bitcoin_wallet); + let seller_peer_id = seller + .extract_peer_id() + .context("Seller address must contain peer ID")?; + db.insert_address(seller_peer_id, seller.clone()).await?; + let mut swarm = swarm::cli( &seed, seller_peer_id, @@ -94,9 +99,7 @@ async fn main() -> Result<()> { bitcoin_wallet.clone(), ) .await?; - swarm - .behaviour_mut() - .add_address(seller_peer_id, seller_addr); + swarm.behaviour_mut().add_address(seller_peer_id, seller); tracing::debug!(peer_id = %swarm.local_peer_id(), "Network layer initialized"); @@ -157,7 +160,6 @@ async fn main() -> Result<()> { } Command::Resume { swap_id, - seller_addr, bitcoin_electrum_rpc_url, bitcoin_target_block, monero_receive_address, @@ -187,6 +189,7 @@ async fn main() -> Result<()> { let bitcoin_wallet = Arc::new(bitcoin_wallet); let seller_peer_id = db.get_peer_id(swap_id)?; + let seller_addresses = db.get_addresses(seller_peer_id)?; let mut swarm = swarm::cli( &seed, @@ -198,9 +201,12 @@ async fn main() -> Result<()> { .await?; let our_peer_id = swarm.local_peer_id(); tracing::debug!(peer_id = %our_peer_id, "Initializing network module"); - swarm - .behaviour_mut() - .add_address(seller_peer_id, seller_addr); + + for seller_address in seller_addresses { + swarm + .behaviour_mut() + .add_address(seller_peer_id, seller_address); + } let (event_loop, event_loop_handle) = EventLoop::new(swap_id, swarm, seller_peer_id, env_config)?; diff --git a/swap/src/cli/command.rs b/swap/src/cli/command.rs index 815288a3..d1f82398 100644 --- a/swap/src/cli/command.rs +++ b/swap/src/cli/command.rs @@ -3,7 +3,6 @@ use crate::fs::system_data_dir; use crate::{env, monero}; use anyhow::{Context, Result}; use libp2p::core::Multiaddr; -use libp2p::PeerId; use std::ffi::OsString; use std::path::PathBuf; use std::str::FromStr; @@ -68,8 +67,7 @@ where let arguments = match args.cmd { RawCommand::BuyXmr { - seller_peer_id, - seller_addr: SellerAddr { seller_addr }, + seller: Seller { seller }, bitcoin: Bitcoin { bitcoin_electrum_rpc_url, @@ -87,8 +85,7 @@ where json, data_dir: data::data_dir_from(data, is_testnet)?, cmd: Command::BuyXmr { - seller_peer_id, - seller_addr, + seller, bitcoin_electrum_rpc_url: bitcoin_electrum_rpc_url_from( bitcoin_electrum_rpc_url, is_testnet, @@ -114,7 +111,6 @@ where }, RawCommand::Resume { swap_id: SwapId { swap_id }, - seller_addr: SellerAddr { seller_addr }, bitcoin: Bitcoin { bitcoin_electrum_rpc_url, @@ -133,7 +129,6 @@ where data_dir: data::data_dir_from(data, is_testnet)?, cmd: Command::Resume { swap_id, - seller_addr, bitcoin_electrum_rpc_url: bitcoin_electrum_rpc_url_from( bitcoin_electrum_rpc_url, is_testnet, @@ -201,8 +196,7 @@ where #[derive(Debug, PartialEq)] pub enum Command { BuyXmr { - seller_peer_id: PeerId, - seller_addr: Multiaddr, + seller: Multiaddr, bitcoin_electrum_rpc_url: Url, bitcoin_target_block: usize, monero_receive_address: monero::Address, @@ -212,7 +206,6 @@ pub enum Command { History, Resume { swap_id: Uuid, - seller_addr: Multiaddr, bitcoin_electrum_rpc_url: Url, bitcoin_target_block: usize, monero_receive_address: monero::Address, @@ -268,11 +261,8 @@ pub struct RawArguments { pub enum RawCommand { /// Start a XMR for BTC swap BuyXmr { - #[structopt(long = "seller-peer-id", help = "The seller's peer id")] - seller_peer_id: PeerId, - #[structopt(flatten)] - seller_addr: SellerAddr, + seller: Seller, #[structopt(flatten)] bitcoin: Bitcoin, @@ -290,9 +280,6 @@ pub enum RawCommand { #[structopt(flatten)] swap_id: SwapId, - #[structopt(flatten)] - seller_addr: SellerAddr, - #[structopt(flatten)] bitcoin: Bitcoin, @@ -373,9 +360,12 @@ pub struct SwapId { } #[derive(structopt::StructOpt, Debug)] -pub struct SellerAddr { - #[structopt(long = "seller-addr", help = "The seller's multiaddress")] - pub seller_addr: Multiaddr, +pub struct Seller { + #[structopt( + long, + help = "The seller's address. Must include a peer ID part, i.e. `/p2p/`" + )] + pub seller: Multiaddr, } mod data { @@ -492,8 +482,8 @@ mod tests { const MONERO_STAGENET_ADDRESS: &str = "53gEuGZUhP9JMEBZoGaFNzhwEgiG7hwQdMCqFxiyiTeFPmkbt1mAoNybEUvYBKHcnrSgxnVWgZsTvRBaHBNXPa8tHiCU51a"; const MONERO_MAINNET_ADDRESS: &str = "44Ato7HveWidJYUAVw5QffEcEtSH1DwzSP3FPPkHxNAS4LX9CqgucphTisH978FLHE34YNEx7FcbBfQLQUU8m3NUC4VqsRa"; - const MUTLI_ADDRESS: &str = "/ip4/127.0.0.1/tcp/9939"; - const PEER_ID: &str = "12D3KooWCdMKjesXMJz1SiZ7HgotrxuqhQJbP5sgBm2BwP1cqThi"; + const MULTI_ADDRESS: &str = + "/ip4/127.0.0.1/tcp/9939/p2p/12D3KooWCdMKjesXMJz1SiZ7HgotrxuqhQJbP5sgBm2BwP1cqThi"; const SWAP_ID: &str = "ea030832-3be9-454f-bb98-5ea9a788406b"; #[test] @@ -503,10 +493,8 @@ mod tests { "buy-xmr", "--receive-address", MONERO_MAINNET_ADDRESS, - "--seller-addr", - MUTLI_ADDRESS, - "--seller-peer-id", - PEER_ID, + "--seller", + MULTI_ADDRESS, ]; let expected_args = ParseResult::Arguments(Arguments::buy_xmr_mainnet_defaults()); @@ -523,10 +511,8 @@ mod tests { "buy-xmr", "--receive-address", MONERO_STAGENET_ADDRESS, - "--seller-addr", - MUTLI_ADDRESS, - "--seller-peer-id", - PEER_ID, + "--seller", + MULTI_ADDRESS, ]; let args = parse_args_and_apply_defaults(raw_ars).unwrap(); @@ -544,10 +530,8 @@ mod tests { "buy-xmr", "--receive-address", MONERO_STAGENET_ADDRESS, - "--seller-addr", - MUTLI_ADDRESS, - "--seller-peer-id", - PEER_ID, + "--seller", + MULTI_ADDRESS, ]; let err = parse_args_and_apply_defaults(raw_ars).unwrap_err(); @@ -569,10 +553,8 @@ mod tests { "buy-xmr", "--receive-address", MONERO_MAINNET_ADDRESS, - "--seller-addr", - MUTLI_ADDRESS, - "--seller-peer-id", - PEER_ID, + "--seller", + MULTI_ADDRESS, ]; let err = parse_args_and_apply_defaults(raw_ars).unwrap_err(); @@ -595,8 +577,6 @@ mod tests { SWAP_ID, "--receive-address", MONERO_MAINNET_ADDRESS, - "--seller-addr", - MUTLI_ADDRESS, ]; let args = parse_args_and_apply_defaults(raw_ars).unwrap(); @@ -617,8 +597,6 @@ mod tests { SWAP_ID, "--receive-address", MONERO_STAGENET_ADDRESS, - "--seller-addr", - MUTLI_ADDRESS, ]; let args = parse_args_and_apply_defaults(raw_ars).unwrap(); @@ -688,10 +666,8 @@ mod tests { "buy-xmr", "--receive-address", MONERO_MAINNET_ADDRESS, - "--seller-addr", - MUTLI_ADDRESS, - "--seller-peer-id", - PEER_ID, + "--seller", + MULTI_ADDRESS, ]; let args = parse_args_and_apply_defaults(raw_ars).unwrap(); @@ -712,10 +688,8 @@ mod tests { "buy-xmr", "--receive-address", MONERO_STAGENET_ADDRESS, - "--seller-addr", - MUTLI_ADDRESS, - "--seller-peer-id", - PEER_ID, + "--seller", + MULTI_ADDRESS, ]; let args = parse_args_and_apply_defaults(raw_ars).unwrap(); @@ -737,8 +711,6 @@ mod tests { SWAP_ID, "--receive-address", MONERO_MAINNET_ADDRESS, - "--seller-addr", - MUTLI_ADDRESS, ]; let args = parse_args_and_apply_defaults(raw_ars).unwrap(); @@ -761,8 +733,6 @@ mod tests { SWAP_ID, "--receive-address", MONERO_STAGENET_ADDRESS, - "--seller-addr", - MUTLI_ADDRESS, ]; let args = parse_args_and_apply_defaults(raw_ars).unwrap(); @@ -784,10 +754,8 @@ mod tests { "buy-xmr", "--receive-address", MONERO_MAINNET_ADDRESS, - "--seller-addr", - MUTLI_ADDRESS, - "--seller-peer-id", - PEER_ID, + "--seller", + MULTI_ADDRESS, ]; let args = parse_args_and_apply_defaults(raw_ars).unwrap(); @@ -803,10 +771,8 @@ mod tests { "buy-xmr", "--receive-address", MONERO_STAGENET_ADDRESS, - "--seller-addr", - MUTLI_ADDRESS, - "--seller-peer-id", - PEER_ID, + "--seller", + MULTI_ADDRESS, ]; let args = parse_args_and_apply_defaults(raw_ars).unwrap(); @@ -823,8 +789,6 @@ mod tests { SWAP_ID, "--receive-address", MONERO_MAINNET_ADDRESS, - "--seller-addr", - MUTLI_ADDRESS, ]; let args = parse_args_and_apply_defaults(raw_ars).unwrap(); @@ -842,8 +806,6 @@ mod tests { SWAP_ID, "--receive-address", MONERO_STAGENET_ADDRESS, - "--seller-addr", - MUTLI_ADDRESS, ]; let args = parse_args_and_apply_defaults(raw_ars).unwrap(); @@ -861,10 +823,8 @@ mod tests { "buy-xmr", "--receive-address", MONERO_MAINNET_ADDRESS, - "--seller-addr", - MUTLI_ADDRESS, - "--seller-peer-id", - PEER_ID, + "--seller", + MULTI_ADDRESS, ]; let args = parse_args_and_apply_defaults(raw_ars).unwrap(); @@ -880,10 +840,8 @@ mod tests { "buy-xmr", "--receive-address", MONERO_STAGENET_ADDRESS, - "--seller-addr", - MUTLI_ADDRESS, - "--seller-peer-id", - PEER_ID, + "--seller", + MULTI_ADDRESS, ]; let args = parse_args_and_apply_defaults(raw_ars).unwrap(); @@ -900,8 +858,6 @@ mod tests { SWAP_ID, "--receive-address", MONERO_MAINNET_ADDRESS, - "--seller-addr", - MUTLI_ADDRESS, ]; let args = parse_args_and_apply_defaults(raw_ars).unwrap(); @@ -919,8 +875,6 @@ mod tests { SWAP_ID, "--receive-address", MONERO_STAGENET_ADDRESS, - "--seller-addr", - MUTLI_ADDRESS, ]; let args = parse_args_and_apply_defaults(raw_ars).unwrap(); @@ -938,8 +892,7 @@ mod tests { json: false, data_dir: data_dir_path_cli().join(TESTNET), cmd: Command::BuyXmr { - seller_peer_id: PeerId::from_str(PEER_ID).unwrap(), - seller_addr: Multiaddr::from_str(MUTLI_ADDRESS).unwrap(), + seller: Multiaddr::from_str(MULTI_ADDRESS).unwrap(), bitcoin_electrum_rpc_url: Url::from_str(DEFAULT_ELECTRUM_RPC_URL_TESTNET) .unwrap(), bitcoin_target_block: DEFAULT_BITCOIN_CONFIRMATION_TARGET_TESTNET, @@ -958,8 +911,7 @@ mod tests { json: false, data_dir: data_dir_path_cli().join(MAINNET), cmd: Command::BuyXmr { - seller_peer_id: PeerId::from_str(PEER_ID).unwrap(), - seller_addr: Multiaddr::from_str(MUTLI_ADDRESS).unwrap(), + seller: Multiaddr::from_str(MULTI_ADDRESS).unwrap(), bitcoin_electrum_rpc_url: Url::from_str(DEFAULT_ELECTRUM_RPC_URL).unwrap(), bitcoin_target_block: DEFAULT_BITCOIN_CONFIRMATION_TARGET, monero_receive_address: monero::Address::from_str(MONERO_MAINNET_ADDRESS) @@ -978,7 +930,6 @@ mod tests { data_dir: data_dir_path_cli().join(TESTNET), cmd: Command::Resume { swap_id: Uuid::from_str(SWAP_ID).unwrap(), - seller_addr: Multiaddr::from_str(MUTLI_ADDRESS).unwrap(), bitcoin_electrum_rpc_url: Url::from_str(DEFAULT_ELECTRUM_RPC_URL_TESTNET) .unwrap(), bitcoin_target_block: DEFAULT_BITCOIN_CONFIRMATION_TARGET_TESTNET, @@ -998,7 +949,6 @@ mod tests { data_dir: data_dir_path_cli().join(MAINNET), cmd: Command::Resume { swap_id: Uuid::from_str(SWAP_ID).unwrap(), - seller_addr: Multiaddr::from_str(MUTLI_ADDRESS).unwrap(), bitcoin_electrum_rpc_url: Url::from_str(DEFAULT_ELECTRUM_RPC_URL).unwrap(), bitcoin_target_block: DEFAULT_BITCOIN_CONFIRMATION_TARGET, monero_receive_address: monero::Address::from_str(MONERO_MAINNET_ADDRESS) diff --git a/swap/src/database.rs b/swap/src/database.rs index a6839544..77ae6cb4 100644 --- a/swap/src/database.rs +++ b/swap/src/database.rs @@ -3,7 +3,7 @@ pub use bob::Bob; use anyhow::{anyhow, bail, Context, Result}; use itertools::Itertools; -use libp2p::PeerId; +use libp2p::{Multiaddr, PeerId}; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; use std::fmt::Display; @@ -68,6 +68,7 @@ impl Swap { pub struct Database { swaps: sled::Tree, peers: sled::Tree, + addresses: sled::Tree, } impl Database { @@ -79,8 +80,13 @@ impl Database { let swaps = db.open_tree("swaps")?; let peers = db.open_tree("peers")?; + let addresses = db.open_tree("addresses")?; - Ok(Database { swaps, peers }) + Ok(Database { + swaps, + peers, + addresses, + }) } pub async fn insert_peer_id(&self, swap_id: Uuid, peer_id: PeerId) -> Result<()> { @@ -110,6 +116,46 @@ impl Database { Ok(PeerId::from_str(peer_id.as_str())?) } + pub async fn insert_address(&self, peer_id: PeerId, address: Multiaddr) -> Result<()> { + let key = peer_id.to_bytes(); + + let existing_addresses = self.addresses.get(&key)?; + + let new_addresses = { + let existing_addresses = existing_addresses.clone(); + + Some(match existing_addresses { + Some(encoded) => { + let mut addresses = deserialize::>(&encoded)?; + addresses.push(address); + + serialize(&addresses)? + } + None => serialize(&[address])?, + }) + }; + + self.addresses + .compare_and_swap(key, existing_addresses, new_addresses)??; + + self.addresses + .flush_async() + .await + .map(|_| ()) + .context("Could not flush db") + } + + pub fn get_addresses(&self, peer_id: PeerId) -> Result> { + let key = peer_id.to_bytes(); + + let addresses = match self.addresses.get(&key)? { + Some(encoded) => deserialize(&encoded).context("Failed to deserialize addresses")?, + None => vec![], + }; + + Ok(addresses) + } + pub async fn insert_latest_state(&self, swap_id: Uuid, state: Swap) -> Result<()> { let key = serialize(&swap_id)?; let new_value = serialize(&state).context("Could not serialize new state value")?; diff --git a/swap/src/lib.rs b/swap/src/lib.rs index 3fb58372..9308b571 100644 --- a/swap/src/lib.rs +++ b/swap/src/lib.rs @@ -23,6 +23,7 @@ pub mod database; pub mod env; pub mod fs; pub mod kraken; +pub mod libp2p_ext; pub mod monero; pub mod network; pub mod protocol; diff --git a/swap/src/libp2p_ext.rs b/swap/src/libp2p_ext.rs new file mode 100644 index 00000000..11be4c50 --- /dev/null +++ b/swap/src/libp2p_ext.rs @@ -0,0 +1,15 @@ +use libp2p::multiaddr::Protocol; +use libp2p::{Multiaddr, PeerId}; + +pub trait MultiAddrExt { + fn extract_peer_id(&self) -> Option; +} + +impl MultiAddrExt for Multiaddr { + fn extract_peer_id(&self) -> Option { + match self.iter().last()? { + Protocol::P2p(multihash) => PeerId::from_multihash(multihash).ok(), + _ => None, + } + } +}