diff --git a/monero-harness/Cargo.toml b/monero-harness/Cargo.toml index 226866f7..060df781 100644 --- a/monero-harness/Cargo.toml +++ b/monero-harness/Cargo.toml @@ -6,6 +6,7 @@ edition = "2018" [dependencies] anyhow = "1" +digest_auth = "0.2.3" futures = "0.3" port_check = "0.1" rand = "0.7" @@ -13,7 +14,7 @@ reqwest = { version = "0.10", default-features = false, features = ["json", "nat serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" spectral = "0.6" -testcontainers = "0.10" +testcontainers = "0.11" tokio = { version = "0.2", default-features = false, features = ["blocking", "macros", "rt-core", "time"] } tracing = "0.1" url = "2" diff --git a/monero-harness/src/image.rs b/monero-harness/src/image.rs index 1998b4bc..a491a345 100644 --- a/monero-harness/src/image.rs +++ b/monero-harness/src/image.rs @@ -4,10 +4,10 @@ use testcontainers::{ Image, }; +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 MINER_WALLET_RPC_PORT: u16 = 48083; -pub const ALICE_WALLET_RPC_PORT: u16 = 48084; -pub const BOB_WALLET_RPC_PORT: u16 = 48085; +pub const WALLET_RPC_PORT: u16 = 48083; #[derive(Debug)] pub struct Monero { @@ -15,6 +15,7 @@ pub struct Monero { args: Args, ports: Option>, entrypoint: Option, + wait_for_message: String, } impl Image for Monero { @@ -31,9 +32,7 @@ impl Image for Monero { container .logs() .stdout - .wait_for_message( - "The daemon is running offline and will not attempt to sync to the Monero network", - ) + .wait_for_message(&self.wait_for_message) .unwrap(); let additional_sleep_period = @@ -85,6 +84,7 @@ impl Default for Monero { args: Args::default(), ports: None, entrypoint: Some("".into()), + wait_for_message: "core RPC server started ok".to_string(), } } } @@ -104,24 +104,45 @@ impl Monero { self } - pub fn with_wallet(self, name: &str, rpc_port: u16) -> Self { - let wallet = WalletArgs::new(name, rpc_port); - let mut wallet_args = self.args.wallets; - wallet_args.push(wallet); + 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 { - monerod: self.args.monerod, - wallets: wallet_args, + image_args: ImageArgs::WalletArgs(wallet), }, - ..self + wait_for_message: "Run server thread name: RPC".to_string(), + ..default } } } -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug)] pub struct Args { - monerod: MonerodArgs, - wallets: Vec, + image_args: ImageArgs, +} + +impl Default for Args { + fn default() -> Self { + Self { + image_args: ImageArgs::MonerodArgs(MonerodArgs::default()), + } + } +} + +#[derive(Clone, Debug)] +pub enum ImageArgs { + MonerodArgs(MonerodArgs), + WalletArgs(WalletArgs), +} + +impl ImageArgs { + fn args(&self) -> String { + match self { + ImageArgs::MonerodArgs(monerod_args) => monerod_args.args(), + ImageArgs::WalletArgs(wallet_args) => wallet_args.args(), + } + } } #[derive(Debug, Clone)] @@ -137,6 +158,7 @@ pub struct MonerodArgs { pub rpc_bind_port: u16, pub fixed_difficulty: u32, pub data_dir: String, + pub log_level: u32, } #[derive(Debug, Clone)] @@ -165,6 +187,7 @@ impl Default for MonerodArgs { rpc_bind_port: MONEROD_RPC_PORT, fixed_difficulty: 1, data_dir: "/monero".to_string(), + log_level: 2, } } } @@ -218,17 +241,20 @@ impl MonerodArgs { args.push(format!("--fixed-difficulty {}", self.fixed_difficulty)); } + if self.log_level != 0 { + args.push(format!("--log-level {}", self.log_level)); + } + args.join(" ") } } impl WalletArgs { - pub fn new(wallet_dir: &str, rpc_port: u16) -> Self { - let daemon_address = format!("localhost:{}", MONEROD_RPC_PORT); + pub fn new(wallet_name: &str, daemon_address: String, rpc_port: u16) -> Self { WalletArgs { disable_rpc_login: true, confirm_external_bind: true, - wallet_dir: wallet_dir.into(), + wallet_dir: wallet_name.into(), rpc_bind_ip: "0.0.0.0".into(), rpc_bind_port: rpc_port, daemon_address, @@ -282,10 +308,7 @@ impl IntoIterator for Args { args.push("/bin/bash".into()); args.push("-c".into()); - let wallet_args: Vec = self.wallets.iter().map(|wallet| wallet.args()).collect(); - let wallet_args = wallet_args.join(" & "); - - let cmd = format!("{} & {} ", self.monerod.args(), wallet_args); + let cmd = format!("{} ", self.image_args.args()); args.push(cmd); args.into_iter() diff --git a/monero-harness/src/lib.rs b/monero-harness/src/lib.rs index 4c7e262b..7d42a586 100644 --- a/monero-harness/src/lib.rs +++ b/monero-harness/src/lib.rs @@ -24,17 +24,18 @@ pub mod image; pub mod rpc; -use anyhow::{anyhow, Result}; -use serde::Deserialize; +use anyhow::{anyhow, bail, Result}; use std::time::Duration; -use testcontainers::{clients::Cli, core::Port, Container, Docker}; +use testcontainers::{clients::Cli, core::Port, Container, Docker, RunArgs}; use tokio::time; use crate::{ - image::{ALICE_WALLET_RPC_PORT, BOB_WALLET_RPC_PORT, MINER_WALLET_RPC_PORT, MONEROD_RPC_PORT}, + image::{ + MONEROD_DAEMON_CONTAINER_NAME, MONEROD_DEFAULT_NETWORK, MONEROD_RPC_PORT, WALLET_RPC_PORT, + }, rpc::{ monerod, - wallet::{self, GetAddress, Transfer}, + wallet::{self, GetAddress, Refreshed, Transfer}, }, }; @@ -44,196 +45,253 @@ const BLOCK_TIME_SECS: u64 = 1; /// Poll interval when checking if the wallet has synced with monerod. const WAIT_WALLET_SYNC_MILLIS: u64 = 1000; -/// Wallet sub-account indices. -const ACCOUNT_INDEX_PRIMARY: u32 = 0; - -#[derive(Copy, Clone, Debug)] +#[derive(Clone, Debug)] pub struct Monero { - monerod_rpc_port: u16, - miner_wallet_rpc_port: u16, - alice_wallet_rpc_port: u16, - bob_wallet_rpc_port: u16, + monerod: Monerod, + wallets: Vec, + prefix: String, } - impl<'c> Monero { - /// Starts a new regtest monero container. - pub fn new(cli: &'c Cli) -> 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 miner_wallet_rpc_port: u16 = - port_check::free_local_port().ok_or_else(|| anyhow!("Could not retrieve free port"))?; - let alice_wallet_rpc_port: u16 = - port_check::free_local_port().ok_or_else(|| anyhow!("Could not retrieve free port"))?; - let bob_wallet_rpc_port: u16 = - port_check::free_local_port().ok_or_else(|| anyhow!("Could not retrieve free port"))?; + /// Starts a new regtest monero container setup consisting out of 1 monerod + /// node and n wallets. The containers and network will be prefixed, either + /// randomly generated or as defined in `prefix` if provided. There will + /// be 1 miner wallet started automatically. Default monerod container + /// name will be: `prefix`_`monerod` Default miner wallet container name + /// will be: `prefix`_`miner` Default network will be: `prefix`_`monero` + pub async fn new( + cli: &'c Cli, + prefix: Option, + additional_wallets: Vec, + ) -> Result<(Self, Vec>)> { + let prefix = format!("{}_", prefix.unwrap_or_else(random_prefix)); - let image = image::Monero::default() - .with_mapped_port(Port { - local: monerod_rpc_port, - internal: MONEROD_RPC_PORT, - }) - .with_mapped_port(Port { - local: miner_wallet_rpc_port, - internal: MINER_WALLET_RPC_PORT, - }) - .with_wallet("miner", MINER_WALLET_RPC_PORT) - .with_mapped_port(Port { - local: alice_wallet_rpc_port, - internal: ALICE_WALLET_RPC_PORT, - }) - .with_wallet("alice", ALICE_WALLET_RPC_PORT) - .with_mapped_port(Port { - local: bob_wallet_rpc_port, - internal: BOB_WALLET_RPC_PORT, - }) - .with_wallet("bob", BOB_WALLET_RPC_PORT); + let monerod_name = format!("{}{}", prefix, MONEROD_DAEMON_CONTAINER_NAME); + let network = format!("{}{}", prefix, MONEROD_DEFAULT_NETWORK); - println!("running image ..."); - let docker = cli.run(image); - println!("image ran"); + tracing::info!("Starting monerod... {}", monerod_name); + let (monerod, monerod_container) = Monerod::new(cli, monerod_name, network)?; + let mut containers = vec![monerod_container]; + let mut wallets = vec![]; + + let miner = format!("{}{}", prefix, "miner"); + tracing::info!("Starting miner wallet... {}", miner); + let (miner_wallet, miner_container) = MoneroWalletRpc::new(cli, &miner, &monerod).await?; + + wallets.push(miner_wallet); + containers.push(miner_container); + for wallet in additional_wallets.iter() { + tracing::info!("Starting wallet: {}...", wallet); + let wallet = format!("{}{}", prefix, wallet); + let (wallet, container) = MoneroWalletRpc::new(cli, &wallet, &monerod).await?; + wallets.push(wallet); + containers.push(container); + } Ok(( Self { - monerod_rpc_port, - miner_wallet_rpc_port, - alice_wallet_rpc_port, - bob_wallet_rpc_port, + monerod, + wallets, + prefix, + }, + containers, + )) + } + + pub fn monerod(&self) -> &Monerod { + &self.monerod + } + + pub fn wallet(&self, name: &str) -> Result<&MoneroWalletRpc> { + let name = format!("{}{}", self.prefix, name); + let wallet = self + .wallets + .iter() + .find(|wallet| wallet.name.eq(&name)) + .ok_or_else(|| anyhow!("Could not find wallet container."))?; + + Ok(wallet) + } + + pub async fn init(&self, wallet_amount: Vec<(&str, u64)>) -> Result<()> { + let miner_wallet = self.wallet("miner")?; + let miner_address = miner_wallet.address().await?.address; + + // generate the first 70 as bulk + let monerod = &self.monerod; + let block = monerod.client().generate_blocks(70, &miner_address).await?; + tracing::info!("Generated {:?} blocks", block); + miner_wallet.refresh().await?; + + for (wallet, amount) in wallet_amount.iter() { + if *amount > 0 { + let wallet = self.wallet(wallet)?; + let address = wallet.address().await?.address; + miner_wallet.transfer(&address, *amount).await?; + tracing::info!("Funded {} wallet with {}", wallet.name, amount); + monerod.client().generate_blocks(10, &miner_address).await?; + wallet.refresh().await?; + } + } + + monerod.start_miner(&miner_address).await?; + + tracing::info!("Waiting for miner wallet to catch up..."); + let block_height = monerod.client().get_block_count().await?; + miner_wallet + .wait_for_wallet_height(block_height) + .await + .unwrap(); + + Ok(()) + } +} + +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 +} + +#[derive(Clone, Debug)] +pub struct Monerod { + rpc_port: u16, + name: String, + network: String, +} + +#[derive(Clone, Debug)] +pub struct MoneroWalletRpc { + rpc_port: u16, + name: String, + network: String, +} + +impl<'c> Monerod { + /// Starts a new regtest monero container. + fn new( + 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().with_mapped_port(Port { + local: monerod_rpc_port, + internal: MONEROD_RPC_PORT, + }); + let run_args = RunArgs::default() + .with_name(name.clone()) + .with_network(network.clone()); + let docker = cli.run_with_args(image, run_args); + + Ok(( + Self { + rpc_port: monerod_rpc_port, + name, + network, }, docker, )) } - pub fn miner_wallet_rpc_client(&self) -> wallet::Client { - wallet::Client::localhost(self.miner_wallet_rpc_port) + pub fn client(&self) -> monerod::Client { + monerod::Client::localhost(self.rpc_port) } - pub fn alice_wallet_rpc_client(&self) -> wallet::Client { - wallet::Client::localhost(self.alice_wallet_rpc_port) - } - - pub fn bob_wallet_rpc_client(&self) -> wallet::Client { - wallet::Client::localhost(self.bob_wallet_rpc_port) - } - - pub fn monerod_rpc_client(&self) -> monerod::Client { - monerod::Client::localhost(self.monerod_rpc_port) - } - - /// Initialise by creating a wallet, generating some `blocks`, and starting - /// a miner thread that mines to the primary account. Also create two - /// sub-accounts, one for Alice and one for Bob. If alice/bob_funding is - /// some, the value needs to be > 0. - pub async fn init(&self, alice_funding: u64, bob_funding: u64) -> Result<()> { - let miner_wallet = self.miner_wallet_rpc_client(); - let alice_wallet = self.alice_wallet_rpc_client(); - let bob_wallet = self.bob_wallet_rpc_client(); - let monerod = self.monerod_rpc_client(); - - miner_wallet.create_wallet("miner_wallet").await?; - alice_wallet.create_wallet("alice_wallet").await?; - bob_wallet.create_wallet("bob_wallet").await?; - - let miner = self.get_address_miner().await?.address; - let alice = self.get_address_alice().await?.address; - let bob = self.get_address_bob().await?.address; - - let _ = monerod.generate_blocks(70, &miner).await?; - self.wait_for_miner_wallet_block_height().await?; - - if alice_funding > 0 { - self.fund_account(&alice, &miner, alice_funding).await?; - self.wait_for_alice_wallet_block_height().await?; - let balance = self.get_balance_alice().await?; - debug_assert!(balance == alice_funding); - } - - if bob_funding > 0 { - self.fund_account(&bob, &miner, bob_funding).await?; - self.wait_for_bob_wallet_block_height().await?; - let balance = self.get_balance_bob().await?; - debug_assert!(balance == bob_funding); - } - - let _ = tokio::spawn(mine(monerod.clone(), miner)); - + /// 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 _ = tokio::spawn(mine(monerod, miner_wallet_address.to_string())); Ok(()) } - - async fn fund_account(&self, address: &str, miner: &str, funding: u64) -> Result<()> { - let monerod = self.monerod_rpc_client(); - - self.transfer_from_primary(funding, address).await?; - let _ = monerod.generate_blocks(10, miner).await?; - self.wait_for_miner_wallet_block_height().await?; - - Ok(()) - } - - async fn wait_for_miner_wallet_block_height(&self) -> Result<()> { - self.wait_for_wallet_height(self.miner_wallet_rpc_client()) - .await - } - - pub async fn wait_for_alice_wallet_block_height(&self) -> Result<()> { - self.wait_for_wallet_height(self.alice_wallet_rpc_client()) - .await - } - - pub async fn wait_for_bob_wallet_block_height(&self) -> Result<()> { - self.wait_for_wallet_height(self.bob_wallet_rpc_client()) - .await - } - - // It takes a little while for the wallet to sync with monerod. - async fn wait_for_wallet_height(&self, wallet: wallet::Client) -> Result<()> { - let monerod = self.monerod_rpc_client(); - let height = monerod.get_block_count().await?; - - while wallet.block_height().await?.height < height { - time::delay_for(Duration::from_millis(WAIT_WALLET_SYNC_MILLIS)).await; - } - Ok(()) - } - - /// Get addresses for the primary account. - async fn get_address_miner(&self) -> Result { - let wallet = self.miner_wallet_rpc_client(); - wallet.get_address(ACCOUNT_INDEX_PRIMARY).await - } - - /// Get addresses for the Alice's account. - async fn get_address_alice(&self) -> Result { - let wallet = self.alice_wallet_rpc_client(); - wallet.get_address(ACCOUNT_INDEX_PRIMARY).await - } - - /// Get addresses for the Bob's account. - async fn get_address_bob(&self) -> Result { - let wallet = self.bob_wallet_rpc_client(); - wallet.get_address(ACCOUNT_INDEX_PRIMARY).await - } - - /// Gets the balance of Alice's account. - async fn get_balance_alice(&self) -> Result { - let wallet = self.alice_wallet_rpc_client(); - wallet.get_balance(ACCOUNT_INDEX_PRIMARY).await - } - - /// Gets the balance of Bob's account. - async fn get_balance_bob(&self) -> Result { - let wallet = self.bob_wallet_rpc_client(); - wallet.get_balance(ACCOUNT_INDEX_PRIMARY).await - } - - /// Transfers moneroj from the primary account. - async fn transfer_from_primary(&self, amount: u64, address: &str) -> Result { - let wallet = self.miner_wallet_rpc_client(); - wallet - .transfer(ACCOUNT_INDEX_PRIMARY, amount, address) - .await - } } +impl<'c> MoneroWalletRpc { + /// Starts a new wallet container which is attached to + /// MONEROD_DEFAULT_NETWORK and MONEROD_DAEMON_CONTAINER_NAME + async fn new( + cli: &'c Cli, + name: &str, + monerod: &Monerod, + ) -> 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).with_mapped_port(Port { + local: wallet_rpc_port, + internal: WALLET_RPC_PORT, + }); + + let network = monerod.network.clone(); + let run_args = RunArgs::default() + .with_name(name) + .with_network(network.clone()); + let docker = cli.run_with_args(image, run_args); + + // create new wallet + wallet::Client::localhost(wallet_rpc_port) + .create_wallet(name) + .await + .unwrap(); + + Ok(( + Self { + rpc_port: wallet_rpc_port, + name: name.to_string(), + network, + }, + docker, + )) + } + + pub fn client(&self) -> wallet::Client { + wallet::Client::localhost(self.rpc_port) + } + + // 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 { + if retry >= 30 { + // ~30 seconds + bail!("Wallet could not catch up with monerod after 30 retries.") + } + time::delay_for(Duration::from_millis(WAIT_WALLET_SYNC_MILLIS)).await; + retry += 1; + } + Ok(()) + } + + /// Sends amount to address + pub async fn transfer(&self, address: &str, amount: u64) -> Result { + self.client().transfer(0, amount, address).await + } + + pub async fn address(&self) -> Result { + self.client().get_address(0).await + } + + pub async fn balance(&self) -> Result { + self.client().refresh().await?; + self.client().get_balance(0).await + } + + pub async fn refresh(&self) -> Result { + self.client().refresh().await + } +} /// Mine a block ever BLOCK_TIME_SECS seconds. async fn mine(monerod: monerod::Client, reward_address: String) -> Result<()> { loop { @@ -241,22 +299,3 @@ async fn mine(monerod: monerod::Client, reward_address: String) -> Result<()> { monerod.generate_blocks(1, &reward_address).await?; } } - -// 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-harness/src/rpc/monerod.rs b/monero-harness/src/rpc/monerod.rs index aa00a62d..e27f2556 100644 --- a/monero-harness/src/rpc/monerod.rs +++ b/monero-harness/src/rpc/monerod.rs @@ -1,7 +1,4 @@ -use crate::{ - rpc::{Request, Response}, - BlockHeader, -}; +use crate::rpc::{Request, Response}; use anyhow::Result; use reqwest::Url; @@ -36,11 +33,23 @@ impl Client { 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(self.url.clone()) + .post(url) .json(&request) .send() .await? @@ -125,3 +134,22 @@ 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-harness/src/rpc/wallet.rs b/monero-harness/src/rpc/wallet.rs index a81702ac..97c2804d 100644 --- a/monero-harness/src/rpc/wallet.rs +++ b/monero-harness/src/rpc/wallet.rs @@ -264,6 +264,24 @@ impl Client { let r: Response = 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: Response = serde_json::from_str(&response)?; + Ok(r.result) + } } #[derive(Serialize, Debug, Clone)] @@ -393,3 +411,9 @@ 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, +} diff --git a/monero-harness/tests/client.rs b/monero-harness/tests/client.rs deleted file mode 100644 index 15e0a19f..00000000 --- a/monero-harness/tests/client.rs +++ /dev/null @@ -1,31 +0,0 @@ -use monero_harness::Monero; -use spectral::prelude::*; -use testcontainers::clients::Cli; - -const ALICE_FUND_AMOUNT: u64 = 1_000_000_000_000; -const BOB_FUND_AMOUNT: u64 = 0; - -#[tokio::test] -async fn init_accounts_for_alice_and_bob() { - let tc = Cli::default(); - let (monero, _container) = Monero::new(&tc).unwrap(); - monero - .init(ALICE_FUND_AMOUNT, BOB_FUND_AMOUNT) - .await - .unwrap(); - - let got_balance_alice = monero - .alice_wallet_rpc_client() - .get_balance(0) - .await - .expect("failed to get alice's balance"); - - let got_balance_bob = monero - .bob_wallet_rpc_client() - .get_balance(0) - .await - .expect("failed to get bob's balance"); - - assert_that!(got_balance_alice).is_equal_to(ALICE_FUND_AMOUNT); - assert_that!(got_balance_bob).is_equal_to(BOB_FUND_AMOUNT); -} diff --git a/monero-harness/tests/monerod.rs b/monero-harness/tests/monerod.rs index 01d4e269..35f12744 100644 --- a/monero-harness/tests/monerod.rs +++ b/monero-harness/tests/monerod.rs @@ -1,21 +1,26 @@ use monero_harness::Monero; use spectral::prelude::*; +use std::time::Duration; use testcontainers::clients::Cli; - -fn init_cli() -> Cli { - Cli::default() -} +use tokio::time; #[tokio::test] -async fn connect_to_monerod() { - let tc = init_cli(); - let (monero, _container) = Monero::new(&tc).unwrap(); - let cli = monero.monerod_rpc_client(); +async fn init_miner_and_mine_to_miner_address() { + let tc = Cli::default(); + let (monero, _monerod_container) = Monero::new(&tc, None, vec![]).await.unwrap(); - let header = cli - .get_block_header_by_height(0) - .await - .expect("failed to get block 0"); + monero.init(vec![]).await.unwrap(); - assert_that!(header.height).is_equal_to(0); + let monerod = monero.monerod(); + let miner_wallet = monero.wallet("miner").unwrap(); + + let got_miner_balance = miner_wallet.balance().await.unwrap(); + assert_that!(got_miner_balance).is_greater_than(0); + + time::delay_for(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(); + + assert_that(&block_height).is_greater_than(70); } diff --git a/monero-harness/tests/wallet.rs b/monero-harness/tests/wallet.rs index eca88be3..44fc6ec5 100644 --- a/monero-harness/tests/wallet.rs +++ b/monero-harness/tests/wallet.rs @@ -3,86 +3,61 @@ use spectral::prelude::*; use testcontainers::clients::Cli; #[tokio::test] -async fn wallet_and_accounts() { - let tc = Cli::default(); - let (monero, _container) = Monero::new(&tc).unwrap(); - let cli = monero.miner_wallet_rpc_client(); - - println!("creating wallet ..."); - - let _ = cli - .create_wallet("wallet") - .await - .expect("failed to create wallet"); - - let got = cli.get_balance(0).await.expect("failed to get balance"); - let want = 0; - - assert_that!(got).is_equal_to(want); -} - -#[tokio::test] -async fn create_account_and_retrieve_it() { - let tc = Cli::default(); - let (monero, _container) = Monero::new(&tc).unwrap(); - let cli = monero.miner_wallet_rpc_client(); - - let label = "Iron Man"; // This is intentionally _not_ Alice or Bob. - - let _ = cli - .create_wallet("wallet") - .await - .expect("failed to create wallet"); - - let _ = cli - .create_account(label) - .await - .expect("failed to create account"); - - let mut found: bool = false; - let accounts = cli - .get_accounts("") // Empty filter. - .await - .expect("failed to get accounts"); - for account in accounts.subaddress_accounts { - if account.label == label { - found = true; - } - } - assert!(found); -} - -#[tokio::test] -async fn transfer_and_check_tx_key() { - let fund_alice = 1_000_000_000_000; +async fn fund_transfer_and_check_tx_key() { + let fund_alice: u64 = 1_000_000_000_000; let fund_bob = 0; + let send_to_bob = 5_000_000_000; let tc = Cli::default(); - let (monero, _container) = Monero::new(&tc).unwrap(); - let _ = monero.init(fund_alice, fund_bob).await; + let (monero, _containers) = Monero::new(&tc, Some("test_".to_string()), vec![ + "alice".to_string(), + "bob".to_string(), + ]) + .await + .unwrap(); + let alice_wallet = monero.wallet("alice").unwrap(); + let bob_wallet = monero.wallet("bob").unwrap(); + let miner_wallet = monero.wallet("miner").unwrap(); - let address_bob = monero - .bob_wallet_rpc_client() - .get_address(0) + let miner_address = miner_wallet.address().await.unwrap().address; + + // fund alice + monero + .init(vec![("alice", fund_alice), ("bob", fund_bob)]) .await - .expect("failed to get Bob's address") - .address; + .unwrap(); - let transfer_amount = 100; - let transfer = monero - .alice_wallet_rpc_client() - .transfer(0, transfer_amount, &address_bob) + // check alice balance + alice_wallet.refresh().await.unwrap(); + let got_alice_balance = alice_wallet.balance().await.unwrap(); + assert_that(&got_alice_balance).is_equal_to(fund_alice); + + // transfer from alice to bob + let bob_address = bob_wallet.address().await.unwrap().address; + let transfer = alice_wallet + .transfer(&bob_address, send_to_bob) .await - .expect("transfer failed"); + .unwrap(); + monero + .monerod() + .client() + .generate_blocks(10, &miner_address) + .await + .unwrap(); + + bob_wallet.refresh().await.unwrap(); + let got_bob_balance = bob_wallet.balance().await.unwrap(); + assert_that(&got_bob_balance).is_equal_to(send_to_bob); + + // check if tx was actually seen let tx_id = transfer.tx_hash; let tx_key = transfer.tx_key; - - let cli = monero.miner_wallet_rpc_client(); - let res = cli - .check_tx_key(&tx_id, &tx_key, &address_bob) + let res = bob_wallet + .client() + .check_tx_key(&tx_id, &tx_key, &bob_address) .await .expect("failed to check tx by key"); - assert_that!(res.received).is_equal_to(transfer_amount); + assert_that!(res.received).is_equal_to(send_to_bob); } diff --git a/swap/Cargo.toml b/swap/Cargo.toml index a92615f4..af99bcba 100644 --- a/swap/Cargo.toml +++ b/swap/Cargo.toml @@ -12,7 +12,7 @@ atty = "0.2" backoff = { version = "0.2", features = ["tokio"] } base64 = "0.12" bitcoin = { version = "0.23", features = ["rand", "use-serde"] } # TODO: Upgrade other crates in this repo to use this version. -bitcoin-harness = { git = "https://github.com/coblox/bitcoin-harness-rs", rev = "f1bbe6a4540d0741f1f4f22577cfeeadbfd7aaaf" } +bitcoin-harness = { git = "https://github.com/coblox/bitcoin-harness-rs", rev = "3be644cd9512c157d3337a189298b8257ed54d04" } derivative = "2" futures = { version = "0.3", default-features = false } genawaiter = "0.99.1" @@ -47,4 +47,4 @@ hyper = "0.13" port_check = "0.1" spectral = "0.6" tempfile = "3" -testcontainers = "0.10" +testcontainers = "0.11" diff --git a/swap/tests/e2e.rs b/swap/tests/e2e.rs index cb49ef05..9c8eda1b 100644 --- a/swap/tests/e2e.rs +++ b/swap/tests/e2e.rs @@ -51,11 +51,21 @@ mod e2e_test { .await .unwrap(); - let (monero, _container) = Monero::new(&cli).unwrap(); - monero.init(xmr_alice, xmr_bob).await.unwrap(); + let (monero, _container) = Monero::new(&cli, Some("swap_".to_string()), vec![ + "alice".to_string(), + "bob".to_string(), + ]) + .await + .unwrap(); + monero + .init(vec![("alice", xmr_alice), ("bob", xmr_bob)]) + .await + .unwrap(); - let alice_xmr_wallet = Arc::new(swap::monero::Wallet(monero.alice_wallet_rpc_client())); - let bob_xmr_wallet = Arc::new(swap::monero::Wallet(monero.bob_wallet_rpc_client())); + let alice_xmr_wallet = Arc::new(swap::monero::Wallet( + monero.wallet("alice").unwrap().client(), + )); + let bob_xmr_wallet = Arc::new(swap::monero::Wallet(monero.wallet("bob").unwrap().client())); let alice_behaviour = alice::Alice::default(); let alice_transport = build(alice_behaviour.identity()).unwrap(); @@ -92,7 +102,7 @@ mod e2e_test { let xmr_alice_final = alice_xmr_wallet.as_ref().get_balance().await.unwrap(); - monero.wait_for_bob_wallet_block_height().await.unwrap(); + bob_xmr_wallet.as_ref().0.refresh().await.unwrap(); let xmr_bob_final = bob_xmr_wallet.as_ref().get_balance().await.unwrap(); assert_eq!( diff --git a/xmr-btc/Cargo.toml b/xmr-btc/Cargo.toml index 7d13407d..40373456 100644 --- a/xmr-btc/Cargo.toml +++ b/xmr-btc/Cargo.toml @@ -28,12 +28,12 @@ tracing = "0.1" [dev-dependencies] backoff = { version = "0.2", features = ["tokio"] } base64 = "0.12" -bitcoin-harness = { git = "https://github.com/coblox/bitcoin-harness-rs", rev = "f1bbe6a4540d0741f1f4f22577cfeeadbfd7aaaf" } +bitcoin-harness = { git = "https://github.com/coblox/bitcoin-harness-rs", rev = "3be644cd9512c157d3337a189298b8257ed54d04" } futures = "0.3" monero-harness = { path = "../monero-harness" } reqwest = { version = "0.10", default-features = false } serde_cbor = "0.11" tempfile = "3" -testcontainers = "0.10" +testcontainers = "0.11" tracing = "0.1" tracing-subscriber = "0.2" diff --git a/xmr-btc/tests/e2e.rs b/xmr-btc/tests/e2e.rs index 72795fb7..c20655b2 100644 --- a/xmr-btc/tests/e2e.rs +++ b/xmr-btc/tests/e2e.rs @@ -32,7 +32,12 @@ mod tests { .set_default(); let cli = Cli::default(); - let (monero, _container) = Monero::new(&cli).unwrap(); + let (monero, _container) = Monero::new(&cli, Some("hp".to_string()), vec![ + "alice".to_string(), + "bob".to_string(), + ]) + .await + .unwrap(); let bitcoind = init_bitcoind(&cli).await; let ( @@ -75,7 +80,7 @@ mod tests { let alice_final_xmr_balance = alice_node.monero_wallet.get_balance().await.unwrap(); - monero.wait_for_bob_wallet_block_height().await.unwrap(); + monero.wallet("bob").unwrap().refresh().await.unwrap(); let bob_final_xmr_balance = bob_node.monero_wallet.get_balance().await.unwrap(); @@ -106,7 +111,12 @@ mod tests { .set_default(); let cli = Cli::default(); - let (monero, _container) = Monero::new(&cli).unwrap(); + let (monero, _container) = Monero::new(&cli, Some("br".to_string()), vec![ + "alice".to_string(), + "bob".to_string(), + ]) + .await + .unwrap(); let bitcoind = init_bitcoind(&cli).await; let ( @@ -158,7 +168,7 @@ mod tests { .await .unwrap(); - monero.wait_for_alice_wallet_block_height().await.unwrap(); + monero.wallet("alice").unwrap().refresh().await.unwrap(); let alice_final_xmr_balance = alice_node.monero_wallet.get_balance().await.unwrap(); let bob_final_xmr_balance = bob_node.monero_wallet.get_balance().await.unwrap(); @@ -182,7 +192,13 @@ mod tests { .set_default(); let cli = Cli::default(); - let (monero, _container) = Monero::new(&cli).unwrap(); + let (monero, _containers) = Monero::new(&cli, Some("ap".to_string()), vec![ + "alice".to_string(), + "bob".to_string(), + ]) + .await + .unwrap(); + let bitcoind = init_bitcoind(&cli).await; let ( diff --git a/xmr-btc/tests/harness/mod.rs b/xmr-btc/tests/harness/mod.rs index 3d189243..3d789b8f 100644 --- a/xmr-btc/tests/harness/mod.rs +++ b/xmr-btc/tests/harness/mod.rs @@ -130,10 +130,13 @@ pub async fn init_test( let fund_alice = TEN_XMR; let fund_bob = 0; - monero.init(fund_alice, fund_bob).await.unwrap(); + monero + .init(vec![("alice", fund_alice), ("bob", fund_bob)]) + .await + .unwrap(); - let alice_monero_wallet = wallet::monero::Wallet(monero.alice_wallet_rpc_client()); - let bob_monero_wallet = wallet::monero::Wallet(monero.bob_wallet_rpc_client()); + let alice_monero_wallet = wallet::monero::Wallet(monero.wallet("alice").unwrap().client()); + let bob_monero_wallet = wallet::monero::Wallet(monero.wallet("bob").unwrap().client()); let alice_btc_wallet = wallet::bitcoin::Wallet::new("alice", &bitcoind.node_url) .await diff --git a/xmr-btc/tests/on_chain.rs b/xmr-btc/tests/on_chain.rs index e61db076..7bec0e9e 100644 --- a/xmr-btc/tests/on_chain.rs +++ b/xmr-btc/tests/on_chain.rs @@ -238,7 +238,12 @@ async fn swap_as_bob( #[tokio::test] async fn on_chain_happy_path() { let cli = Cli::default(); - let (monero, _container) = Monero::new(&cli).unwrap(); + let (monero, _container) = Monero::new(&cli, Some("ochp".to_string()), vec![ + "alice".to_string(), + "bob".to_string(), + ]) + .await + .unwrap(); let bitcoind = init_bitcoind(&cli).await; let (alice_state0, bob_state0, mut alice_node, mut bob_node, initial_balances, swap_amounts) = @@ -304,7 +309,7 @@ async fn on_chain_happy_path() { let alice_final_xmr_balance = alice_monero_wallet.get_balance().await.unwrap(); - monero.wait_for_bob_wallet_block_height().await.unwrap(); + monero.wallet("bob").unwrap().refresh().await.unwrap(); let bob_final_xmr_balance = bob_monero_wallet.get_balance().await.unwrap(); assert_eq!( @@ -329,7 +334,12 @@ async fn on_chain_happy_path() { #[tokio::test] async fn on_chain_both_refund_if_alice_never_redeems() { let cli = Cli::default(); - let (monero, _container) = Monero::new(&cli).unwrap(); + let (monero, _container) = Monero::new(&cli, Some("ocbr".to_string()), vec![ + "alice".to_string(), + "bob".to_string(), + ]) + .await + .unwrap(); let bitcoind = init_bitcoind(&cli).await; let (alice_state0, bob_state0, mut alice_node, mut bob_node, initial_balances, swap_amounts) = @@ -396,7 +406,7 @@ async fn on_chain_both_refund_if_alice_never_redeems() { .await .unwrap(); - monero.wait_for_alice_wallet_block_height().await.unwrap(); + monero.wallet("alice").unwrap().refresh().await.unwrap(); let alice_final_xmr_balance = alice_monero_wallet.get_balance().await.unwrap(); let bob_final_xmr_balance = bob_monero_wallet.get_balance().await.unwrap(); @@ -419,7 +429,12 @@ async fn on_chain_both_refund_if_alice_never_redeems() { #[tokio::test] async fn on_chain_alice_punishes_if_bob_never_acts_after_fund() { let cli = Cli::default(); - let (monero, _container) = Monero::new(&cli).unwrap(); + let (monero, _container) = Monero::new(&cli, Some("ocap".to_string()), vec![ + "alice".to_string(), + "bob".to_string(), + ]) + .await + .unwrap(); let bitcoind = init_bitcoind(&cli).await; let (alice_state0, bob_state0, mut alice_node, mut bob_node, initial_balances, swap_amounts) =