From d90496931b8f9799720d7385de3a3207c487a0fa Mon Sep 17 00:00:00 2001 From: Daniel Karzel Date: Tue, 30 Mar 2021 15:40:59 +1100 Subject: [PATCH] Save Alice's peer-id in the db for Bob This allows loading the seller-peer-id from the database upon resuming a swap. Thus, the parameters `--seller-peer-id` is removed for the `resume` command. Other than the peer-id the multi address of a seller can change and thus is still a parameter. This parameter might become optional once we add DHT support. --- CHANGELOG.md | 5 +++ swap/src/bin/swap.rs | 19 ++++---- swap/src/cli/command.rs | 20 ++++----- swap/src/database.rs | 98 +++++++++++++++++++++++++++++++++++++---- 4 files changed, 116 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6561f042..0beb192e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- The `resume` command of the `swap` CLI no longer require the `--seller-peer-id` parameter. + This information is now saved in the database. + ### Added - A changelog file. diff --git a/swap/src/bin/swap.rs b/swap/src/bin/swap.rs index a4b877c4..ec731880 100644 --- a/swap/src/bin/swap.rs +++ b/swap/src/bin/swap.rs @@ -21,7 +21,7 @@ use std::sync::Arc; use std::time::Duration; use structopt::StructOpt; use swap::bitcoin::{Amount, TxLock}; -use swap::cli::command::{AliceConnectParams, Arguments, Command, Data, MoneroParams}; +use swap::cli::command::{AliceMultiaddress, Arguments, Command, Data, MoneroParams}; use swap::database::Database; use swap::env::{Config, GetConfig}; use swap::network::quote::BidQuote; @@ -81,9 +81,9 @@ async fn main() -> Result<()> { match args.cmd { Command::BuyXmr { - connect_params: - AliceConnectParams { - peer_id: alice_peer_id, + alice_peer_id, + alice_multi_addr: + AliceMultiaddress { multiaddr: alice_addr, }, monero_params: @@ -131,9 +131,12 @@ async fn main() -> Result<()> { ) .await?; + let swap_id = Uuid::new_v4(); + db.insert_peer_id(swap_id, alice_peer_id).await?; + let swap = Builder::new( db, - Uuid::new_v4(), + swap_id, bitcoin_wallet.clone(), Arc::new(monero_wallet), env_config, @@ -167,9 +170,8 @@ async fn main() -> Result<()> { } Command::Resume { swap_id, - connect_params: - AliceConnectParams { - peer_id: alice_peer_id, + alice_multi_addr: + AliceMultiaddress { multiaddr: alice_addr, }, monero_params: @@ -189,6 +191,7 @@ async fn main() -> Result<()> { init_monero_wallet(data_dir, monero_daemon_host, env_config).await?; let bitcoin_wallet = Arc::new(bitcoin_wallet); + let alice_peer_id = db.get_peer_id(swap_id)?; let mut swarm = swarm::new::(&seed)?; swarm.add_address(alice_peer_id, alice_addr); diff --git a/swap/src/cli/command.rs b/swap/src/cli/command.rs index 47c8b9f1..6e66736e 100644 --- a/swap/src/cli/command.rs +++ b/swap/src/cli/command.rs @@ -37,8 +37,15 @@ pub struct Arguments { pub enum Command { /// Start a XMR for BTC swap BuyXmr { + #[structopt( + long = "seller-peer-id", + default_value = DEFAULT_ALICE_PEER_ID, + help = "The peer id of a specific swap partner can be optionally provided" + )] + alice_peer_id: PeerId, + #[structopt(flatten)] - connect_params: AliceConnectParams, + alice_multi_addr: AliceMultiaddress, #[structopt(long = "electrum-rpc", help = "Provide the Bitcoin Electrum RPC URL", @@ -60,7 +67,7 @@ pub enum Command { swap_id: Uuid, #[structopt(flatten)] - connect_params: AliceConnectParams, + alice_multi_addr: AliceMultiaddress, #[structopt(long = "electrum-rpc", help = "Provide the Bitcoin Electrum RPC URL", @@ -108,14 +115,7 @@ pub enum Command { } #[derive(structopt::StructOpt, Debug)] -pub struct AliceConnectParams { - #[structopt( - long = "seller-peer-id", - default_value = DEFAULT_ALICE_PEER_ID, - help = "The peer id of a specific swap partner can be optionally provided" - )] - pub peer_id: PeerId, - +pub struct AliceMultiaddress { #[structopt( long = "seller-addr", default_value = DEFAULT_ALICE_MULTIADDR, diff --git a/swap/src/database.rs b/swap/src/database.rs index 4e62af59..a6839544 100644 --- a/swap/src/database.rs +++ b/swap/src/database.rs @@ -3,10 +3,12 @@ pub use bob::Bob; use anyhow::{anyhow, bail, Context, Result}; use itertools::Itertools; +use libp2p::PeerId; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; use std::fmt::Display; use std::path::Path; +use std::str::FromStr; use uuid::Uuid; mod alice; @@ -63,7 +65,10 @@ impl Swap { } } -pub struct Database(sled::Db); +pub struct Database { + swaps: sled::Tree, + peers: sled::Tree, +} impl Database { pub fn open(path: &Path) -> Result { @@ -72,22 +77,51 @@ impl Database { let db = sled::open(path).with_context(|| format!("Could not open the DB at {:?}", path))?; - Ok(Database(db)) + let swaps = db.open_tree("swaps")?; + let peers = db.open_tree("peers")?; + + Ok(Database { swaps, peers }) + } + + pub async fn insert_peer_id(&self, swap_id: Uuid, peer_id: PeerId) -> Result<()> { + let peer_id_str = peer_id.to_string(); + + let key = serialize(&swap_id)?; + let value = serialize(&peer_id_str).context("Could not serialize peer-id")?; + + self.peers.insert(key, value)?; + + self.peers + .flush_async() + .await + .map(|_| ()) + .context("Could not flush db") + } + + pub fn get_peer_id(&self, swap_id: Uuid) -> Result { + let key = serialize(&swap_id)?; + + let encoded = self + .peers + .get(&key)? + .ok_or_else(|| anyhow!("No peer-id found for swap id {} in database", swap_id))?; + + let peer_id: String = deserialize(&encoded).context("Could not deserialize peer-id")?; + Ok(PeerId::from_str(peer_id.as_str())?) } 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")?; - let old_value = self.0.get(&key)?; + let old_value = self.swaps.get(&key)?; - self.0 + self.swaps .compare_and_swap(key, old_value, Some(new_value)) .context("Could not write in the DB")? .context("Stored swap somehow changed, aborting saving")?; - // TODO: see if this can be done through sled config - self.0 + self.swaps .flush_async() .await .map(|_| ()) @@ -98,7 +132,7 @@ impl Database { let key = serialize(&swap_id)?; let encoded = self - .0 + .swaps .get(&key)? .ok_or_else(|| anyhow!("Swap with id {} not found in database", swap_id))?; @@ -129,7 +163,7 @@ impl Database { } fn all_swaps_iter(&self) -> impl Iterator> { - self.0.iter().map(|item| { + self.swaps.iter().map(|item| { let (key, value) = item.context("Failed to retrieve swap from DB")?; let swap_id = deserialize::(&key)?; @@ -277,4 +311,52 @@ mod tests { assert_eq!(err.downcast_ref::().unwrap(), &NotBob); } + + #[tokio::test] + async fn can_save_swap_state_and_peer_id_with_same_swap_id() -> Result<()> { + let db_dir = tempfile::tempdir().unwrap(); + let db = Database::open(db_dir.path()).unwrap(); + + let alice_id = Uuid::new_v4(); + let alice_state = Alice::Done(AliceEndState::BtcPunished); + let alice_swap = Swap::Alice(alice_state); + let peer_id = PeerId::random(); + + db.insert_latest_state(alice_id, alice_swap.clone()).await?; + db.insert_peer_id(alice_id, peer_id).await?; + + let loaded_swap = db.get_state(alice_id)?; + let loaded_peer_id = db.get_peer_id(alice_id)?; + + assert_eq!(alice_swap, loaded_swap); + assert_eq!(peer_id, loaded_peer_id); + + Ok(()) + } + + #[tokio::test] + async fn test_reopen_db() -> Result<()> { + let db_dir = tempfile::tempdir().unwrap(); + let alice_id = Uuid::new_v4(); + let alice_state = Alice::Done(AliceEndState::BtcPunished); + let alice_swap = Swap::Alice(alice_state); + + let peer_id = PeerId::random(); + + { + let db = Database::open(db_dir.path()).unwrap(); + db.insert_latest_state(alice_id, alice_swap.clone()).await?; + db.insert_peer_id(alice_id, peer_id).await?; + } + + let db = Database::open(db_dir.path()).unwrap(); + + let loaded_swap = db.get_state(alice_id)?; + let loaded_peer_id = db.get_peer_id(alice_id)?; + + assert_eq!(alice_swap, loaded_swap); + assert_eq!(peer_id, loaded_peer_id); + + Ok(()) + } }