From 6d06db3259c0447bf8797864ff5182d4968bd306 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Thu, 15 Apr 2021 18:39:59 +1000 Subject: [PATCH 01/15] Use macro-based JSON-RPC client --- Cargo.lock | 30 +- monero-harness/src/lib.rs | 31 +- monero-harness/tests/monerod.rs | 3 +- monero-harness/tests/wallet.rs | 5 +- monero-rpc/Cargo.toml | 2 + monero-rpc/src/lib.rs | 5 +- monero-rpc/src/monerod.rs | 60 ++++ monero-rpc/src/rpc.rs | 63 ---- monero-rpc/src/rpc/monerod.rs | 154 --------- monero-rpc/src/rpc/wallet.rs | 569 -------------------------------- monero-rpc/src/wallet.rs | 258 +++++++++++++++ swap/src/monero/wallet.rs | 76 +++-- swap/src/monero/wallet_rpc.rs | 2 +- swap/src/protocol/alice/swap.rs | 10 +- swap/src/protocol/bob/swap.rs | 13 +- 15 files changed, 435 insertions(+), 846 deletions(-) create mode 100644 monero-rpc/src/monerod.rs delete mode 100644 monero-rpc/src/rpc.rs delete mode 100644 monero-rpc/src/rpc/monerod.rs delete mode 100644 monero-rpc/src/rpc/wallet.rs create mode 100644 monero-rpc/src/wallet.rs diff --git a/Cargo.lock b/Cargo.lock index 04d61a90..261d3600 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -356,7 +356,7 @@ dependencies = [ "bitcoincore-rpc-json", "futures", "hex 0.4.3", - "jsonrpc_client", + "jsonrpc_client 0.5.1", "reqwest", "serde", "serde_json", @@ -1629,7 +1629,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc8515639023bf4260cf89475355fa77301685418f655c680528c380759e7782" dependencies = [ "async-trait", - "jsonrpc_client_macro", + "jsonrpc_client_macro 0.2.0", + "reqwest", + "serde", + "serde_json", + "url 2.2.1", +] + +[[package]] +name = "jsonrpc_client" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a85cf2c5ce158eabf30b2ac4f535463d7b09ce7905502e11238b7d6048ef7d02" +dependencies = [ + "async-trait", + "jsonrpc_client_macro 0.3.0", "reqwest", "serde", "serde_json", @@ -1646,6 +1660,16 @@ dependencies = [ "syn", ] +[[package]] +name = "jsonrpc_client_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97c11e429f0eaa41fe659013680b459d2368d8f0a3e69dccfb7a35800b0dc27b" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "keccak-hash" version = "0.7.0" @@ -2157,6 +2181,8 @@ name = "monero-rpc" version = "0.1.0" dependencies = [ "anyhow", + "jsonrpc_client 0.6.0", + "monero", "reqwest", "serde", "serde_json", diff --git a/monero-harness/src/lib.rs b/monero-harness/src/lib.rs index 48615b92..0824a44d 100644 --- a/monero-harness/src/lib.rs +++ b/monero-harness/src/lib.rs @@ -28,7 +28,8 @@ use crate::image::{ use anyhow::{anyhow, bail, Result}; use monero_rpc::{ monerod, - wallet::{self, GetAddress, Refreshed, Transfer}, + monerod::MonerodRpc as _, + wallet::{self, GetAddress, MoneroWalletRpc as _, Refreshed, Transfer}, }; use std::time::Duration; use testcontainers::{clients::Cli, core::Port, Container, Docker, RunArgs}; @@ -104,7 +105,10 @@ impl<'c> Monero { // generate the first 70 as bulk let monerod = &self.monerod; - let res = monerod.client().generate_blocks(70, &miner_address).await?; + let res = monerod + .client() + .generateblocks(70, miner_address.clone()) + .await?; tracing::info!("Generated {:?} blocks", res.blocks.len()); miner_wallet.refresh().await?; @@ -123,7 +127,10 @@ impl<'c> Monero { if amount > 0 { miner_wallet.transfer(&address, amount).await?; tracing::info!("Funded {} wallet with {}", wallet.name, amount); - monerod.client().generate_blocks(10, &miner_address).await?; + monerod + .client() + .generateblocks(10, miner_address.clone()) + .await?; wallet.refresh().await?; } } @@ -139,7 +146,7 @@ impl<'c> Monero { monerod.start_miner(&miner_address).await?; tracing::info!("Waiting for miner wallet to catch up..."); - let block_height = monerod.client().get_block_count().await?; + let block_height = monerod.client().get_block_count().await?.count; miner_wallet .wait_for_wallet_height(block_height) .await @@ -256,7 +263,7 @@ impl<'c> MoneroWalletRpc { // create new wallet wallet::Client::localhost(wallet_rpc_port) - .create_wallet(name) + .create_wallet(name.to_owned(), "English".to_owned()) .await .unwrap(); @@ -277,7 +284,7 @@ impl<'c> MoneroWalletRpc { // It takes a little while for the wallet to sync with monerod. pub async fn wait_for_wallet_height(&self, height: u32) -> Result<()> { let mut retry: u8 = 0; - while self.client().block_height().await?.height < height { + while self.client().get_height().await?.height < height { if retry >= 30 { // ~30 seconds bail!("Wallet could not catch up with monerod after 30 retries.") @@ -290,26 +297,28 @@ impl<'c> MoneroWalletRpc { /// Sends amount to address pub async fn transfer(&self, address: &str, amount: u64) -> Result { - self.client().transfer(0, amount, address).await + Ok(self.client().transfer_single(0, amount, address).await?) } pub async fn address(&self) -> Result { - self.client().get_address(0).await + Ok(self.client().get_address(0).await?) } pub async fn balance(&self) -> Result { self.client().refresh().await?; - self.client().get_balance(0).await + let balance = self.client().get_balance(0).await?.balance; + + Ok(balance) } pub async fn refresh(&self) -> Result { - self.client().refresh().await + Ok(self.client().refresh().await?) } } /// Mine a block ever BLOCK_TIME_SECS seconds. async fn mine(monerod: monerod::Client, reward_address: String) -> Result<()> { loop { time::sleep(Duration::from_secs(BLOCK_TIME_SECS)).await; - monerod.generate_blocks(1, &reward_address).await?; + monerod.generateblocks(1, reward_address.clone()).await?; } } diff --git a/monero-harness/tests/monerod.rs b/monero-harness/tests/monerod.rs index 257e888d..03311072 100644 --- a/monero-harness/tests/monerod.rs +++ b/monero-harness/tests/monerod.rs @@ -1,4 +1,5 @@ use monero_harness::Monero; +use monero_rpc::monerod::MonerodRpc as _; use spectral::prelude::*; use std::time::Duration; use testcontainers::clients::Cli; @@ -25,7 +26,7 @@ async fn init_miner_and_mine_to_miner_address() { time::sleep(Duration::from_millis(1010)).await; // after a bit more than 1 sec another block should have been mined - let block_height = monerod.client().get_block_count().await.unwrap(); + let block_height = monerod.client().get_block_count().await.unwrap().count; assert_that(&block_height).is_greater_than(70); } diff --git a/monero-harness/tests/wallet.rs b/monero-harness/tests/wallet.rs index a3f4e52e..d2e97cb4 100644 --- a/monero-harness/tests/wallet.rs +++ b/monero-harness/tests/wallet.rs @@ -1,4 +1,5 @@ use monero_harness::{Monero, MoneroWalletRpc}; +use monero_rpc::wallet::MoneroWalletRpc as _; use spectral::prelude::*; use std::time::Duration; use testcontainers::clients::Cli; @@ -45,10 +46,10 @@ async fn fund_transfer_and_check_tx_key() { // check if tx was actually seen let tx_id = transfer.tx_hash; - let tx_key = transfer.tx_key; + let tx_key = transfer.tx_key.unwrap().to_string(); let res = bob_wallet .client() - .check_tx_key(&tx_id, &tx_key, &bob_address) + .check_tx_key(tx_id, tx_key, bob_address) .await .expect("failed to check tx by key"); diff --git a/monero-rpc/Cargo.toml b/monero-rpc/Cargo.toml index c70f2797..8f1b68ed 100644 --- a/monero-rpc/Cargo.toml +++ b/monero-rpc/Cargo.toml @@ -10,3 +10,5 @@ reqwest = { version = "0.11", default-features = false, features = ["json"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" tracing = "0.1" +jsonrpc_client = { version = "0.6", features = ["reqwest"] } +monero = "0.11" diff --git a/monero-rpc/src/lib.rs b/monero-rpc/src/lib.rs index 1a83b3e1..a1b2eaaa 100644 --- a/monero-rpc/src/lib.rs +++ b/monero-rpc/src/lib.rs @@ -12,6 +12,5 @@ )] #![forbid(unsafe_code)] -mod rpc; - -pub use self::rpc::*; +pub mod monerod; +pub mod wallet; diff --git a/monero-rpc/src/monerod.rs b/monero-rpc/src/monerod.rs new file mode 100644 index 00000000..9926d7ac --- /dev/null +++ b/monero-rpc/src/monerod.rs @@ -0,0 +1,60 @@ +use serde::Deserialize; + +#[jsonrpc_client::api(version = "2.0")] +pub trait MonerodRpc { + async fn generateblocks(&self, amount_of_blocks: u32, wallet_address: String) + -> GenerateBlocks; + async fn get_block_header_by_height(&self, height: u32) -> BlockHeader; + async fn get_block_count(&self) -> BlockCount; +} + +#[jsonrpc_client::implement(MonerodRpc)] +#[derive(Debug, Clone)] +pub struct Client { + inner: reqwest::Client, + base_url: reqwest::Url, +} + +impl Client { + /// New local host monerod RPC client. + pub fn localhost(port: u16) -> Self { + Self { + inner: reqwest::Client::new(), + base_url: format!("http://127.0.0.1:{}/json_rpc", port) + .parse() + .expect("url is well formed"), + } + } +} + +#[derive(Clone, Debug, Deserialize)] +pub struct GenerateBlocks { + pub blocks: Vec, + pub height: u32, + pub status: String, +} + +#[derive(Clone, Debug, Deserialize)] +pub struct BlockCount { + pub count: u32, + pub status: String, +} + +// We should be able to use monero-rs for this but it does not include all +// the fields. +#[derive(Clone, Debug, Deserialize)] +pub struct BlockHeader { + pub block_size: u32, + pub depth: u32, + pub difficulty: u32, + pub hash: String, + pub height: u32, + pub major_version: u32, + pub minor_version: u32, + pub nonce: u32, + pub num_txes: u32, + pub orphan_status: bool, + pub prev_hash: String, + pub reward: u64, + pub timestamp: u32, +} diff --git a/monero-rpc/src/rpc.rs b/monero-rpc/src/rpc.rs deleted file mode 100644 index e0e58ed7..00000000 --- a/monero-rpc/src/rpc.rs +++ /dev/null @@ -1,63 +0,0 @@ -//! JSON RPC clients for `monerd` and `monero-wallet-rpc`. -pub mod monerod; -pub mod wallet; - -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Debug, Clone)] -pub struct Request { - /// JSON RPC version, we hard cod this to 2.0. - jsonrpc: String, - /// Client controlled identifier, we hard code this to 1. - id: String, - /// The method to call. - method: String, - /// The method parameters. - params: T, -} - -/// JSON RPC request. -impl Request { - pub fn new(method: &str, params: T) -> Self { - Self { - jsonrpc: "2.0".to_owned(), - id: "1".to_owned(), - method: method.to_owned(), - params, - } - } -} - -/// JSON RPC response. -#[derive(Deserialize, Serialize, Debug, Clone)] -struct Response { - pub id: String, - pub jsonrpc: String, - pub result: T, -} - -#[cfg(test)] -mod tests { - use super::*; - - #[derive(Serialize, Debug, Clone)] - struct Params { - val: u32, - } - - #[test] - fn can_serialize_request_with_params() { - // Dummy method and parameters. - let params = Params { val: 0 }; - let method = "get_block"; - - let r = Request::new(method, ¶ms); - let got = serde_json::to_string(&r).expect("failed to serialize request"); - - let want = - "{\"jsonrpc\":\"2.0\",\"id\":\"1\",\"method\":\"get_block\",\"params\":{\"val\":0}}" - .to_string(); - - assert_eq!(got, want); - } -} diff --git a/monero-rpc/src/rpc/monerod.rs b/monero-rpc/src/rpc/monerod.rs deleted file mode 100644 index a98300a7..00000000 --- a/monero-rpc/src/rpc/monerod.rs +++ /dev/null @@ -1,154 +0,0 @@ -use crate::rpc::{Request, Response}; -use anyhow::Result; -use reqwest::Url; -use serde::{Deserialize, Serialize}; -use tracing::debug; - -/// RPC client for monerod and monero-wallet-rpc. -#[derive(Debug, Clone)] -pub struct Client { - pub inner: reqwest::Client, - pub url: Url, -} - -impl Client { - /// New local host monerod RPC client. - pub fn localhost(port: u16) -> Self { - let url = format!("http://127.0.0.1:{}/json_rpc", port); - let url = Url::parse(&url).expect("url is well formed"); - - Self { - inner: reqwest::Client::new(), - url, - } - } - - pub async fn generate_blocks( - &self, - amount_of_blocks: u32, - wallet_address: &str, - ) -> Result { - let params = GenerateBlocksParams { - amount_of_blocks, - wallet_address: wallet_address.to_owned(), - }; - let url = self.url.clone(); - // // Step 1: Get the auth header - // let res = self.inner.get(url.clone()).send().await?; - // let headers = res.headers(); - // let wwwauth = headers["www-authenticate"].to_str()?; - // - // // Step 2: Given the auth header, sign the digest for the real req. - // let tmp_url = url.clone(); - // let context = AuthContext::new("username", "password", tmp_url.path()); - // let mut prompt = digest_auth::parse(wwwauth)?; - // let answer = prompt.respond(&context)?.to_header_string(); - - let request = Request::new("generateblocks", params); - - let response = self - .inner - .post(url) - .json(&request) - .send() - .await? - .text() - .await?; - - debug!("generate blocks response: {}", response); - - let res: Response = serde_json::from_str(&response)?; - - Ok(res.result) - } - - // $ curl http://127.0.0.1:18081/json_rpc -d '{"jsonrpc":"2.0","id":"0","method":"get_block_header_by_height","params":{"height":1}}' -H 'Content-Type: application/json' - pub async fn get_block_header_by_height(&self, height: u32) -> Result { - let params = GetBlockHeaderByHeightParams { height }; - let request = Request::new("get_block_header_by_height", params); - - let response = self - .inner - .post(self.url.clone()) - .json(&request) - .send() - .await? - .text() - .await?; - - debug!("get block header by height response: {}", response); - - let res: Response = serde_json::from_str(&response)?; - - Ok(res.result.block_header) - } - - pub async fn get_block_count(&self) -> Result { - let request = Request::new("get_block_count", ""); - - let response = self - .inner - .post(self.url.clone()) - .json(&request) - .send() - .await? - .text() - .await?; - - debug!("get block count response: {}", response); - - let res: Response = serde_json::from_str(&response)?; - - Ok(res.result.count) - } -} - -#[derive(Clone, Debug, Serialize)] -struct GenerateBlocksParams { - amount_of_blocks: u32, - wallet_address: String, -} - -#[derive(Clone, Debug, Deserialize)] -pub struct GenerateBlocks { - pub blocks: Vec, - pub height: u32, - pub status: String, -} - -#[derive(Clone, Debug, Serialize)] -struct GetBlockHeaderByHeightParams { - height: u32, -} - -#[derive(Clone, Debug, Deserialize)] -struct GetBlockHeaderByHeight { - block_header: BlockHeader, - status: String, - untrusted: bool, -} - -#[derive(Clone, Debug, Deserialize)] -struct BlockCount { - count: u32, - status: String, -} - -// We should be able to use monero-rs for this but it does not include all -// the fields. -#[derive(Clone, Debug, Deserialize)] -pub struct BlockHeader { - pub block_size: u32, - pub depth: u32, - pub difficulty: u32, - pub hash: String, - pub height: u32, - pub major_version: u32, - pub minor_version: u32, - pub nonce: u32, - pub num_txes: u32, - pub orphan_status: bool, - pub prev_hash: String, - pub reward: u64, - pub timestamp: u32, -} diff --git a/monero-rpc/src/rpc/wallet.rs b/monero-rpc/src/rpc/wallet.rs deleted file mode 100644 index 675be036..00000000 --- a/monero-rpc/src/rpc/wallet.rs +++ /dev/null @@ -1,569 +0,0 @@ -use crate::rpc::{Request, Response}; -use anyhow::{bail, Result}; -use reqwest::Url; -use serde::{Deserialize, Serialize}; -use tracing::debug; - -/// JSON RPC client for monero-wallet-rpc. -#[derive(Debug, Clone)] -pub struct Client { - pub inner: reqwest::Client, - pub url: Url, -} - -impl Client { - /// Constructs a monero-wallet-rpc client with localhost endpoint. - pub fn localhost(port: u16) -> Self { - let url = format!("http://127.0.0.1:{}/json_rpc", port); - let url = Url::parse(&url).expect("url is well formed"); - - Client::new(url) - } - - /// Constructs a monero-wallet-rpc client with `url` endpoint. - pub fn new(url: Url) -> Self { - Self { - inner: reqwest::Client::new(), - url, - } - } - - /// Get addresses for account by index. - pub async fn get_address(&self, account_index: u32) -> Result { - let params = GetAddressParams { account_index }; - let request = Request::new("get_address", params); - - let response = self - .inner - .post(self.url.clone()) - .json(&request) - .send() - .await? - .text() - .await?; - - debug!("get address RPC response: {}", response); - - let r = serde_json::from_str::>(&response)?; - Ok(r.result) - } - - /// Gets the balance of account by index. - pub async fn get_balance(&self, index: u32) -> Result { - let params = GetBalanceParams { - account_index: index, - }; - let request = Request::new("get_balance", params); - - let response = self - .inner - .post(self.url.clone()) - .json(&request) - .send() - .await? - .text() - .await?; - - debug!( - "get balance of account index {} RPC response: {}", - index, response - ); - - let r = serde_json::from_str::>(&response)?; - - let balance = r.result.balance; - - Ok(balance) - } - - pub async fn create_account(&self, label: &str) -> Result { - let params = LabelParams { - label: label.to_owned(), - }; - let request = Request::new("create_account", params); - - let response = self - .inner - .post(self.url.clone()) - .json(&request) - .send() - .await? - .text() - .await?; - - debug!("create account RPC response: {}", response); - - let r = serde_json::from_str::>(&response)?; - Ok(r.result) - } - - /// Get accounts, filtered by tag ("" for no filtering). - pub async fn get_accounts(&self, tag: &str) -> Result { - let params = TagParams { - tag: tag.to_owned(), - }; - let request = Request::new("get_accounts", params); - - let response = self - .inner - .post(self.url.clone()) - .json(&request) - .send() - .await? - .text() - .await?; - - debug!("get accounts RPC response: {}", response); - - let r = serde_json::from_str::>(&response)?; - - Ok(r.result) - } - - /// Opens a wallet using `filename`. - pub async fn open_wallet(&self, filename: &str) -> Result<()> { - let params = OpenWalletParams { - filename: filename.to_owned(), - }; - let request = Request::new("open_wallet", params); - - let response = self - .inner - .post(self.url.clone()) - .json(&request) - .send() - .await? - .text() - .await?; - - debug!("open wallet RPC response: {}", response); - - // TODO: Proper error handling once switching to https://github.com/thomaseizinger/rust-jsonrpc-client/ - // Currently blocked by https://github.com/thomaseizinger/rust-jsonrpc-client/issues/20 - if response.contains("error") { - bail!("Failed to open wallet") - } - - Ok(()) - } - - /// Close the currently opened wallet, after trying to save it. - pub async fn close_wallet(&self) -> Result<()> { - let request = Request::new("close_wallet", ""); - - let response = self - .inner - .post(self.url.clone()) - .json(&request) - .send() - .await? - .text() - .await?; - - debug!("close wallet RPC response: {}", response); - - if response.contains("error") { - bail!("Failed to close wallet") - } - - Ok(()) - } - - /// Creates a wallet using `filename`. - pub async fn create_wallet(&self, filename: &str) -> Result<()> { - let params = CreateWalletParams { - filename: filename.to_owned(), - language: "English".to_owned(), - }; - let request = Request::new("create_wallet", params); - - let response = self - .inner - .post(self.url.clone()) - .json(&request) - .send() - .await? - .text() - .await?; - - debug!("create wallet RPC response: {}", response); - - if response.contains("error") { - bail!("Failed to create wallet") - } - - Ok(()) - } - - /// Transfers `amount` moneroj from `account_index` to `address`. - pub async fn transfer( - &self, - account_index: u32, - amount: u64, - address: &str, - ) -> Result { - let dest = vec![Destination { - amount, - address: address.to_owned(), - }]; - self.multi_transfer(account_index, dest).await - } - - /// Transfers moneroj from `account_index` to `destinations`. - pub async fn multi_transfer( - &self, - account_index: u32, - destinations: Vec, - ) -> Result { - let params = TransferParams { - account_index, - destinations, - get_tx_key: true, - }; - let request = Request::new("transfer", params); - - let response = self - .inner - .post(self.url.clone()) - .json(&request) - .send() - .await? - .text() - .await?; - - debug!("transfer RPC response: {}", response); - - let r = serde_json::from_str::>(&response)?; - Ok(r.result) - } - - /// Get wallet block height, this might be behind monerod height. - pub async fn block_height(&self) -> Result { - let request = Request::new("get_height", ""); - - let response = self - .inner - .post(self.url.clone()) - .json(&request) - .send() - .await? - .text() - .await?; - - debug!("wallet height RPC response: {}", response); - - let r = serde_json::from_str::>(&response)?; - Ok(r.result) - } - - /// Check a transaction in the blockchain with its secret key. - pub async fn check_tx_key( - &self, - tx_id: &str, - tx_key: &str, - address: &str, - ) -> Result { - let params = CheckTxKeyParams { - tx_id: tx_id.to_owned(), - tx_key: tx_key.to_owned(), - address: address.to_owned(), - }; - let request = Request::new("check_tx_key", params); - - let response = self - .inner - .post(self.url.clone()) - .json(&request) - .send() - .await? - .text() - .await?; - - debug!("check_tx_key RPC response: {}", response); - - let check_tx_key = serde_json::from_str::>(&response)?; - let mut check_tx_key = check_tx_key.result; - - // Due to a bug in monerod that causes check_tx_key confirmations - // to overflow we safeguard the confirmations to avoid unwanted - // side effects. - if check_tx_key.confirmations > u64::MAX - 1000 { - check_tx_key.confirmations = 0u64; - } - - Ok(check_tx_key) - } - - pub async fn generate_from_keys( - &self, - filename: &str, - address: &str, - spend_key: &str, - view_key: &str, - restore_height: u32, - ) -> Result { - let params = GenerateFromKeysParams { - restore_height, - filename: filename.into(), - address: address.into(), - spendkey: spend_key.into(), - viewkey: view_key.into(), - password: "".into(), - autosave_current: true, - }; - let request = Request::new("generate_from_keys", params); - - let response = self - .inner - .post(self.url.clone()) - .json(&request) - .send() - .await? - .text() - .await?; - - debug!("generate_from_keys RPC response: {}", response); - - let r = serde_json::from_str::>(&response)?; - Ok(r.result) - } - - pub async fn refresh(&self) -> Result { - let request = Request::new("refresh", ""); - - let response = self - .inner - .post(self.url.clone()) - .json(&request) - .send() - .await? - .text() - .await?; - - debug!("refresh RPC response: {}", response); - - let r = serde_json::from_str::>(&response)?; - Ok(r.result) - } - - /// Transfers the complete balance of the account to `address`. - pub async fn sweep_all(&self, address: &str) -> Result { - let params = SweepAllParams { - address: address.into(), - }; - let request = Request::new("sweep_all", params); - - let response = self - .inner - .post(self.url.clone()) - .json(&request) - .send() - .await? - .text() - .await?; - - debug!("sweep_all RPC response: {}", response); - - let r = serde_json::from_str::>(&response)?; - Ok(r.result) - } - - pub async fn get_version(&self) -> Result { - let request = Request::new("get_version", ""); - - let response = self - .inner - .post(self.url.clone()) - .json(&request) - .send() - .await? - .text() - .await?; - - debug!("get_version RPC response: {}", response); - - let r = serde_json::from_str::>(&response)?; - Ok(r.result) - } -} - -#[derive(Serialize, Debug, Clone)] -struct GetAddressParams { - account_index: u32, -} - -#[derive(Deserialize, Debug, Clone)] -pub struct GetAddress { - pub address: String, -} - -#[derive(Serialize, Debug, Clone)] -struct GetBalanceParams { - account_index: u32, -} - -#[derive(Deserialize, Debug, Clone)] -struct GetBalance { - balance: u64, - blocks_to_unlock: u32, - multisig_import_needed: bool, - time_to_unlock: u32, - unlocked_balance: u64, -} - -#[derive(Serialize, Debug, Clone)] -struct LabelParams { - label: String, -} - -#[derive(Deserialize, Debug, Clone)] -pub struct CreateAccount { - pub account_index: u32, - pub address: String, -} - -#[derive(Serialize, Debug, Clone)] -struct TagParams { - tag: String, -} - -#[derive(Deserialize, Debug, Clone)] -pub struct GetAccounts { - pub subaddress_accounts: Vec, - pub total_balance: u64, - pub total_unlocked_balance: u64, -} - -#[derive(Deserialize, Debug, Clone)] -pub struct SubAddressAccount { - pub account_index: u32, - pub balance: u32, - pub base_address: String, - pub label: String, - pub tag: String, - pub unlocked_balance: u64, -} - -#[derive(Serialize, Debug, Clone)] -struct OpenWalletParams { - filename: String, -} - -#[derive(Serialize, Debug, Clone)] -struct CreateWalletParams { - filename: String, - language: String, -} - -#[derive(Serialize, Debug, Clone)] -struct TransferParams { - // Transfer from this account. - account_index: u32, - // Destinations to receive XMR: - destinations: Vec, - // Return the transaction key after sending. - get_tx_key: bool, -} - -#[derive(Serialize, Debug, Clone)] -pub struct Destination { - amount: u64, - address: String, -} - -#[derive(Deserialize, Debug, Clone)] -pub struct Transfer { - pub amount: u64, - pub fee: u64, - pub multisig_txset: String, - pub tx_blob: String, - pub tx_hash: String, - pub tx_key: String, - pub tx_metadata: String, - pub unsigned_txset: String, -} - -#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq)] -pub struct BlockHeight { - pub height: u32, -} - -#[derive(Serialize, Debug, Clone)] -struct CheckTxKeyParams { - #[serde(rename = "txid")] - tx_id: String, - tx_key: String, - address: String, -} - -#[derive(Clone, Copy, Debug, Deserialize)] -pub struct CheckTxKey { - pub confirmations: u64, - pub received: u64, -} - -#[derive(Clone, Debug, Serialize)] -pub struct GenerateFromKeysParams { - pub restore_height: u32, - pub filename: String, - pub address: String, - pub spendkey: String, - pub viewkey: String, - pub password: String, - pub autosave_current: bool, -} - -#[derive(Clone, Debug, Deserialize)] -pub struct GenerateFromKeys { - pub address: String, - pub info: String, -} - -#[derive(Clone, Copy, Debug, Deserialize)] -pub struct Refreshed { - pub blocks_fetched: u32, - pub received_money: bool, -} - -#[derive(Debug, Clone, Serialize)] -pub struct SweepAllParams { - pub address: String, -} - -#[derive(Debug, Clone, Deserialize)] -pub struct SweepAll { - amount_list: Vec, - fee_list: Vec, - multisig_txset: String, - pub tx_hash_list: Vec, - unsigned_txset: String, - weight_list: Vec, -} - -#[derive(Debug, Copy, Clone, Deserialize)] -pub struct Version { - version: u32, -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn can_deserialize_sweep_all_response() { - let response = r#"{ - "id": "0", - "jsonrpc": "2.0", - "result": { - "amount_list": [29921410000], - "fee_list": [78590000], - "multisig_txset": "", - "tx_hash_list": ["c1d8cfa87d445c1915a59d67be3e93ba8a29018640cf69b465f07b1840a8f8c8"], - "unsigned_txset": "", - "weight_list": [1448] - } - }"#; - - let _: Response = serde_json::from_str(&response).unwrap(); - } -} diff --git a/monero-rpc/src/wallet.rs b/monero-rpc/src/wallet.rs new file mode 100644 index 00000000..67d7ce21 --- /dev/null +++ b/monero-rpc/src/wallet.rs @@ -0,0 +1,258 @@ +use anyhow::Result; +use serde::{de::Error, Deserialize, Deserializer, Serialize}; + +#[jsonrpc_client::api(version = "2.0")] +pub trait MoneroWalletRpc { + async fn get_address(&self, account_index: u32) -> GetAddress; + async fn get_balance(&self, account_index: u32) -> GetBalance; + async fn create_account(&self, label: String) -> CreateAccount; + async fn get_accounts(&self, tag: String) -> GetAccounts; + async fn open_wallet(&self, filename: String) -> WalletOpened; + async fn close_wallet(&self) -> WalletClosed; + async fn create_wallet(&self, filename: String, language: String) -> WalletCreated; + async fn transfer( + &self, + account_index: u32, + destinations: Vec, + get_tx_key: bool, + ) -> Transfer; + async fn get_height(&self) -> BlockHeight; + async fn check_tx_key(&self, txid: String, tx_key: String, address: String) -> CheckTxKey; + #[allow(clippy::too_many_arguments)] + async fn generate_from_keys( + &self, + filename: String, + address: String, + spendkey: String, + viewkey: String, + restore_height: u32, + password: String, + autosave_current: bool, + ) -> GenerateFromKeys; + async fn refresh(&self) -> Refreshed; + async fn sweep_all(&self, address: String) -> SweepAll; + async fn get_version(&self) -> Version; +} + +#[jsonrpc_client::implement(MoneroWalletRpc)] +#[derive(Debug, Clone)] +pub struct Client { + inner: reqwest::Client, + base_url: reqwest::Url, +} + +impl Client { + /// Constructs a monero-wallet-rpc client with localhost endpoint. + pub fn localhost(port: u16) -> Self { + Client::new( + format!("http://127.0.0.1:{}/json_rpc", port) + .parse() + .expect("url is well formed"), + ) + } + + /// Constructs a monero-wallet-rpc client with `url` endpoint. + pub fn new(url: reqwest::Url) -> Self { + Self { + inner: reqwest::Client::new(), + base_url: url, + } + } + + /// Transfers `amount` monero from `account_index` to `address`. + pub async fn transfer_single( + &self, + account_index: u32, + amount: u64, + address: &str, + ) -> Result { + let dest = vec![Destination { + amount, + address: address.to_owned(), + }]; + + Ok(self.transfer(account_index, dest, true).await?) + } +} + +#[derive(Deserialize, Debug, Clone)] +pub struct GetAddress { + pub address: String, +} + +#[derive(Deserialize, Debug, Clone, Copy)] +pub struct GetBalance { + pub balance: u64, + pub blocks_to_unlock: u32, + pub multisig_import_needed: bool, + pub time_to_unlock: u32, + pub unlocked_balance: u64, +} + +#[derive(Deserialize, Debug, Clone)] +pub struct CreateAccount { + pub account_index: u32, + pub address: String, +} + +#[derive(Deserialize, Debug, Clone)] +pub struct GetAccounts { + pub subaddress_accounts: Vec, + pub total_balance: u64, + pub total_unlocked_balance: u64, +} + +#[derive(Deserialize, Debug, Clone)] +pub struct SubAddressAccount { + pub account_index: u32, + pub balance: u32, + pub base_address: String, + pub label: String, + pub tag: String, + pub unlocked_balance: u64, +} + +#[derive(Serialize, Debug, Clone)] +pub struct Destination { + pub amount: u64, + pub address: String, +} + +#[derive(Deserialize, Debug, Clone)] +pub struct Transfer { + pub amount: u64, + pub fee: u64, + pub multisig_txset: String, + pub tx_blob: String, + pub tx_hash: String, + #[serde(deserialize_with = "opt_key_from_blank")] + pub tx_key: Option, + pub tx_metadata: String, + pub unsigned_txset: String, +} + +#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq)] +pub struct BlockHeight { + pub height: u32, +} + +#[derive(Clone, Copy, Debug, Deserialize)] +#[serde(from = "CheckTxKeyResponse")] +pub struct CheckTxKey { + pub confirmations: u64, + pub received: u64, +} + +#[derive(Clone, Copy, Debug, Deserialize)] +struct CheckTxKeyResponse { + pub confirmations: u64, + pub received: u64, +} + +impl From for CheckTxKey { + fn from(response: CheckTxKeyResponse) -> Self { + // Due to a bug in monerod that causes check_tx_key confirmations + // to overflow we safeguard the confirmations to avoid unwanted + // side effects. + let confirmations = if response.confirmations > u64::MAX - 1000 { + 0 + } else { + response.confirmations + }; + + CheckTxKey { + confirmations, + received: response.received, + } + } +} + +#[derive(Clone, Debug, Deserialize)] +pub struct GenerateFromKeys { + pub address: String, + pub info: String, +} + +#[derive(Clone, Copy, Debug, Deserialize)] +pub struct Refreshed { + pub blocks_fetched: u32, + pub received_money: bool, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct SweepAll { + amount_list: Vec, + fee_list: Vec, + multisig_txset: String, + pub tx_hash_list: Vec, + unsigned_txset: String, + weight_list: Vec, +} + +#[derive(Debug, Copy, Clone, Deserialize)] +pub struct Version { + pub version: u32, +} + +pub type WalletCreated = Empty; +pub type WalletClosed = Empty; +pub type WalletOpened = Empty; + +/// Zero-sized struct to allow serde to deserialize an empty JSON object. +/// +/// With `serde`, an empty JSON object (`{ }`) does not deserialize into Rust's +/// `()`. With the adoption of `jsonrpc_client`, we need to be explicit about +/// what the response of every RPC call is. Unfortunately, monerod likes to +/// return empty objects instead of `null`s in certain cases. We use this struct +/// to all the "deserialization" to happily continue. +#[derive(Debug, Copy, Clone, Deserialize)] +pub struct Empty {} + +fn opt_key_from_blank<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let string = String::deserialize(deserializer)?; + + if string.is_empty() { + return Ok(None); + } + + Ok(Some(string.parse().map_err(D::Error::custom)?)) +} + +#[cfg(test)] +mod tests { + use super::*; + use jsonrpc_client::Response; + + #[test] + fn can_deserialize_sweep_all_response() { + let response = r#"{ + "id": "0", + "jsonrpc": "2.0", + "result": { + "amount_list": [29921410000], + "fee_list": [78590000], + "multisig_txset": "", + "tx_hash_list": ["c1d8cfa87d445c1915a59d67be3e93ba8a29018640cf69b465f07b1840a8f8c8"], + "unsigned_txset": "", + "weight_list": [1448] + } + }"#; + + let _: Response = serde_json::from_str(&response).unwrap(); + } + + #[test] + fn can_deserialize_create_wallet() { + let response = r#"{ + "id": 0, + "jsonrpc": "2.0", + "result": { + } + }"#; + + let _: Response = serde_json::from_str(&response).unwrap(); + } +} diff --git a/swap/src/monero/wallet.rs b/swap/src/monero/wallet.rs index b3561281..3f9cf9d4 100644 --- a/swap/src/monero/wallet.rs +++ b/swap/src/monero/wallet.rs @@ -5,7 +5,7 @@ use crate::monero::{ use ::monero::{Address, Network, PrivateKey, PublicKey}; use anyhow::{Context, Result}; use monero_rpc::wallet; -use monero_rpc::wallet::{BlockHeight, CheckTxKey, Refreshed}; +use monero_rpc::wallet::{BlockHeight, CheckTxKey, MoneroWalletRpc as _, Refreshed}; use std::future::Future; use std::str::FromStr; use std::time::Duration; @@ -27,9 +27,9 @@ impl Wallet { pub async fn open_or_create(url: Url, name: String, env_config: Config) -> Result { let client = wallet::Client::new(url); - let open_wallet_response = client.open_wallet(name.as_str()).await; + let open_wallet_response = client.open_wallet(name.clone()).await; if open_wallet_response.is_err() { - client.create_wallet(name.as_str()).await.context( + client.create_wallet(name.clone(), "English".to_owned()).await.context( "Unable to create Monero wallet, please ensure that the monero-wallet-rpc is available", )?; @@ -59,12 +59,12 @@ impl Wallet { self.inner .lock() .await - .open_wallet(self.name.as_str()) + .open_wallet(self.name.clone()) .await?; Ok(()) } - pub async fn open(&self, filename: &str) -> Result<()> { + pub async fn open(&self, filename: String) -> Result<()> { self.inner.lock().await.open_wallet(filename).await?; Ok(()) } @@ -73,7 +73,7 @@ impl Wallet { /// keys. The generated wallet will remain loaded. pub async fn create_from_and_load( &self, - file_name: &str, + file_name: String, private_spend_key: PrivateKey, private_view_key: PrivateViewKey, restore_height: BlockHeight, @@ -87,17 +87,23 @@ impl Wallet { // Properly close the wallet before generating the other wallet to ensure that // it saves its state correctly - let _ = wallet.close_wallet().await?; + let _ = wallet + .close_wallet() + .await + .context("Failed to close wallet")?; let _ = wallet .generate_from_keys( file_name, - &address.to_string(), - &private_spend_key.to_string(), - &PrivateKey::from(private_view_key).to_string(), + address.to_string(), + private_spend_key.to_string(), + PrivateKey::from(private_view_key).to_string(), restore_height.height, + String::from(""), + true, ) - .await?; + .await + .context("Failed to generate new wallet from keys")?; Ok(()) } @@ -108,7 +114,7 @@ impl Wallet { /// stored name. pub async fn create_from( &self, - file_name: &str, + file_name: String, private_spend_key: PrivateKey, private_view_key: PrivateViewKey, restore_height: BlockHeight, @@ -128,19 +134,18 @@ impl Wallet { let _ = wallet .generate_from_keys( file_name, - &temp_wallet_address.to_string(), - &private_spend_key.to_string(), - &PrivateKey::from(private_view_key).to_string(), + temp_wallet_address.to_string(), + private_spend_key.to_string(), + PrivateKey::from(private_view_key).to_string(), restore_height.height, + String::from(""), + true, ) .await?; // Try to send all the funds from the generated wallet to the default wallet match wallet.refresh().await { - Ok(_) => match wallet - .sweep_all(self.main_address.to_string().as_str()) - .await - { + Ok(_) => match wallet.sweep_all(self.main_address.to_string()).await { Ok(sweep_all) => { for tx in sweep_all.tx_hash_list { tracing::info!(%tx, "Monero transferred back to default wallet {}", self.main_address); @@ -159,7 +164,7 @@ impl Wallet { } } - let _ = wallet.open_wallet(self.name.as_str()).await?; + let _ = wallet.open_wallet(self.name.clone()).await?; Ok(()) } @@ -178,7 +183,7 @@ impl Wallet { .inner .lock() .await - .transfer(0, amount.as_piconero(), &destination_address.to_string()) + .transfer_single(0, amount.as_piconero(), &destination_address.to_string()) .await?; tracing::debug!( @@ -190,7 +195,8 @@ impl Wallet { Ok(TransferProof::new( TxHash(res.tx_hash), - PrivateKey::from_str(&res.tx_key)?, + res.tx_key + .context("Missing tx_key in `transfer` response")?, )) } @@ -210,16 +216,20 @@ impl Wallet { let address = Address::standard(self.network, public_spend_key, public_view_key.into()); let check_interval = tokio::time::interval(self.sync_interval); - let key = &transfer_proof.tx_key().to_string(); + let key = transfer_proof.tx_key().to_string(); wait_for_confirmations( txid.0, - |txid| async move { - self.inner - .lock() - .await - .check_tx_key(&txid, &key, &address.to_string()) - .await + move |txid| { + let key = key.clone(); + async move { + Ok(self + .inner + .lock() + .await + .check_tx_key(txid, key, address.to_string()) + .await?) + } }, check_interval, expected, @@ -235,7 +245,7 @@ impl Wallet { .inner .lock() .await - .sweep_all(address.to_string().as_str()) + .sweep_all(address.to_string()) .await?; let tx_hashes = sweep_all.tx_hash_list.into_iter().map(TxHash).collect(); @@ -244,13 +254,13 @@ impl Wallet { /// Get the balance of the primary account. pub async fn get_balance(&self) -> Result { - let amount = self.inner.lock().await.get_balance(0).await?; + let amount = self.inner.lock().await.get_balance(0).await?.balance; Ok(Amount::from_piconero(amount)) } pub async fn block_height(&self) -> Result { - self.inner.lock().await.block_height().await + Ok(self.inner.lock().await.get_height().await?) } pub fn get_main_address(&self) -> Address { @@ -258,7 +268,7 @@ impl Wallet { } pub async fn refresh(&self) -> Result { - self.inner.lock().await.refresh().await + Ok(self.inner.lock().await.refresh().await?) } pub fn static_tx_fee_estimate(&self) -> Amount { diff --git a/swap/src/monero/wallet_rpc.rs b/swap/src/monero/wallet_rpc.rs index 96138098..a2aa9582 100644 --- a/swap/src/monero/wallet_rpc.rs +++ b/swap/src/monero/wallet_rpc.rs @@ -2,7 +2,7 @@ use ::monero::Network; use anyhow::{Context, Result}; use big_bytes::BigByte; use futures::{StreamExt, TryStreamExt}; -use monero_rpc::wallet::Client; +use monero_rpc::wallet::{Client, MoneroWalletRpc as _}; use reqwest::header::CONTENT_LENGTH; use reqwest::Url; use std::io::ErrorKind; diff --git a/swap/src/protocol/alice/swap.rs b/swap/src/protocol/alice/swap.rs index 57e66a9f..7e1b8e20 100644 --- a/swap/src/protocol/alice/swap.rs +++ b/swap/src/protocol/alice/swap.rs @@ -104,7 +104,13 @@ async fn next_state( ExpiredTimelocks::None => { monero_wallet .watch_for_transfer(state3.lock_xmr_watch_request(transfer_proof.clone(), 1)) - .await?; + .await + .with_context(|| { + format!( + "Failed to watch for transfer of XMR in transaction {}", + transfer_proof.tx_hash() + ) + })?; AliceState::XmrLocked { monero_wallet_restore_blockheight, @@ -299,7 +305,7 @@ async fn next_state( monero_wallet .create_from( - &swap_id.to_string(), + swap_id.to_string(), spend_key, view_key, monero_wallet_restore_blockheight, diff --git a/swap/src/protocol/bob/swap.rs b/swap/src/protocol/bob/swap.rs index 84b8257f..0702f76e 100644 --- a/swap/src/protocol/bob/swap.rs +++ b/swap/src/protocol/bob/swap.rs @@ -197,21 +197,24 @@ async fn next_state( BobState::BtcRedeemed(state) => { let (spend_key, view_key) = state.xmr_keys(); - let generated_wallet_file_name = &swap_id.to_string(); - if monero_wallet + let generated_wallet_file_name = swap_id.to_string(); + if let Err(e) = monero_wallet .create_from_and_load( - generated_wallet_file_name, + generated_wallet_file_name.clone(), spend_key, view_key, state.monero_wallet_restore_blockheight, ) .await - .is_err() { // In case we failed to refresh/sweep, when resuming the wallet might already // exist! This is a very unlikely scenario, but if we don't take care of it we // might not be able to ever transfer the Monero. - tracing::warn!("Failed to generate monero wallet from keys, falling back to trying to open the the wallet if it already exists: {}", swap_id); + tracing::warn!("Failed to generate monero wallet from keys: {:#}", e); + tracing::info!( + "Falling back to trying to open the the wallet if it already exists: {}", + swap_id + ); monero_wallet.open(generated_wallet_file_name).await?; } From 881913ad9cb3f68ae3a467b70563a38bc0526a67 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 16 Apr 2021 11:12:27 +1000 Subject: [PATCH 02/15] Re-order content of harness/mod.rs in a sane way Most important things come first, remaining items are defined roughly in the order they are used by the top level components. --- swap/tests/harness/mod.rs | 604 +++++++++++++++++++------------------- 1 file changed, 302 insertions(+), 302 deletions(-) diff --git a/swap/tests/harness/mod.rs b/swap/tests/harness/mod.rs index e23801b4..baf969c1 100644 --- a/swap/tests/harness/mod.rs +++ b/swap/tests/harness/mod.rs @@ -36,6 +36,308 @@ use tracing_subscriber::util::SubscriberInitExt; use url::Url; use uuid::Uuid; +pub async fn setup_test(_config: C, testfn: T) +where + T: Fn(TestContext) -> F, + F: Future>, + C: GetConfig, +{ + let cli = Cli::default(); + + let _guard = tracing_subscriber::fmt() + .with_env_filter("warn,swap=debug,monero_harness=debug,monero_rpc=debug,bitcoin_harness=info,testcontainers=info") + .with_test_writer() + .set_default(); + + let env_config = C::get_config(); + + let (monero, containers) = harness::init_containers(&cli).await; + monero.init_miner().await.unwrap(); + + let btc_amount = bitcoin::Amount::from_sat(1_000_000); + let xmr_amount = monero::Amount::from_monero(btc_amount.as_btc() / FixedRate::RATE).unwrap(); + + let alice_starting_balances = + StartingBalances::new(bitcoin::Amount::ZERO, xmr_amount, Some(10)); + + let electrs_rpc_port = containers + .electrs + .get_host_port(harness::electrs::RPC_PORT) + .expect("Could not map electrs rpc port"); + + let alice_seed = Seed::random().unwrap(); + let (alice_bitcoin_wallet, alice_monero_wallet) = init_test_wallets( + MONERO_WALLET_NAME_ALICE, + containers.bitcoind_url.clone(), + &monero, + alice_starting_balances.clone(), + tempdir().unwrap().path(), + electrs_rpc_port, + &alice_seed, + env_config, + ) + .await; + + let alice_listen_port = get_port().expect("Failed to find a free port"); + let alice_listen_address: Multiaddr = format!("/ip4/127.0.0.1/tcp/{}", alice_listen_port) + .parse() + .expect("failed to parse Alice's address"); + + let alice_db_path = tempdir().unwrap().into_path(); + let (alice_handle, alice_swap_handle) = start_alice( + &alice_seed, + alice_db_path.clone(), + alice_listen_address.clone(), + env_config, + alice_bitcoin_wallet.clone(), + alice_monero_wallet.clone(), + ); + + let bob_seed = Seed::random().unwrap(); + let bob_starting_balances = StartingBalances::new(btc_amount * 10, monero::Amount::ZERO, None); + + let (bob_bitcoin_wallet, bob_monero_wallet) = init_test_wallets( + MONERO_WALLET_NAME_BOB, + containers.bitcoind_url, + &monero, + bob_starting_balances.clone(), + tempdir().unwrap().path(), + electrs_rpc_port, + &bob_seed, + env_config, + ) + .await; + + let bob_params = BobParams { + seed: Seed::random().unwrap(), + db_path: tempdir().unwrap().path().to_path_buf(), + bitcoin_wallet: bob_bitcoin_wallet.clone(), + monero_wallet: bob_monero_wallet.clone(), + alice_address: alice_listen_address.clone(), + alice_peer_id: alice_handle.peer_id, + env_config, + }; + + monero.start_miner().await.unwrap(); + + let test = TestContext { + env_config, + btc_amount, + xmr_amount, + alice_seed, + alice_db_path, + alice_listen_address, + alice_starting_balances, + alice_bitcoin_wallet, + alice_monero_wallet, + alice_swap_handle, + alice_handle, + bob_params, + bob_starting_balances, + bob_bitcoin_wallet, + bob_monero_wallet, + }; + + testfn(test).await.unwrap() +} + +async fn init_containers(cli: &Cli) -> (Monero, Containers<'_>) { + let prefix = random_prefix(); + let bitcoind_name = format!("{}_{}", prefix, "bitcoind"); + let (bitcoind, bitcoind_url) = + init_bitcoind_container(&cli, prefix.clone(), bitcoind_name.clone(), prefix.clone()) + .await + .expect("could not init bitcoind"); + let electrs = init_electrs_container(&cli, prefix.clone(), bitcoind_name, prefix) + .await + .expect("could not init electrs"); + let (monero, monerods) = init_monero_container(&cli).await; + (monero, Containers { + bitcoind_url, + bitcoind, + monerods, + electrs, + }) +} + +async fn init_bitcoind_container( + cli: &Cli, + volume: String, + name: String, + network: String, +) -> Result<(Container<'_, Cli, bitcoind::Bitcoind>, Url)> { + let image = bitcoind::Bitcoind::default().with_volume(volume); + + let run_args = RunArgs::default().with_name(name).with_network(network); + + let docker = cli.run_with_args(image, run_args); + let a = docker + .get_host_port(harness::bitcoind::RPC_PORT) + .context("Could not map bitcoind rpc port")?; + + let bitcoind_url = { + let input = format!( + "http://{}:{}@localhost:{}", + bitcoind::RPC_USER, + bitcoind::RPC_PASSWORD, + a + ); + Url::parse(&input).unwrap() + }; + + init_bitcoind(bitcoind_url.clone(), 5).await?; + + Ok((docker, bitcoind_url.clone())) +} + +async fn init_monero_container( + cli: &Cli, +) -> ( + Monero, + Vec>, +) { + let (monero, monerods) = Monero::new(&cli, vec![ + MONERO_WALLET_NAME_ALICE.to_owned(), + MONERO_WALLET_NAME_BOB.to_owned(), + ]) + .await + .unwrap(); + + (monero, monerods) +} + +pub async fn init_electrs_container( + cli: &Cli, + volume: String, + bitcoind_container_name: String, + network: String, +) -> Result> { + let bitcoind_rpc_addr = format!( + "{}:{}", + bitcoind_container_name, + harness::bitcoind::RPC_PORT + ); + let image = electrs::Electrs::default() + .with_volume(volume) + .with_daemon_rpc_addr(bitcoind_rpc_addr) + .with_tag("latest"); + + let run_args = RunArgs::default().with_network(network); + + let docker = cli.run_with_args(image, run_args); + + Ok(docker) +} + +fn start_alice( + seed: &Seed, + db_path: PathBuf, + listen_address: Multiaddr, + env_config: Config, + bitcoin_wallet: Arc, + monero_wallet: Arc, +) -> (AliceApplicationHandle, Receiver) { + let db = Arc::new(Database::open(db_path.as_path()).unwrap()); + + let mut swarm = swarm::alice(&seed).unwrap(); + swarm.listen_on(listen_address).unwrap(); + + let (event_loop, swap_handle) = alice::EventLoop::new( + swarm, + env_config, + bitcoin_wallet, + monero_wallet, + db, + FixedRate::default(), + bitcoin::Amount::ONE_BTC, + ) + .unwrap(); + + let peer_id = event_loop.peer_id(); + let handle = tokio::spawn(event_loop.run()); + + (AliceApplicationHandle { handle, peer_id }, swap_handle) +} + +#[allow(clippy::too_many_arguments)] +async fn init_test_wallets( + name: &str, + bitcoind_url: Url, + monero: &Monero, + starting_balances: StartingBalances, + datadir: &Path, + electrum_rpc_port: u16, + seed: &Seed, + env_config: Config, +) -> (Arc, Arc) { + monero + .init_wallet( + name, + starting_balances + .xmr_outputs + .into_iter() + .map(|amount| amount.as_piconero()) + .collect(), + ) + .await + .unwrap(); + + let xmr_wallet = swap::monero::Wallet::connect( + monero.wallet(name).unwrap().client(), + name.to_string(), + env_config, + ) + .await + .unwrap(); + + let electrum_rpc_url = { + let input = format!("tcp://@localhost:{}", electrum_rpc_port); + Url::parse(&input).unwrap() + }; + + let btc_wallet = swap::bitcoin::Wallet::new( + electrum_rpc_url, + datadir, + seed.derive_extended_private_key(env_config.bitcoin_network) + .expect("Could not create extended private key from seed"), + env_config, + ) + .await + .expect("could not init btc wallet"); + + if starting_balances.btc != bitcoin::Amount::ZERO { + mint( + bitcoind_url, + btc_wallet.new_address().await.unwrap(), + starting_balances.btc, + ) + .await + .expect("could not mint btc starting balance"); + + let mut interval = interval(Duration::from_secs(1u64)); + let mut retries = 0u8; + let max_retries = 30u8; + loop { + retries += 1; + btc_wallet.sync().await.unwrap(); + + let btc_balance = btc_wallet.balance().await.unwrap(); + + if btc_balance == starting_balances.btc { + break; + } else if retries == max_retries { + panic!( + "Bitcoin wallet initialization failed, reached max retries upon balance sync" + ) + } + + interval.tick().await; + } + } + + (Arc::new(btc_wallet), Arc::new(xmr_wallet)) +} + const MONERO_WALLET_NAME_BOB: &str = "bob"; const MONERO_WALLET_NAME_ALICE: &str = "alice"; const BITCOIN_TEST_WALLET_NAME: &str = "testwallet"; @@ -528,141 +830,6 @@ impl Wallet for bitcoin::Wallet { } } -pub async fn setup_test(_config: C, testfn: T) -where - T: Fn(TestContext) -> F, - F: Future>, - C: GetConfig, -{ - let cli = Cli::default(); - - let _guard = tracing_subscriber::fmt() - .with_env_filter("warn,swap=debug,monero_harness=debug,monero_rpc=debug,bitcoin_harness=info,testcontainers=info") - .with_test_writer() - .set_default(); - - let env_config = C::get_config(); - - let (monero, containers) = harness::init_containers(&cli).await; - monero.init_miner().await.unwrap(); - - let btc_amount = bitcoin::Amount::from_sat(1_000_000); - let xmr_amount = monero::Amount::from_monero(btc_amount.as_btc() / FixedRate::RATE).unwrap(); - - let alice_starting_balances = - StartingBalances::new(bitcoin::Amount::ZERO, xmr_amount, Some(10)); - - let electrs_rpc_port = containers - .electrs - .get_host_port(harness::electrs::RPC_PORT) - .expect("Could not map electrs rpc port"); - - let alice_seed = Seed::random().unwrap(); - let (alice_bitcoin_wallet, alice_monero_wallet) = init_test_wallets( - MONERO_WALLET_NAME_ALICE, - containers.bitcoind_url.clone(), - &monero, - alice_starting_balances.clone(), - tempdir().unwrap().path(), - electrs_rpc_port, - &alice_seed, - env_config, - ) - .await; - - let alice_listen_port = get_port().expect("Failed to find a free port"); - let alice_listen_address: Multiaddr = format!("/ip4/127.0.0.1/tcp/{}", alice_listen_port) - .parse() - .expect("failed to parse Alice's address"); - - let alice_db_path = tempdir().unwrap().into_path(); - let (alice_handle, alice_swap_handle) = start_alice( - &alice_seed, - alice_db_path.clone(), - alice_listen_address.clone(), - env_config, - alice_bitcoin_wallet.clone(), - alice_monero_wallet.clone(), - ); - - let bob_seed = Seed::random().unwrap(); - let bob_starting_balances = StartingBalances::new(btc_amount * 10, monero::Amount::ZERO, None); - - let (bob_bitcoin_wallet, bob_monero_wallet) = init_test_wallets( - MONERO_WALLET_NAME_BOB, - containers.bitcoind_url, - &monero, - bob_starting_balances.clone(), - tempdir().unwrap().path(), - electrs_rpc_port, - &bob_seed, - env_config, - ) - .await; - - let bob_params = BobParams { - seed: Seed::random().unwrap(), - db_path: tempdir().unwrap().path().to_path_buf(), - bitcoin_wallet: bob_bitcoin_wallet.clone(), - monero_wallet: bob_monero_wallet.clone(), - alice_address: alice_listen_address.clone(), - alice_peer_id: alice_handle.peer_id, - env_config, - }; - - monero.start_miner().await.unwrap(); - - let test = TestContext { - env_config, - btc_amount, - xmr_amount, - alice_seed, - alice_db_path, - alice_listen_address, - alice_starting_balances, - alice_bitcoin_wallet, - alice_monero_wallet, - alice_swap_handle, - alice_handle, - bob_params, - bob_starting_balances, - bob_bitcoin_wallet, - bob_monero_wallet, - }; - - testfn(test).await.unwrap() -} - -fn start_alice( - seed: &Seed, - db_path: PathBuf, - listen_address: Multiaddr, - env_config: Config, - bitcoin_wallet: Arc, - monero_wallet: Arc, -) -> (AliceApplicationHandle, Receiver) { - let db = Arc::new(Database::open(db_path.as_path()).unwrap()); - - let mut swarm = swarm::alice(&seed).unwrap(); - swarm.listen_on(listen_address).unwrap(); - - let (event_loop, swap_handle) = alice::EventLoop::new( - swarm, - env_config, - bitcoin_wallet, - monero_wallet, - db, - FixedRate::default(), - bitcoin::Amount::ONE_BTC, - ) - .unwrap(); - - let peer_id = event_loop.peer_id(); - let handle = tokio::spawn(event_loop.run()); - - (AliceApplicationHandle { handle, peer_id }, swap_handle) -} - fn random_prefix() -> String { use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng}; @@ -677,78 +844,6 @@ fn random_prefix() -> String { chars } -async fn init_containers(cli: &Cli) -> (Monero, Containers<'_>) { - let prefix = random_prefix(); - let bitcoind_name = format!("{}_{}", prefix, "bitcoind"); - let (bitcoind, bitcoind_url) = - init_bitcoind_container(&cli, prefix.clone(), bitcoind_name.clone(), prefix.clone()) - .await - .expect("could not init bitcoind"); - let electrs = init_electrs_container(&cli, prefix.clone(), bitcoind_name, prefix) - .await - .expect("could not init electrs"); - let (monero, monerods) = init_monero_container(&cli).await; - (monero, Containers { - bitcoind_url, - bitcoind, - monerods, - electrs, - }) -} - -async fn init_bitcoind_container( - cli: &Cli, - volume: String, - name: String, - network: String, -) -> Result<(Container<'_, Cli, bitcoind::Bitcoind>, Url)> { - let image = bitcoind::Bitcoind::default().with_volume(volume); - - let run_args = RunArgs::default().with_name(name).with_network(network); - - let docker = cli.run_with_args(image, run_args); - let a = docker - .get_host_port(harness::bitcoind::RPC_PORT) - .context("Could not map bitcoind rpc port")?; - - let bitcoind_url = { - let input = format!( - "http://{}:{}@localhost:{}", - bitcoind::RPC_USER, - bitcoind::RPC_PASSWORD, - a - ); - Url::parse(&input).unwrap() - }; - - init_bitcoind(bitcoind_url.clone(), 5).await?; - - Ok((docker, bitcoind_url.clone())) -} - -pub async fn init_electrs_container( - cli: &Cli, - volume: String, - bitcoind_container_name: String, - network: String, -) -> Result> { - let bitcoind_rpc_addr = format!( - "{}:{}", - bitcoind_container_name, - harness::bitcoind::RPC_PORT - ); - let image = electrs::Electrs::default() - .with_volume(volume) - .with_daemon_rpc_addr(bitcoind_rpc_addr) - .with_tag("latest"); - - let run_args = RunArgs::default().with_network(network); - - let docker = cli.run_with_args(image, run_args); - - Ok(docker) -} - async fn mine(bitcoind_client: Client, reward_address: bitcoin::Address) -> Result<()> { loop { tokio::time::sleep(Duration::from_secs(1)).await; @@ -798,101 +893,6 @@ pub async fn mint(node_url: Url, address: bitcoin::Address, amount: bitcoin::Amo Ok(()) } -async fn init_monero_container( - cli: &Cli, -) -> ( - Monero, - Vec>, -) { - let (monero, monerods) = Monero::new(&cli, vec![ - MONERO_WALLET_NAME_ALICE.to_string(), - MONERO_WALLET_NAME_BOB.to_string(), - ]) - .await - .unwrap(); - - (monero, monerods) -} - -#[allow(clippy::too_many_arguments)] -async fn init_test_wallets( - name: &str, - bitcoind_url: Url, - monero: &Monero, - starting_balances: StartingBalances, - datadir: &Path, - electrum_rpc_port: u16, - seed: &Seed, - env_config: Config, -) -> (Arc, Arc) { - monero - .init_wallet( - name, - starting_balances - .xmr_outputs - .into_iter() - .map(|amount| amount.as_piconero()) - .collect(), - ) - .await - .unwrap(); - - let xmr_wallet = swap::monero::Wallet::connect( - monero.wallet(name).unwrap().client(), - name.to_string(), - env_config, - ) - .await - .unwrap(); - - let electrum_rpc_url = { - let input = format!("tcp://@localhost:{}", electrum_rpc_port); - Url::parse(&input).unwrap() - }; - - let btc_wallet = swap::bitcoin::Wallet::new( - electrum_rpc_url, - datadir, - seed.derive_extended_private_key(env_config.bitcoin_network) - .expect("Could not create extended private key from seed"), - env_config, - ) - .await - .expect("could not init btc wallet"); - - if starting_balances.btc != bitcoin::Amount::ZERO { - mint( - bitcoind_url, - btc_wallet.new_address().await.unwrap(), - starting_balances.btc, - ) - .await - .expect("could not mint btc starting balance"); - - let mut interval = interval(Duration::from_secs(1u64)); - let mut retries = 0u8; - let max_retries = 30u8; - loop { - retries += 1; - btc_wallet.sync().await.unwrap(); - - let btc_balance = btc_wallet.balance().await.unwrap(); - - if btc_balance == starting_balances.btc { - break; - } else if retries == max_retries { - panic!( - "Bitcoin wallet initialization failed, reached max retries upon balance sync" - ) - } - - interval.tick().await; - } - } - - (Arc::new(btc_wallet), Arc::new(xmr_wallet)) -} - // This is just to keep the containers alive #[allow(dead_code)] struct Containers<'a> { From a31d6febca2319d2d8245fa92032522d44c43ab1 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 16 Apr 2021 11:14:29 +1000 Subject: [PATCH 03/15] We don't need to import ourselves --- swap/tests/harness/mod.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/swap/tests/harness/mod.rs b/swap/tests/harness/mod.rs index baf969c1..8264b527 100644 --- a/swap/tests/harness/mod.rs +++ b/swap/tests/harness/mod.rs @@ -1,7 +1,6 @@ mod bitcoind; mod electrs; -use crate::harness; use anyhow::{bail, Context, Result}; use async_trait::async_trait; use bitcoin_harness::{BitcoindRpcApi, Client}; @@ -51,7 +50,7 @@ where let env_config = C::get_config(); - let (monero, containers) = harness::init_containers(&cli).await; + let (monero, containers) = init_containers(&cli).await; monero.init_miner().await.unwrap(); let btc_amount = bitcoin::Amount::from_sat(1_000_000); @@ -62,7 +61,7 @@ where let electrs_rpc_port = containers .electrs - .get_host_port(harness::electrs::RPC_PORT) + .get_host_port(electrs::RPC_PORT) .expect("Could not map electrs rpc port"); let alice_seed = Seed::random().unwrap(); @@ -172,7 +171,7 @@ async fn init_bitcoind_container( let docker = cli.run_with_args(image, run_args); let a = docker - .get_host_port(harness::bitcoind::RPC_PORT) + .get_host_port(bitcoind::RPC_PORT) .context("Could not map bitcoind rpc port")?; let bitcoind_url = { @@ -212,11 +211,7 @@ pub async fn init_electrs_container( bitcoind_container_name: String, network: String, ) -> Result> { - let bitcoind_rpc_addr = format!( - "{}:{}", - bitcoind_container_name, - harness::bitcoind::RPC_PORT - ); + let bitcoind_rpc_addr = format!("{}:{}", bitcoind_container_name, bitcoind::RPC_PORT); let image = electrs::Electrs::default() .with_volume(volume) .with_daemon_rpc_addr(bitcoind_rpc_addr) From 5b515d6fb243658bc6ad73ca5de9bbf817d1f5e2 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 16 Apr 2021 11:17:13 +1000 Subject: [PATCH 04/15] Don't qualify with `alice::` if we are within a module of Alice --- swap/src/protocol/alice/swap.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/swap/src/protocol/alice/swap.rs b/swap/src/protocol/alice/swap.rs index 7e1b8e20..6f97f2c0 100644 --- a/swap/src/protocol/alice/swap.rs +++ b/swap/src/protocol/alice/swap.rs @@ -2,9 +2,8 @@ //! Alice holds XMR and wishes receive BTC. use crate::bitcoin::ExpiredTimelocks; use crate::env::Config; -use crate::protocol::alice; use crate::protocol::alice::event_loop::EventLoopHandle; -use crate::protocol::alice::AliceState; +use crate::protocol::alice::{AliceState, Swap}; use crate::{bitcoin, database, monero}; use anyhow::{bail, Context, Result}; use tokio::select; @@ -12,15 +11,12 @@ use tokio::time::timeout; use tracing::{error, info}; use uuid::Uuid; -pub async fn run(swap: alice::Swap) -> Result { +pub async fn run(swap: Swap) -> Result { run_until(swap, |_| false).await } #[tracing::instrument(name = "swap", skip(swap,exit_early), fields(id = %swap.swap_id), err)] -pub async fn run_until( - mut swap: alice::Swap, - exit_early: fn(&AliceState) -> bool, -) -> Result { +pub async fn run_until(mut swap: Swap, exit_early: fn(&AliceState) -> bool) -> Result { let mut current_state = swap.state; while !is_complete(¤t_state) && !exit_early(¤t_state) { From 325fcbdb8c98302e340f1493dd05358ee78742f1 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 16 Apr 2021 11:38:34 +1000 Subject: [PATCH 05/15] Introduce protocol::bob::behaviour to mimic structure from alice module --- swap/src/protocol/bob.rs | 90 +----------------------------- swap/src/protocol/bob/behaviour.rs | 88 +++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 87 deletions(-) create mode 100644 swap/src/protocol/bob/behaviour.rs diff --git a/swap/src/protocol/bob.rs b/swap/src/protocol/bob.rs index 6d045aa3..a0968b09 100644 --- a/swap/src/protocol/bob.rs +++ b/swap/src/protocol/bob.rs @@ -1,23 +1,19 @@ use crate::database::Database; use crate::env::Config; -use crate::network::quote::BidQuote; -use crate::network::{encrypted_signature, quote, redial, spot_price, transfer_proof}; use crate::protocol::bob; use crate::{bitcoin, monero}; -use anyhow::{anyhow, Error, Result}; -use libp2p::core::Multiaddr; -use libp2p::request_response::{RequestId, ResponseChannel}; -use libp2p::{NetworkBehaviour, PeerId}; +use anyhow::Result; use std::sync::Arc; use uuid::Uuid; +pub use self::behaviour::{Behaviour, OutEvent}; pub use self::cancel::cancel; pub use self::event_loop::{EventLoop, EventLoopHandle}; pub use self::refund::refund; pub use self::state::*; pub use self::swap::{run, run_until}; -use std::time::Duration; +mod behaviour; pub mod cancel; pub mod event_loop; mod execution_setup; @@ -104,83 +100,3 @@ impl Builder { }) } } - -#[derive(Debug)] -pub enum OutEvent { - QuoteReceived { - id: RequestId, - response: BidQuote, - }, - SpotPriceReceived { - id: RequestId, - response: spot_price::Response, - }, - ExecutionSetupDone(Box>), - TransferProofReceived { - msg: Box, - channel: ResponseChannel<()>, - }, - EncryptedSignatureAcknowledged { - id: RequestId, - }, - AllRedialAttemptsExhausted { - peer: PeerId, - }, - Failure { - peer: PeerId, - error: Error, - }, - /// "Fallback" variant that allows the event mapping code to swallow certain - /// events that we don't want the caller to deal with. - Other, -} - -impl OutEvent { - pub fn unexpected_request(peer: PeerId) -> OutEvent { - OutEvent::Failure { - peer, - error: anyhow!("Unexpected request received"), - } - } - - pub fn unexpected_response(peer: PeerId) -> OutEvent { - OutEvent::Failure { - peer, - error: anyhow!("Unexpected response received"), - } - } -} - -/// A `NetworkBehaviour` that represents an XMR/BTC swap node as Bob. -#[derive(NetworkBehaviour)] -#[behaviour(out_event = "OutEvent", event_process = false)] -#[allow(missing_debug_implementations)] -pub struct Behaviour { - pub quote: quote::Behaviour, - pub spot_price: spot_price::Behaviour, - pub execution_setup: execution_setup::Behaviour, - pub transfer_proof: transfer_proof::Behaviour, - pub encrypted_signature: encrypted_signature::Behaviour, - pub redial: redial::Behaviour, -} - -impl Behaviour { - pub fn new(alice: PeerId) -> Self { - Self { - quote: quote::bob(), - spot_price: spot_price::bob(), - execution_setup: Default::default(), - transfer_proof: transfer_proof::bob(), - encrypted_signature: encrypted_signature::bob(), - redial: redial::Behaviour::new(alice, Duration::from_secs(2)), - } - } - - /// Add a known address for the given peer - pub fn add_address(&mut self, peer_id: PeerId, address: Multiaddr) { - self.quote.add_address(&peer_id, address.clone()); - self.spot_price.add_address(&peer_id, address.clone()); - self.transfer_proof.add_address(&peer_id, address.clone()); - self.encrypted_signature.add_address(&peer_id, address); - } -} diff --git a/swap/src/protocol/bob/behaviour.rs b/swap/src/protocol/bob/behaviour.rs new file mode 100644 index 00000000..06ca9a8c --- /dev/null +++ b/swap/src/protocol/bob/behaviour.rs @@ -0,0 +1,88 @@ +use crate::network::quote::BidQuote; +use crate::network::{encrypted_signature, quote, redial, spot_price, transfer_proof}; +use crate::protocol::bob::{execution_setup, State2}; +use anyhow::{anyhow, Error, Result}; +use libp2p::core::Multiaddr; +use libp2p::request_response::{RequestId, ResponseChannel}; +use libp2p::{NetworkBehaviour, PeerId}; +use std::time::Duration; + +#[derive(Debug)] +pub enum OutEvent { + QuoteReceived { + id: RequestId, + response: BidQuote, + }, + SpotPriceReceived { + id: RequestId, + response: spot_price::Response, + }, + ExecutionSetupDone(Box>), + TransferProofReceived { + msg: Box, + channel: ResponseChannel<()>, + }, + EncryptedSignatureAcknowledged { + id: RequestId, + }, + AllRedialAttemptsExhausted { + peer: PeerId, + }, + Failure { + peer: PeerId, + error: Error, + }, + /// "Fallback" variant that allows the event mapping code to swallow certain + /// events that we don't want the caller to deal with. + Other, +} + +impl OutEvent { + pub fn unexpected_request(peer: PeerId) -> OutEvent { + OutEvent::Failure { + peer, + error: anyhow!("Unexpected request received"), + } + } + + pub fn unexpected_response(peer: PeerId) -> OutEvent { + OutEvent::Failure { + peer, + error: anyhow!("Unexpected response received"), + } + } +} + +/// A `NetworkBehaviour` that represents an XMR/BTC swap node as Bob. +#[derive(NetworkBehaviour)] +#[behaviour(out_event = "OutEvent", event_process = false)] +#[allow(missing_debug_implementations)] +pub struct Behaviour { + pub quote: quote::Behaviour, + pub spot_price: spot_price::Behaviour, + pub execution_setup: execution_setup::Behaviour, + pub transfer_proof: transfer_proof::Behaviour, + pub encrypted_signature: encrypted_signature::Behaviour, + pub redial: redial::Behaviour, +} + +impl Behaviour { + pub fn new(alice: PeerId) -> Self { + Self { + quote: quote::bob(), + spot_price: spot_price::bob(), + execution_setup: Default::default(), + transfer_proof: transfer_proof::bob(), + encrypted_signature: encrypted_signature::bob(), + redial: redial::Behaviour::new(alice, Duration::from_secs(2)), + } + } + + /// Add a known address for the given peer + pub fn add_address(&mut self, peer_id: PeerId, address: Multiaddr) { + self.quote.add_address(&peer_id, address.clone()); + self.spot_price.add_address(&peer_id, address.clone()); + self.transfer_proof.add_address(&peer_id, address.clone()); + self.encrypted_signature.add_address(&peer_id, address); + } +} From be5bf01ed477c4a3470dbe35fbc77b985e759402 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 16 Apr 2021 11:42:24 +1000 Subject: [PATCH 06/15] Don't overqualify types that are already imported --- swap/src/protocol/bob.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/swap/src/protocol/bob.rs b/swap/src/protocol/bob.rs index a0968b09..e8328ea1 100644 --- a/swap/src/protocol/bob.rs +++ b/swap/src/protocol/bob.rs @@ -1,6 +1,5 @@ use crate::database::Database; use crate::env::Config; -use crate::protocol::bob; use crate::{bitcoin, monero}; use anyhow::Result; use std::sync::Arc; @@ -23,13 +22,13 @@ pub mod swap; pub struct Swap { pub state: BobState, - pub event_loop_handle: bob::EventLoopHandle, + pub event_loop_handle: EventLoopHandle, pub db: Database, pub bitcoin_wallet: Arc, pub monero_wallet: Arc, pub env_config: Config, pub swap_id: Uuid, - pub receive_monero_address: ::monero::Address, + pub receive_monero_address: monero::Address, } pub struct Builder { @@ -44,7 +43,7 @@ pub struct Builder { event_loop_handle: EventLoopHandle, - receive_monero_address: ::monero::Address, + receive_monero_address: monero::Address, } enum InitParams { @@ -61,7 +60,7 @@ impl Builder { monero_wallet: Arc, env_config: Config, event_loop_handle: EventLoopHandle, - receive_monero_address: ::monero::Address, + receive_monero_address: monero::Address, ) -> Self { Self { swap_id, @@ -82,7 +81,7 @@ impl Builder { } } - pub fn build(self) -> Result { + pub fn build(self) -> Result { let state = match self.init_params { InitParams::New { btc_amount } => BobState::Started { btc_amount }, InitParams::None => self.db.get_state(self.swap_id)?.try_into_bob()?.into(), From 7adeaae12d06150142f7f0bed9c3d32c82110827 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 16 Apr 2021 11:45:05 +1000 Subject: [PATCH 07/15] Be smart about how we import Config By saying env::Config, we can save a line of code in the imports and make it clearer, what kind of `Config` this is. --- swap/src/protocol/bob.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/swap/src/protocol/bob.rs b/swap/src/protocol/bob.rs index e8328ea1..36e4d0d0 100644 --- a/swap/src/protocol/bob.rs +++ b/swap/src/protocol/bob.rs @@ -1,6 +1,5 @@ use crate::database::Database; -use crate::env::Config; -use crate::{bitcoin, monero}; +use crate::{bitcoin, env, monero}; use anyhow::Result; use std::sync::Arc; use uuid::Uuid; @@ -26,7 +25,7 @@ pub struct Swap { pub db: Database, pub bitcoin_wallet: Arc, pub monero_wallet: Arc, - pub env_config: Config, + pub env_config: env::Config, pub swap_id: Uuid, pub receive_monero_address: monero::Address, } @@ -39,7 +38,7 @@ pub struct Builder { monero_wallet: Arc, init_params: InitParams, - env_config: Config, + env_config: env::Config, event_loop_handle: EventLoopHandle, @@ -58,7 +57,7 @@ impl Builder { swap_id: Uuid, bitcoin_wallet: Arc, monero_wallet: Arc, - env_config: Config, + env_config: env::Config, event_loop_handle: EventLoopHandle, receive_monero_address: monero::Address, ) -> Self { From e266fb07ef31cc98e02e90fcd3dce1fabeb2b9c3 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 16 Apr 2021 11:48:15 +1000 Subject: [PATCH 08/15] Don't stutter --- swap/src/protocol/bob.rs | 4 ++-- swap/src/protocol/bob/swap.rs | 4 ++-- .../alice_punishes_after_restart_punish_timelock_expired.rs | 2 +- swap/tests/bob_refunds_using_cancel_and_refund_command.rs | 6 +++--- ..._using_cancel_and_refund_command_timelock_not_expired.rs | 6 +++--- ..._cancel_and_refund_command_timelock_not_expired_force.rs | 6 +++--- swap/tests/concurrent_bobs_after_xmr_lock_proof_sent.rs | 2 +- swap/tests/concurrent_bobs_before_xmr_lock_proof_sent.rs | 2 +- swap/tests/ensure_same_swap_id.rs | 2 +- swap/tests/happy_path_restart_bob_after_xmr_locked.rs | 2 +- swap/tests/happy_path_restart_bob_before_xmr_locked.rs | 2 +- swap/tests/punish.rs | 2 +- 12 files changed, 20 insertions(+), 20 deletions(-) diff --git a/swap/src/protocol/bob.rs b/swap/src/protocol/bob.rs index 36e4d0d0..aeae1f00 100644 --- a/swap/src/protocol/bob.rs +++ b/swap/src/protocol/bob.rs @@ -26,7 +26,7 @@ pub struct Swap { pub bitcoin_wallet: Arc, pub monero_wallet: Arc, pub env_config: env::Config, - pub swap_id: Uuid, + pub id: Uuid, pub receive_monero_address: monero::Address, } @@ -92,7 +92,7 @@ impl Builder { db: self.db, bitcoin_wallet: self.bitcoin_wallet.clone(), monero_wallet: self.monero_wallet.clone(), - swap_id: self.swap_id, + id: self.swap_id, env_config: self.env_config, receive_monero_address: self.receive_monero_address, }) diff --git a/swap/src/protocol/bob/swap.rs b/swap/src/protocol/bob/swap.rs index 0702f76e..88ad040d 100644 --- a/swap/src/protocol/bob/swap.rs +++ b/swap/src/protocol/bob/swap.rs @@ -33,7 +33,7 @@ pub async fn run_until( while !is_target_state(¤t_state) { current_state = next_state( - swap.swap_id, + swap.id, current_state, &mut swap.event_loop_handle, swap.bitcoin_wallet.as_ref(), @@ -45,7 +45,7 @@ pub async fn run_until( let db_state = current_state.clone().into(); swap.db - .insert_latest_state(swap.swap_id, Swap::Bob(db_state)) + .insert_latest_state(swap.id, Swap::Bob(db_state)) .await?; } diff --git a/swap/tests/alice_punishes_after_restart_punish_timelock_expired.rs b/swap/tests/alice_punishes_after_restart_punish_timelock_expired.rs index 0173b1d0..9157a19c 100644 --- a/swap/tests/alice_punishes_after_restart_punish_timelock_expired.rs +++ b/swap/tests/alice_punishes_after_restart_punish_timelock_expired.rs @@ -13,7 +13,7 @@ use swap::protocol::{alice, bob}; async fn alice_punishes_after_restart_if_punish_timelock_expired() { harness::setup_test(FastPunishConfig, |mut ctx| async move { let (bob_swap, bob_join_handle) = ctx.bob_swap().await; - let bob_swap_id = bob_swap.swap_id; + let bob_swap_id = bob_swap.id; let bob_swap = tokio::spawn(bob::run_until(bob_swap, is_btc_locked)); let alice_swap = ctx.alice_next_swap().await; diff --git a/swap/tests/bob_refunds_using_cancel_and_refund_command.rs b/swap/tests/bob_refunds_using_cancel_and_refund_command.rs index 71a902a8..d218341e 100644 --- a/swap/tests/bob_refunds_using_cancel_and_refund_command.rs +++ b/swap/tests/bob_refunds_using_cancel_and_refund_command.rs @@ -9,7 +9,7 @@ use swap::protocol::{alice, bob}; async fn given_bob_manually_refunds_after_btc_locked_bob_refunds() { harness::setup_test(FastCancelConfig, |mut ctx| async move { let (bob_swap, bob_join_handle) = ctx.bob_swap().await; - let bob_swap_id = bob_swap.swap_id; + let bob_swap_id = bob_swap.id; let bob_swap = tokio::spawn(bob::run_until(bob_swap, is_btc_locked)); let alice_swap = ctx.alice_next_swap().await; @@ -37,7 +37,7 @@ async fn given_bob_manually_refunds_after_btc_locked_bob_refunds() { // Bob manually cancels bob_join_handle.abort(); let (_, state) = bob::cancel( - bob_swap.swap_id, + bob_swap.id, bob_swap.state, bob_swap.bitcoin_wallet, bob_swap.db, @@ -54,7 +54,7 @@ async fn given_bob_manually_refunds_after_btc_locked_bob_refunds() { // Bob manually refunds bob_join_handle.abort(); let bob_state = bob::refund( - bob_swap.swap_id, + bob_swap.id, bob_swap.state, bob_swap.bitcoin_wallet, bob_swap.db, diff --git a/swap/tests/bob_refunds_using_cancel_and_refund_command_timelock_not_expired.rs b/swap/tests/bob_refunds_using_cancel_and_refund_command_timelock_not_expired.rs index 49d73a58..5c8e0f20 100644 --- a/swap/tests/bob_refunds_using_cancel_and_refund_command_timelock_not_expired.rs +++ b/swap/tests/bob_refunds_using_cancel_and_refund_command_timelock_not_expired.rs @@ -10,7 +10,7 @@ use swap::protocol::{alice, bob}; async fn given_bob_manually_cancels_when_timelock_not_expired_errors() { harness::setup_test(SlowCancelConfig, |mut ctx| async move { let (bob_swap, bob_join_handle) = ctx.bob_swap().await; - let bob_swap_id = bob_swap.swap_id; + let bob_swap_id = bob_swap.id; let bob_swap = tokio::spawn(bob::run_until(bob_swap, is_btc_locked)); let alice_swap = ctx.alice_next_swap().await; @@ -26,7 +26,7 @@ async fn given_bob_manually_cancels_when_timelock_not_expired_errors() { // Bob tries but fails to manually cancel let result = bob::cancel( - bob_swap.swap_id, + bob_swap.id, bob_swap.state, bob_swap.bitcoin_wallet, bob_swap.db, @@ -45,7 +45,7 @@ async fn given_bob_manually_cancels_when_timelock_not_expired_errors() { // Bob tries but fails to manually refund bob::refund( - bob_swap.swap_id, + bob_swap.id, bob_swap.state, bob_swap.bitcoin_wallet, bob_swap.db, diff --git a/swap/tests/bob_refunds_using_cancel_and_refund_command_timelock_not_expired_force.rs b/swap/tests/bob_refunds_using_cancel_and_refund_command_timelock_not_expired_force.rs index 057db955..df06416d 100644 --- a/swap/tests/bob_refunds_using_cancel_and_refund_command_timelock_not_expired_force.rs +++ b/swap/tests/bob_refunds_using_cancel_and_refund_command_timelock_not_expired_force.rs @@ -9,7 +9,7 @@ use swap::protocol::{alice, bob}; async fn given_bob_manually_forces_cancel_when_timelock_not_expired_errors() { harness::setup_test(SlowCancelConfig, |mut ctx| async move { let (bob_swap, bob_join_handle) = ctx.bob_swap().await; - let bob_swap_id = bob_swap.swap_id; + let bob_swap_id = bob_swap.id; let bob_swap = tokio::spawn(bob::run_until(bob_swap, is_btc_locked)); let alice_swap = ctx.alice_next_swap().await; @@ -25,7 +25,7 @@ async fn given_bob_manually_forces_cancel_when_timelock_not_expired_errors() { // Bob forces a cancel that will fail let is_error = bob::cancel( - bob_swap.swap_id, + bob_swap.id, bob_swap.state, bob_swap.bitcoin_wallet, bob_swap.db, @@ -43,7 +43,7 @@ async fn given_bob_manually_forces_cancel_when_timelock_not_expired_errors() { // Bob forces a refund that will fail let is_error = bob::refund( - bob_swap.swap_id, + bob_swap.id, bob_swap.state, bob_swap.bitcoin_wallet, bob_swap.db, diff --git a/swap/tests/concurrent_bobs_after_xmr_lock_proof_sent.rs b/swap/tests/concurrent_bobs_after_xmr_lock_proof_sent.rs index 11ae81b9..a580a6be 100644 --- a/swap/tests/concurrent_bobs_after_xmr_lock_proof_sent.rs +++ b/swap/tests/concurrent_bobs_after_xmr_lock_proof_sent.rs @@ -11,7 +11,7 @@ async fn concurrent_bobs_after_xmr_lock_proof_sent() { harness::setup_test(SlowCancelConfig, |mut ctx| async move { let (bob_swap_1, bob_join_handle_1) = ctx.bob_swap().await; - let swap_id = bob_swap_1.swap_id; + let swap_id = bob_swap_1.id; let bob_swap_1 = tokio::spawn(bob::run_until(bob_swap_1, is_xmr_locked)); diff --git a/swap/tests/concurrent_bobs_before_xmr_lock_proof_sent.rs b/swap/tests/concurrent_bobs_before_xmr_lock_proof_sent.rs index ed3efafe..a15d6cae 100644 --- a/swap/tests/concurrent_bobs_before_xmr_lock_proof_sent.rs +++ b/swap/tests/concurrent_bobs_before_xmr_lock_proof_sent.rs @@ -11,7 +11,7 @@ async fn concurrent_bobs_before_xmr_lock_proof_sent() { harness::setup_test(SlowCancelConfig, |mut ctx| async move { let (bob_swap_1, bob_join_handle_1) = ctx.bob_swap().await; - let swap_id = bob_swap_1.swap_id; + let swap_id = bob_swap_1.id; let bob_swap_1 = tokio::spawn(bob::run_until(bob_swap_1, is_btc_locked)); diff --git a/swap/tests/ensure_same_swap_id.rs b/swap/tests/ensure_same_swap_id.rs index 1dbc7046..48758ecd 100644 --- a/swap/tests/ensure_same_swap_id.rs +++ b/swap/tests/ensure_same_swap_id.rs @@ -7,7 +7,7 @@ use swap::protocol::bob; async fn ensure_same_swap_id_for_alice_and_bob() { harness::setup_test(SlowCancelConfig, |mut ctx| async move { let (bob_swap, _) = ctx.bob_swap().await; - let bob_swap_id = bob_swap.swap_id; + let bob_swap_id = bob_swap.id; let _ = tokio::spawn(bob::run(bob_swap)); // once Bob's swap is spawned we can retrieve Alice's swap and assert on the diff --git a/swap/tests/happy_path_restart_bob_after_xmr_locked.rs b/swap/tests/happy_path_restart_bob_after_xmr_locked.rs index 8e18f5ed..a07fb2f2 100644 --- a/swap/tests/happy_path_restart_bob_after_xmr_locked.rs +++ b/swap/tests/happy_path_restart_bob_after_xmr_locked.rs @@ -9,7 +9,7 @@ use swap::protocol::{alice, bob}; async fn given_bob_restarts_after_xmr_is_locked_resume_swap() { harness::setup_test(SlowCancelConfig, |mut ctx| async move { let (bob_swap, bob_join_handle) = ctx.bob_swap().await; - let bob_swap_id = bob_swap.swap_id; + let bob_swap_id = bob_swap.id; let bob_swap = tokio::spawn(bob::run_until(bob_swap, is_xmr_locked)); let alice_swap = ctx.alice_next_swap().await; diff --git a/swap/tests/happy_path_restart_bob_before_xmr_locked.rs b/swap/tests/happy_path_restart_bob_before_xmr_locked.rs index 8e18f5ed..a07fb2f2 100644 --- a/swap/tests/happy_path_restart_bob_before_xmr_locked.rs +++ b/swap/tests/happy_path_restart_bob_before_xmr_locked.rs @@ -9,7 +9,7 @@ use swap::protocol::{alice, bob}; async fn given_bob_restarts_after_xmr_is_locked_resume_swap() { harness::setup_test(SlowCancelConfig, |mut ctx| async move { let (bob_swap, bob_join_handle) = ctx.bob_swap().await; - let bob_swap_id = bob_swap.swap_id; + let bob_swap_id = bob_swap.id; let bob_swap = tokio::spawn(bob::run_until(bob_swap, is_xmr_locked)); let alice_swap = ctx.alice_next_swap().await; diff --git a/swap/tests/punish.rs b/swap/tests/punish.rs index bf053f5b..ef498d10 100644 --- a/swap/tests/punish.rs +++ b/swap/tests/punish.rs @@ -11,7 +11,7 @@ use swap::protocol::{alice, bob}; async fn alice_punishes_if_bob_never_acts_after_fund() { harness::setup_test(FastPunishConfig, |mut ctx| async move { let (bob_swap, bob_join_handle) = ctx.bob_swap().await; - let bob_swap_id = bob_swap.swap_id; + let bob_swap_id = bob_swap.id; let bob_swap = tokio::spawn(bob::run_until(bob_swap, is_btc_locked)); let alice_swap = ctx.alice_next_swap().await; From 22bdc08c838c99790fb7a7fa39636e5dc479163d Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 16 Apr 2021 12:00:11 +1000 Subject: [PATCH 09/15] Get rid of Bob's swap Builder Doesn't serve any purpose. We are better of just having two constructors. --- swap/src/bin/swap.rs | 18 +++++----- swap/src/protocol/bob.rs | 69 ++++++++++++++------------------------- swap/tests/harness/mod.rs | 63 +++++++++++++++++------------------ 3 files changed, 64 insertions(+), 86 deletions(-) diff --git a/swap/src/bin/swap.rs b/swap/src/bin/swap.rs index c155ba57..d5368915 100644 --- a/swap/src/bin/swap.rs +++ b/swap/src/bin/swap.rs @@ -27,7 +27,7 @@ use swap::env::{Config, GetConfig}; use swap::network::quote::BidQuote; use swap::network::swarm; use swap::protocol::bob; -use swap::protocol::bob::{Builder, EventLoop}; +use swap::protocol::bob::{EventLoop, Swap}; use swap::seed::Seed; use swap::{bitcoin, cli, env, monero}; use tracing::{debug, error, info, warn}; @@ -105,17 +105,16 @@ async fn main() -> Result<()> { db.insert_peer_id(swap_id, alice_peer_id).await?; - let swap = Builder::new( + let swap = Swap::new( db, swap_id, - bitcoin_wallet.clone(), + bitcoin_wallet, Arc::new(monero_wallet), env_config, event_loop_handle, receive_monero_address, - ) - .with_init_params(send_bitcoin) - .build()?; + send_bitcoin, + ); tokio::select! { result = event_loop => { @@ -182,16 +181,15 @@ async fn main() -> Result<()> { EventLoop::new(swap_id, swarm, alice_peer_id, bitcoin_wallet.clone())?; let handle = tokio::spawn(event_loop.run()); - let swap = Builder::new( + let swap = Swap::from_db( db, swap_id, - bitcoin_wallet.clone(), + bitcoin_wallet, Arc::new(monero_wallet), env_config, event_loop_handle, receive_monero_address, - ) - .build()?; + )?; tokio::select! { event_loop_result = handle => { diff --git a/swap/src/protocol/bob.rs b/swap/src/protocol/bob.rs index aeae1f00..fbcc7dd1 100644 --- a/swap/src/protocol/bob.rs +++ b/swap/src/protocol/bob.rs @@ -30,71 +30,50 @@ pub struct Swap { pub receive_monero_address: monero::Address, } -pub struct Builder { - swap_id: Uuid, - db: Database, - - bitcoin_wallet: Arc, - monero_wallet: Arc, - - init_params: InitParams, - env_config: env::Config, - - event_loop_handle: EventLoopHandle, - - receive_monero_address: monero::Address, -} - -enum InitParams { - None, - New { btc_amount: bitcoin::Amount }, -} - -impl Builder { +impl Swap { #[allow(clippy::too_many_arguments)] pub fn new( db: Database, - swap_id: Uuid, + id: Uuid, bitcoin_wallet: Arc, monero_wallet: Arc, env_config: env::Config, event_loop_handle: EventLoopHandle, receive_monero_address: monero::Address, + btc_amount: bitcoin::Amount, ) -> Self { Self { - swap_id, + state: BobState::Started { btc_amount }, + event_loop_handle, db, bitcoin_wallet, monero_wallet, - init_params: InitParams::None, env_config, - event_loop_handle, + id, receive_monero_address, } } - pub fn with_init_params(self, btc_amount: bitcoin::Amount) -> Self { - Self { - init_params: InitParams::New { btc_amount }, - ..self - } - } + pub fn from_db( + db: Database, + id: Uuid, + bitcoin_wallet: Arc, + monero_wallet: Arc, + env_config: env::Config, + event_loop_handle: EventLoopHandle, + receive_monero_address: monero::Address, + ) -> Result { + let state = db.get_state(id)?.try_into_bob()?.into(); - pub fn build(self) -> Result { - let state = match self.init_params { - InitParams::New { btc_amount } => BobState::Started { btc_amount }, - InitParams::None => self.db.get_state(self.swap_id)?.try_into_bob()?.into(), - }; - - Ok(Swap { + Ok(Self { state, - event_loop_handle: self.event_loop_handle, - db: self.db, - bitcoin_wallet: self.bitcoin_wallet.clone(), - monero_wallet: self.monero_wallet.clone(), - id: self.swap_id, - env_config: self.env_config, - receive_monero_address: self.receive_monero_address, + event_loop_handle, + db, + bitcoin_wallet, + monero_wallet, + env_config, + id, + receive_monero_address, }) } } diff --git a/swap/tests/harness/mod.rs b/swap/tests/harness/mod.rs index 8264b527..4888a4ce 100644 --- a/swap/tests/harness/mod.rs +++ b/swap/tests/harness/mod.rs @@ -394,22 +394,41 @@ struct BobParams { } impl BobParams { - pub async fn builder( - &self, - event_loop_handle: bob::EventLoopHandle, - swap_id: Uuid, - ) -> Result { - let receive_address = self.monero_wallet.get_main_address(); + pub fn new_swap_from_db(&self, swap_id: Uuid) -> Result<(bob::Swap, bob::EventLoop)> { + let (event_loop, handle) = self.new_eventloop(swap_id)?; + let db = Database::open(&self.db_path)?; - Ok(bob::Builder::new( - Database::open(&self.db_path.clone().as_path()).unwrap(), + let swap = bob::Swap::from_db( + db, swap_id, self.bitcoin_wallet.clone(), self.monero_wallet.clone(), self.env_config, - event_loop_handle, - receive_address, - )) + handle, + self.monero_wallet.get_main_address(), + )?; + + Ok((swap, event_loop)) + } + + pub fn new_swap(&self, btc_amount: bitcoin::Amount) -> Result<(bob::Swap, bob::EventLoop)> { + let swap_id = Uuid::new_v4(); + + let (event_loop, handle) = self.new_eventloop(swap_id)?; + let db = Database::open(&self.db_path)?; + + let swap = bob::Swap::new( + db, + swap_id, + self.bitcoin_wallet.clone(), + self.monero_wallet.clone(), + self.env_config, + handle, + self.monero_wallet.get_main_address(), + btc_amount, + ); + + Ok((swap, event_loop)) } pub fn new_eventloop(&self, swap_id: Uuid) -> Result<(bob::EventLoop, bob::EventLoopHandle)> { @@ -493,17 +512,7 @@ impl TestContext { } pub async fn bob_swap(&mut self) -> (bob::Swap, BobApplicationHandle) { - let swap_id = Uuid::new_v4(); - let (event_loop, event_loop_handle) = self.bob_params.new_eventloop(swap_id).unwrap(); - - let swap = self - .bob_params - .builder(event_loop_handle, swap_id) - .await - .unwrap() - .with_init_params(self.btc_amount) - .build() - .unwrap(); + let (swap, event_loop) = self.bob_params.new_swap(self.btc_amount).unwrap(); // ensure the wallet is up to date for concurrent swap tests swap.bitcoin_wallet.sync().await.unwrap(); @@ -520,15 +529,7 @@ impl TestContext { ) -> (bob::Swap, BobApplicationHandle) { join_handle.abort(); - let (event_loop, event_loop_handle) = self.bob_params.new_eventloop(swap_id).unwrap(); - - let swap = self - .bob_params - .builder(event_loop_handle, swap_id) - .await - .unwrap() - .build() - .unwrap(); + let (swap, event_loop) = self.bob_params.new_swap_from_db(swap_id).unwrap(); let join_handle = tokio::spawn(event_loop.run()); From dc840e156265fa61f00f20e3124d083ef4529221 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 16 Apr 2021 12:05:12 +1000 Subject: [PATCH 10/15] Take wallet names by reference We are always passing constants here. Make that more ergonomic. --- monero-harness/src/lib.rs | 2 +- monero-harness/tests/wallet.rs | 4 +--- swap/tests/harness/mod.rs | 10 ++++------ 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/monero-harness/src/lib.rs b/monero-harness/src/lib.rs index 0824a44d..5f69cd43 100644 --- a/monero-harness/src/lib.rs +++ b/monero-harness/src/lib.rs @@ -56,7 +56,7 @@ impl<'c> Monero { /// miner wallet container name is: `miner` pub async fn new( cli: &'c Cli, - additional_wallets: Vec, + additional_wallets: Vec<&'static str>, ) -> Result<(Self, Vec>)> { let prefix = format!("{}_", random_prefix()); let monerod_name = format!("{}{}", prefix, MONEROD_DAEMON_CONTAINER_NAME); diff --git a/monero-harness/tests/wallet.rs b/monero-harness/tests/wallet.rs index d2e97cb4..df26d348 100644 --- a/monero-harness/tests/wallet.rs +++ b/monero-harness/tests/wallet.rs @@ -17,9 +17,7 @@ async fn fund_transfer_and_check_tx_key() { let send_to_bob = 5_000_000_000; let tc = Cli::default(); - let (monero, _containers) = Monero::new(&tc, vec!["alice".to_string(), "bob".to_string()]) - .await - .unwrap(); + let (monero, _containers) = Monero::new(&tc, vec!["alice", "bob"]).await.unwrap(); let alice_wallet = monero.wallet("alice").unwrap(); let bob_wallet = monero.wallet("bob").unwrap(); diff --git a/swap/tests/harness/mod.rs b/swap/tests/harness/mod.rs index 4888a4ce..58c77ea1 100644 --- a/swap/tests/harness/mod.rs +++ b/swap/tests/harness/mod.rs @@ -195,12 +195,10 @@ async fn init_monero_container( Monero, Vec>, ) { - let (monero, monerods) = Monero::new(&cli, vec![ - MONERO_WALLET_NAME_ALICE.to_owned(), - MONERO_WALLET_NAME_BOB.to_owned(), - ]) - .await - .unwrap(); + let (monero, monerods) = + Monero::new(&cli, vec![MONERO_WALLET_NAME_ALICE, MONERO_WALLET_NAME_BOB]) + .await + .unwrap(); (monero, monerods) } From 7e688eb7e830e6e042d0d73e41724582a73b8535 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 16 Apr 2021 13:11:26 +1000 Subject: [PATCH 11/15] Don't reinvent the wheel `Alphanumeric` includes uppercase letters and digits as well but for our usecase, that doesn't matter. --- monero-harness/src/lib.rs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/monero-harness/src/lib.rs b/monero-harness/src/lib.rs index 5f69cd43..090ac7e4 100644 --- a/monero-harness/src/lib.rs +++ b/monero-harness/src/lib.rs @@ -165,17 +165,11 @@ impl<'c> Monero { fn random_prefix() -> String { use rand::Rng; - const CHARSET: &[u8] = b"abcdefghijklmnopqrstuvwxyz"; - const LEN: usize = 4; - let mut rng = rand::thread_rng(); - let prefix: String = (0..LEN) - .map(|_| { - let idx = rng.gen_range(0, CHARSET.len()); - CHARSET[idx] as char - }) - .collect(); - prefix + rand::thread_rng() + .sample_iter(rand::distributions::Alphanumeric) + .take(4) + .collect() } #[derive(Clone, Debug)] From 0970c2bc726067979e4801918ce097938a5adca2 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Mon, 19 Apr 2021 10:10:46 +1000 Subject: [PATCH 12/15] Initialize reqwest clients with verbose logging --- monero-harness/src/lib.rs | 21 +++++++++++++-------- monero-rpc/src/monerod.rs | 13 ++++++++----- monero-rpc/src/wallet.rs | 16 +++++++++------- swap/src/monero/wallet.rs | 2 +- swap/src/monero/wallet_rpc.rs | 2 +- swap/tests/harness/mod.rs | 4 ++-- 6 files changed, 34 insertions(+), 24 deletions(-) diff --git a/monero-harness/src/lib.rs b/monero-harness/src/lib.rs index 090ac7e4..c667e19e 100644 --- a/monero-harness/src/lib.rs +++ b/monero-harness/src/lib.rs @@ -177,6 +177,7 @@ pub struct Monerod { rpc_port: u16, name: String, network: String, + client: monerod::Client, } #[derive(Clone, Debug)] @@ -184,6 +185,7 @@ pub struct MoneroWalletRpc { rpc_port: u16, name: String, network: String, + client: wallet::Client, } impl<'c> Monerod { @@ -211,19 +213,20 @@ impl<'c> Monerod { rpc_port: monerod_rpc_port, name, network, + client: monerod::Client::localhost(monerod_rpc_port)?, }, docker, )) } - pub fn client(&self) -> monerod::Client { - monerod::Client::localhost(self.rpc_port) + pub fn client(&self) -> &monerod::Client { + &self.client } /// Spawns a task to mine blocks in a regular interval to the provided /// address pub async fn start_miner(&self, miner_wallet_address: &str) -> Result<()> { - let monerod = self.client(); + let monerod = self.client().clone(); let _ = tokio::spawn(mine(monerod, miner_wallet_address.to_string())); Ok(()) } @@ -256,23 +259,25 @@ impl<'c> MoneroWalletRpc { let docker = cli.run_with_args(image, run_args); // create new wallet - wallet::Client::localhost(wallet_rpc_port) + let client = wallet::Client::localhost(wallet_rpc_port)?; + + client .create_wallet(name.to_owned(), "English".to_owned()) - .await - .unwrap(); + .await?; Ok(( Self { rpc_port: wallet_rpc_port, name: name.to_string(), network, + client, }, docker, )) } - pub fn client(&self) -> wallet::Client { - wallet::Client::localhost(self.rpc_port) + pub fn client(&self) -> &wallet::Client { + &self.client } // It takes a little while for the wallet to sync with monerod. diff --git a/monero-rpc/src/monerod.rs b/monero-rpc/src/monerod.rs index 9926d7ac..8d044134 100644 --- a/monero-rpc/src/monerod.rs +++ b/monero-rpc/src/monerod.rs @@ -1,3 +1,4 @@ +use anyhow::{Context, Result}; use serde::Deserialize; #[jsonrpc_client::api(version = "2.0")] @@ -17,13 +18,15 @@ pub struct Client { impl Client { /// New local host monerod RPC client. - pub fn localhost(port: u16) -> Self { - Self { - inner: reqwest::Client::new(), + pub fn localhost(port: u16) -> Result { + Ok(Self { + inner: reqwest::ClientBuilder::new() + .connection_verbose(true) + .build()?, base_url: format!("http://127.0.0.1:{}/json_rpc", port) .parse() - .expect("url is well formed"), - } + .context("url is well formed")?, + }) } } diff --git a/monero-rpc/src/wallet.rs b/monero-rpc/src/wallet.rs index 67d7ce21..4f10f6ab 100644 --- a/monero-rpc/src/wallet.rs +++ b/monero-rpc/src/wallet.rs @@ -1,4 +1,4 @@ -use anyhow::Result; +use anyhow::{Context, Result}; use serde::{de::Error, Deserialize, Deserializer, Serialize}; #[jsonrpc_client::api(version = "2.0")] @@ -43,20 +43,22 @@ pub struct Client { impl Client { /// Constructs a monero-wallet-rpc client with localhost endpoint. - pub fn localhost(port: u16) -> Self { + pub fn localhost(port: u16) -> Result { Client::new( format!("http://127.0.0.1:{}/json_rpc", port) .parse() - .expect("url is well formed"), + .context("url is well formed")?, ) } /// Constructs a monero-wallet-rpc client with `url` endpoint. - pub fn new(url: reqwest::Url) -> Self { - Self { - inner: reqwest::Client::new(), + pub fn new(url: reqwest::Url) -> Result { + Ok(Self { + inner: reqwest::ClientBuilder::new() + .connection_verbose(true) + .build()?, base_url: url, - } + }) } /// Transfers `amount` monero from `account_index` to `address`. diff --git a/swap/src/monero/wallet.rs b/swap/src/monero/wallet.rs index 3f9cf9d4..93af277e 100644 --- a/swap/src/monero/wallet.rs +++ b/swap/src/monero/wallet.rs @@ -25,7 +25,7 @@ pub struct Wallet { impl Wallet { /// Connect to a wallet RPC and load the given wallet by name. pub async fn open_or_create(url: Url, name: String, env_config: Config) -> Result { - let client = wallet::Client::new(url); + let client = wallet::Client::new(url)?; let open_wallet_response = client.open_wallet(name.clone()).await; if open_wallet_response.is_err() { diff --git a/swap/src/monero/wallet_rpc.rs b/swap/src/monero/wallet_rpc.rs index a2aa9582..273337ef 100644 --- a/swap/src/monero/wallet_rpc.rs +++ b/swap/src/monero/wallet_rpc.rs @@ -165,7 +165,7 @@ impl WalletRpc { } // Send a json rpc request to make sure monero_wallet_rpc is ready - Client::localhost(port).get_version().await?; + Client::localhost(port)?.get_version().await?; Ok(WalletRpcProcess { _child: child, diff --git a/swap/tests/harness/mod.rs b/swap/tests/harness/mod.rs index 58c77ea1..7acec257 100644 --- a/swap/tests/harness/mod.rs +++ b/swap/tests/harness/mod.rs @@ -44,7 +44,7 @@ where let cli = Cli::default(); let _guard = tracing_subscriber::fmt() - .with_env_filter("warn,swap=debug,monero_harness=debug,monero_rpc=debug,bitcoin_harness=info,testcontainers=info") + .with_env_filter("warn,swap=debug,monero_harness=debug,monero_rpc=debug,bitcoin_harness=info,testcontainers=info") // add `reqwest::connect::verbose=trace` if you want to logs of the RPC clients .with_test_writer() .set_default(); @@ -276,7 +276,7 @@ async fn init_test_wallets( .unwrap(); let xmr_wallet = swap::monero::Wallet::connect( - monero.wallet(name).unwrap().client(), + monero.wallet(name).unwrap().client().clone(), name.to_string(), env_config, ) From 64729ffecce4914bb19ae078ad8724f329d6d851 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Mon, 19 Apr 2021 17:19:03 +1000 Subject: [PATCH 13/15] Don't make tag configurable if we never use that --- monero-harness/src/image.rs | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/monero-harness/src/image.rs b/monero-harness/src/image.rs index f0cfe105..d44ce6d3 100644 --- a/monero-harness/src/image.rs +++ b/monero-harness/src/image.rs @@ -11,7 +11,6 @@ pub const WALLET_RPC_PORT: u16 = 48083; #[derive(Debug)] pub struct Monero { - tag: String, args: Args, entrypoint: Option, wait_for_message: String, @@ -24,7 +23,7 @@ impl Image for Monero { type EntryPoint = str; fn descriptor(&self) -> String { - format!("xmrto/monero:{}", self.tag) + "xmrto/monero:v0.17.2.0".to_owned() } fn wait_until_ready(&self, container: &Container<'_, D, Self>) { @@ -75,7 +74,6 @@ impl Image for Monero { impl Default for Monero { fn default() -> Self { Monero { - tag: "v0.17.2.0".into(), args: Args::default(), entrypoint: Some("".into()), wait_for_message: "core RPC server started ok".to_string(), @@ -84,13 +82,6 @@ impl Default for Monero { } impl Monero { - pub fn with_tag(self, tag_str: &str) -> Self { - Monero { - tag: tag_str.to_string(), - ..self - } - } - pub fn wallet(name: &str, daemon_address: String) -> Self { let wallet = WalletArgs::new(name, daemon_address, WALLET_RPC_PORT); let default = Monero::default(); From 8d76607343bfbbab24dda4bf7d8cf17e6906f77c Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Mon, 19 Apr 2021 17:26:11 +1000 Subject: [PATCH 14/15] Refactor monero-harness containers 1. Split up image::Monero into Monerod and MoneroWalletRpc 2. Don't use `bash` to run the internal command. Instead we disable the entrypoint script as per https://github.com/XMRto/monero#raw-commands 3. Remove the start up delay by listening for the correct log message. To make this more resilient, we make the log level NOT configurable and instead always log verbosely. --- .github/workflows/ci.yml | 2 - Cargo.lock | 1 - monero-harness/Cargo.toml | 1 - monero-harness/src/image.rs | 269 +++++++++++++++----------------- monero-harness/src/lib.rs | 60 ++++--- monero-harness/tests/monerod.rs | 2 +- monero-harness/tests/wallet.rs | 3 +- swap/tests/happy_path.rs | 2 - swap/tests/harness/mod.rs | 26 ++- 9 files changed, 161 insertions(+), 205 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9c9b1cc8..ac711da9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -131,5 +131,3 @@ jobs: - name: Run test ${{ matrix.test_name }} run: cargo test --package swap --all-features --test ${{ matrix.test_name }} "" - env: - MONERO_ADDITIONAL_SLEEP_PERIOD: 60000 diff --git a/Cargo.lock b/Cargo.lock index 261d3600..ac0a1f30 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2167,7 +2167,6 @@ dependencies = [ "anyhow", "futures", "monero-rpc", - "port_check", "rand 0.7.3", "spectral", "testcontainers 0.12.0", diff --git a/monero-harness/Cargo.toml b/monero-harness/Cargo.toml index fa57f1a9..0b090d0c 100644 --- a/monero-harness/Cargo.toml +++ b/monero-harness/Cargo.toml @@ -8,7 +8,6 @@ edition = "2018" anyhow = "1" futures = "0.3" monero-rpc = { path = "../monero-rpc" } -port_check = "0.1" rand = "0.7" spectral = "0.6" testcontainers = "0.12" diff --git a/monero-harness/src/image.rs b/monero-harness/src/image.rs index d44ce6d3..4e1ee8bc 100644 --- a/monero-harness/src/image.rs +++ b/monero-harness/src/image.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, env::var, thread::sleep, time::Duration}; +use std::collections::HashMap; use testcontainers::{ core::{Container, Docker, WaitForMessage}, Image, @@ -6,18 +6,22 @@ use testcontainers::{ pub const MONEROD_DAEMON_CONTAINER_NAME: &str = "monerod"; pub const MONEROD_DEFAULT_NETWORK: &str = "monero_network"; -pub const MONEROD_RPC_PORT: u16 = 48081; -pub const WALLET_RPC_PORT: u16 = 48083; + +/// The port we use for all RPC communication. +/// +/// This is the default when running monerod. +/// For `monero-wallet-rpc` we always need to specify a port. To make things +/// simpler, we just specify the same one. They are in different containers so +/// this doesn't matter. +pub const RPC_PORT: u16 = 18081; #[derive(Debug)] -pub struct Monero { - args: Args, - entrypoint: Option, - wait_for_message: String, +pub struct Monerod { + args: MonerodArgs, } -impl Image for Monero { - type Args = Args; +impl Image for Monerod { + type Args = MonerodArgs; type EnvVars = HashMap; type Volumes = HashMap; type EntryPoint = str; @@ -30,17 +34,8 @@ impl Image for Monero { container .logs() .stdout - .wait_for_message(&self.wait_for_message) + .wait_for_message("JOINING all threads") .unwrap(); - - let additional_sleep_period = - var("MONERO_ADDITIONAL_SLEEP_PERIOD").map(|value| value.parse()); - - if let Ok(Ok(sleep_period)) = additional_sleep_period { - let sleep_period = Duration::from_millis(sleep_period); - - sleep(sleep_period) - } } fn args(&self) -> ::Args { @@ -56,69 +51,80 @@ impl Image for Monero { } fn with_args(self, args: ::Args) -> Self { - Monero { args, ..self } - } - - fn with_entrypoint(self, entrypoint: &Self::EntryPoint) -> Self { - Self { - entrypoint: Some(entrypoint.to_string()), - ..self - } + Self { args } } fn entrypoint(&self) -> Option { - self.entrypoint.to_owned() + Some("".to_owned()) // an empty entrypoint disables the entrypoint + // script and gives us full control } } -impl Default for Monero { - fn default() -> Self { - Monero { - args: Args::default(), - entrypoint: Some("".into()), - wait_for_message: "core RPC server started ok".to_string(), - } - } -} - -impl Monero { - pub fn wallet(name: &str, daemon_address: String) -> Self { - let wallet = WalletArgs::new(name, daemon_address, WALLET_RPC_PORT); - let default = Monero::default(); - Self { - args: Args { - image_args: ImageArgs::WalletArgs(wallet), - }, - wait_for_message: "Run server thread name: RPC".to_string(), - ..default - } - } -} - -#[derive(Clone, Debug)] -pub struct Args { - image_args: ImageArgs, -} - -impl Default for Args { +impl Default for Monerod { fn default() -> Self { Self { - image_args: ImageArgs::MonerodArgs(MonerodArgs::default()), + args: MonerodArgs::default(), } } } -#[derive(Clone, Debug)] -pub enum ImageArgs { - MonerodArgs(MonerodArgs), - WalletArgs(WalletArgs), +#[derive(Debug)] +pub struct MoneroWalletRpc { + args: MoneroWalletRpcArgs, } -impl ImageArgs { - fn args(&self) -> String { - match self { - ImageArgs::MonerodArgs(monerod_args) => monerod_args.args(), - ImageArgs::WalletArgs(wallet_args) => wallet_args.args(), +impl Image for MoneroWalletRpc { + type Args = MoneroWalletRpcArgs; + type EnvVars = HashMap; + type Volumes = HashMap; + type EntryPoint = str; + + fn descriptor(&self) -> String { + "xmrto/monero:v0.17.2.0".to_owned() + } + + fn wait_until_ready(&self, container: &Container<'_, D, Self>) { + container + .logs() + .stdout + .wait_for_message("JOINING all threads") + .unwrap(); + } + + fn args(&self) -> ::Args { + self.args.clone() + } + + fn volumes(&self) -> Self::Volumes { + HashMap::new() + } + + fn env_vars(&self) -> Self::EnvVars { + HashMap::new() + } + + fn with_args(self, args: ::Args) -> Self { + Self { args } + } + + fn entrypoint(&self) -> Option { + Some("".to_owned()) // an empty entrypoint disables the entrypoint + // script and gives us full control + } +} + +impl Default for MoneroWalletRpc { + fn default() -> Self { + Self { + args: MoneroWalletRpcArgs::default(), + } + } +} + +impl MoneroWalletRpc { + pub fn new(name: &str, daemon_address: String) -> Self { + Self { + args: MoneroWalletRpcArgs::new(name, daemon_address), } } } @@ -129,51 +135,39 @@ pub struct MonerodArgs { pub offline: bool, pub rpc_payment_allow_free_loopback: bool, pub confirm_external_bind: bool, - pub non_interactive: bool, pub no_igd: bool, pub hide_my_port: bool, pub rpc_bind_ip: String, - pub rpc_bind_port: u16, pub fixed_difficulty: u32, pub data_dir: String, - pub log_level: u32, } -#[derive(Debug, Clone)] -pub struct WalletArgs { - pub disable_rpc_login: bool, - pub confirm_external_bind: bool, - pub wallet_dir: String, - pub rpc_bind_ip: String, - pub rpc_bind_port: u16, - pub daemon_address: String, - pub log_level: u32, -} - -/// Sane defaults for a mainnet regtest instance. impl Default for MonerodArgs { fn default() -> Self { - MonerodArgs { + Self { regtest: true, offline: true, rpc_payment_allow_free_loopback: true, confirm_external_bind: true, - non_interactive: true, no_igd: true, hide_my_port: true, rpc_bind_ip: "0.0.0.0".to_string(), - rpc_bind_port: MONEROD_RPC_PORT, fixed_difficulty: 1, data_dir: "/monero".to_string(), - log_level: 2, } } } -impl MonerodArgs { - // Return monerod args as is single string so we can pass it to bash. - fn args(&self) -> String { - let mut args = vec!["monerod".to_string()]; +impl IntoIterator for MonerodArgs { + type Item = String; + type IntoIter = ::std::vec::IntoIter; + + fn into_iter(self) -> ::IntoIter { + let mut args = vec![ + "monerod".to_string(), + "--log-level=4".to_string(), + "--non-interactive".to_string(), + ]; if self.regtest { args.push("--regtest".to_string()) @@ -191,10 +185,6 @@ impl MonerodArgs { args.push("--confirm-external-bind".to_string()) } - if self.non_interactive { - args.push("--non-interactive".to_string()) - } - if self.no_igd { args.push("--no-igd".to_string()) } @@ -204,45 +194,60 @@ impl MonerodArgs { } if !self.rpc_bind_ip.is_empty() { - args.push(format!("--rpc-bind-ip {}", self.rpc_bind_ip)); - } - - if self.rpc_bind_port != 0 { - args.push(format!("--rpc-bind-port {}", self.rpc_bind_port)); + args.push(format!("--rpc-bind-ip={}", self.rpc_bind_ip)); } if !self.data_dir.is_empty() { - args.push(format!("--data-dir {}", self.data_dir)); + args.push(format!("--data-dir={}", self.data_dir)); } if self.fixed_difficulty != 0 { - args.push(format!("--fixed-difficulty {}", self.fixed_difficulty)); + args.push(format!("--fixed-difficulty={}", self.fixed_difficulty)); } - if self.log_level != 0 { - args.push(format!("--log-level {}", self.log_level)); - } - - args.join(" ") + args.into_iter() } } -impl WalletArgs { - pub fn new(wallet_name: &str, daemon_address: String, rpc_port: u16) -> Self { - WalletArgs { +#[derive(Debug, Clone)] +pub struct MoneroWalletRpcArgs { + pub disable_rpc_login: bool, + pub confirm_external_bind: bool, + pub wallet_dir: String, + pub rpc_bind_ip: String, + pub daemon_address: String, +} + +impl Default for MoneroWalletRpcArgs { + fn default() -> Self { + unimplemented!("A default instance for `MoneroWalletRpc` doesn't make sense because we always need to connect to a node.") + } +} + +impl MoneroWalletRpcArgs { + pub fn new(wallet_name: &str, daemon_address: String) -> Self { + Self { disable_rpc_login: true, confirm_external_bind: true, wallet_dir: wallet_name.into(), rpc_bind_ip: "0.0.0.0".into(), - rpc_bind_port: rpc_port, daemon_address, - log_level: 4, } } +} - // Return monero-wallet-rpc args as is single string so we can pass it to bash. - fn args(&self) -> String { - let mut args = vec!["monero-wallet-rpc".to_string()]; +impl IntoIterator for MoneroWalletRpcArgs { + type Item = String; + type IntoIter = ::std::vec::IntoIter; + + fn into_iter(self) -> ::IntoIter { + let mut args = vec![ + "monero-wallet-rpc".to_string(), + format!("--wallet-dir={}", self.wallet_dir), + format!("--daemon-address={}", self.daemon_address), + format!("--rpc-bind-port={}", RPC_PORT), + "--log-level=4".to_string(), + ]; if self.disable_rpc_login { args.push("--disable-rpc-login".to_string()) @@ -252,40 +257,10 @@ impl WalletArgs { args.push("--confirm-external-bind".to_string()) } - if !self.wallet_dir.is_empty() { - args.push(format!("--wallet-dir {}", self.wallet_dir)); - } - if !self.rpc_bind_ip.is_empty() { - args.push(format!("--rpc-bind-ip {}", self.rpc_bind_ip)); + args.push(format!("--rpc-bind-ip={}", self.rpc_bind_ip)); } - if self.rpc_bind_port != 0 { - args.push(format!("--rpc-bind-port {}", self.rpc_bind_port)); - } - - if !self.daemon_address.is_empty() { - args.push(format!("--daemon-address {}", self.daemon_address)); - } - - if self.log_level != 0 { - args.push(format!("--log-level {}", self.log_level)); - } - - args.join(" ") - } -} - -impl IntoIterator for Args { - type Item = String; - type IntoIter = ::std::vec::IntoIter; - - fn into_iter(self) -> ::IntoIter { - vec![ - "/bin/bash".to_string(), - "-c".to_string(), - format!("{} ", self.image_args.args()), - ] - .into_iter() + args.into_iter() } } diff --git a/monero-harness/src/lib.rs b/monero-harness/src/lib.rs index c667e19e..3c7214af 100644 --- a/monero-harness/src/lib.rs +++ b/monero-harness/src/lib.rs @@ -22,17 +22,15 @@ //! Also provides standalone JSON RPC clients for monerod and monero-wallet-rpc. pub mod image; -use crate::image::{ - MONEROD_DAEMON_CONTAINER_NAME, MONEROD_DEFAULT_NETWORK, MONEROD_RPC_PORT, WALLET_RPC_PORT, -}; -use anyhow::{anyhow, bail, Result}; +use crate::image::{MONEROD_DAEMON_CONTAINER_NAME, MONEROD_DEFAULT_NETWORK, RPC_PORT}; +use anyhow::{anyhow, bail, Context, Result}; use monero_rpc::{ monerod, monerod::MonerodRpc as _, wallet::{self, GetAddress, MoneroWalletRpc as _, Refreshed, Transfer}, }; use std::time::Duration; -use testcontainers::{clients::Cli, core::Port, Container, Docker, RunArgs}; +use testcontainers::{clients::Cli, Container, Docker, RunArgs}; use tokio::time; /// How often we mine a block. @@ -57,14 +55,18 @@ impl<'c> Monero { pub async fn new( cli: &'c Cli, additional_wallets: Vec<&'static str>, - ) -> Result<(Self, Vec>)> { + ) -> Result<( + Self, + Container<'c, Cli, image::Monerod>, + Vec>, + )> { let prefix = format!("{}_", random_prefix()); let monerod_name = format!("{}{}", prefix, MONEROD_DAEMON_CONTAINER_NAME); let network = format!("{}{}", prefix, MONEROD_DEFAULT_NETWORK); tracing::info!("Starting monerod: {}", monerod_name); let (monerod, monerod_container) = Monerod::new(cli, monerod_name, network)?; - let mut containers = vec![monerod_container]; + let mut containers = vec![]; let mut wallets = vec![]; let miner = "miner"; @@ -82,7 +84,7 @@ impl<'c> Monero { containers.push(container); } - Ok((Self { monerod, wallets }, containers)) + Ok((Self { monerod, wallets }, monerod_container, containers)) } pub fn monerod(&self) -> &Monerod { @@ -194,19 +196,15 @@ impl<'c> Monerod { cli: &'c Cli, name: String, network: String, - ) -> Result<(Self, Container<'c, Cli, image::Monero>)> { - let monerod_rpc_port: u16 = - port_check::free_local_port().ok_or_else(|| anyhow!("Could not retrieve free port"))?; - - let image = image::Monero::default(); + ) -> Result<(Self, Container<'c, Cli, image::Monerod>)> { + let image = image::Monerod::default(); let run_args = RunArgs::default() .with_name(name.clone()) - .with_network(network.clone()) - .with_mapped_port(Port { - local: monerod_rpc_port, - internal: MONEROD_RPC_PORT, - }); - let docker = cli.run_with_args(image, run_args); + .with_network(network.clone()); + let container = cli.run_with_args(image, run_args); + let monerod_rpc_port = container + .get_host_port(RPC_PORT) + .context("port not exposed")?; Ok(( Self { @@ -215,7 +213,7 @@ impl<'c> Monerod { network, client: monerod::Client::localhost(monerod_rpc_port)?, }, - docker, + container, )) } @@ -240,23 +238,19 @@ impl<'c> MoneroWalletRpc { name: &str, monerod: &Monerod, prefix: String, - ) -> Result<(Self, Container<'c, Cli, image::Monero>)> { - let wallet_rpc_port: u16 = - port_check::free_local_port().ok_or_else(|| anyhow!("Could not retrieve free port"))?; - - let daemon_address = format!("{}:{}", monerod.name, MONEROD_RPC_PORT); - let image = image::Monero::wallet(&name, daemon_address); + ) -> Result<(Self, Container<'c, Cli, image::MoneroWalletRpc>)> { + let daemon_address = format!("{}:{}", monerod.name, RPC_PORT); + let image = image::MoneroWalletRpc::new(&name, daemon_address); let network = monerod.network.clone(); let run_args = RunArgs::default() // prefix the container name so we can run multiple tests .with_name(format!("{}{}", prefix, name)) - .with_network(network.clone()) - .with_mapped_port(Port { - local: wallet_rpc_port, - internal: WALLET_RPC_PORT, - }); - let docker = cli.run_with_args(image, run_args); + .with_network(network.clone()); + let container = cli.run_with_args(image, run_args); + let wallet_rpc_port = container + .get_host_port(RPC_PORT) + .context("port not exposed")?; // create new wallet let client = wallet::Client::localhost(wallet_rpc_port)?; @@ -272,7 +266,7 @@ impl<'c> MoneroWalletRpc { network, client, }, - docker, + container, )) } diff --git a/monero-harness/tests/monerod.rs b/monero-harness/tests/monerod.rs index 03311072..d218167b 100644 --- a/monero-harness/tests/monerod.rs +++ b/monero-harness/tests/monerod.rs @@ -13,7 +13,7 @@ async fn init_miner_and_mine_to_miner_address() { .set_default(); let tc = Cli::default(); - let (monero, _monerod_container) = Monero::new(&tc, vec![]).await.unwrap(); + let (monero, _monerod_container, _wallet_containers) = Monero::new(&tc, vec![]).await.unwrap(); monero.init_and_start_miner().await.unwrap(); diff --git a/monero-harness/tests/wallet.rs b/monero-harness/tests/wallet.rs index df26d348..b25683ad 100644 --- a/monero-harness/tests/wallet.rs +++ b/monero-harness/tests/wallet.rs @@ -17,7 +17,8 @@ async fn fund_transfer_and_check_tx_key() { let send_to_bob = 5_000_000_000; let tc = Cli::default(); - let (monero, _containers) = Monero::new(&tc, vec!["alice", "bob"]).await.unwrap(); + let (monero, _monerod_container, _wallet_containers) = + Monero::new(&tc, vec!["alice", "bob"]).await.unwrap(); let alice_wallet = monero.wallet("alice").unwrap(); let bob_wallet = monero.wallet("bob").unwrap(); diff --git a/swap/tests/happy_path.rs b/swap/tests/happy_path.rs index 5f1ac5b3..2f50ff14 100644 --- a/swap/tests/happy_path.rs +++ b/swap/tests/happy_path.rs @@ -4,8 +4,6 @@ use harness::SlowCancelConfig; use swap::protocol::{alice, bob}; use tokio::join; -/// Run the following tests with RUST_MIN_STACK=10000000 - #[tokio::test] async fn happy_path() { harness::setup_test(SlowCancelConfig, |mut ctx| async move { diff --git a/swap/tests/harness/mod.rs b/swap/tests/harness/mod.rs index 7acec257..09ef421a 100644 --- a/swap/tests/harness/mod.rs +++ b/swap/tests/harness/mod.rs @@ -150,11 +150,16 @@ async fn init_containers(cli: &Cli) -> (Monero, Containers<'_>) { let electrs = init_electrs_container(&cli, prefix.clone(), bitcoind_name, prefix) .await .expect("could not init electrs"); - let (monero, monerods) = init_monero_container(&cli).await; + let (monero, monerod_container, monero_wallet_rpc_containers) = + Monero::new(&cli, vec![MONERO_WALLET_NAME_ALICE, MONERO_WALLET_NAME_BOB]) + .await + .unwrap(); + (monero, Containers { bitcoind_url, bitcoind, - monerods, + monerod_container, + monero_wallet_rpc_containers, electrs, }) } @@ -189,20 +194,6 @@ async fn init_bitcoind_container( Ok((docker, bitcoind_url.clone())) } -async fn init_monero_container( - cli: &Cli, -) -> ( - Monero, - Vec>, -) { - let (monero, monerods) = - Monero::new(&cli, vec![MONERO_WALLET_NAME_ALICE, MONERO_WALLET_NAME_BOB]) - .await - .unwrap(); - - (monero, monerods) -} - pub async fn init_electrs_container( cli: &Cli, volume: String, @@ -892,7 +883,8 @@ pub async fn mint(node_url: Url, address: bitcoin::Address, amount: bitcoin::Amo struct Containers<'a> { bitcoind_url: Url, bitcoind: Container<'a, Cli, bitcoind::Bitcoind>, - monerods: Vec>, + monerod_container: Container<'a, Cli, image::Monerod>, + monero_wallet_rpc_containers: Vec>, electrs: Container<'a, Cli, electrs::Electrs>, } From 101483118a97a8d24bbc619bf3bad538c9745c40 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 20 Apr 2021 13:03:59 +1000 Subject: [PATCH 15/15] Always display log output in CI --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ac711da9..13d9ffdf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -130,4 +130,4 @@ jobs: - uses: Swatinem/rust-cache@v1.2.0 - name: Run test ${{ matrix.test_name }} - run: cargo test --package swap --all-features --test ${{ matrix.test_name }} "" + run: cargo test --package swap --all-features --test ${{ matrix.test_name }} -- --nocapture