mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2024-10-01 01:45:40 -04:00
cde3f0f74a
The swap should not be concerned with connection handling. This is the responsibility of the overall application. All but the execution-setup NetworkBehaviour are `request-response` behaviours. These have built-in functionality to automatically emit a dial attempt in case we are not connected at the time we want to send a message. We remove all of the manual dialling code from the swap in favor of this behaviour. Additionally, we make sure to establish a connection as soon as the EventLoop gets started. In case we ever loose the connection to Alice, we try to re-establish it.
745 lines
22 KiB
Rust
745 lines
22 KiB
Rust
mod bitcoind;
|
|
mod electrs;
|
|
|
|
use crate::testutils;
|
|
use anyhow::{Context, Result};
|
|
use bitcoin_harness::{BitcoindRpcApi, Client};
|
|
use futures::Future;
|
|
use get_port::get_port;
|
|
use libp2p::core::Multiaddr;
|
|
use libp2p::{PeerId, Swarm};
|
|
use monero_harness::{image, Monero};
|
|
use std::path::{Path, PathBuf};
|
|
use std::sync::Arc;
|
|
use std::time::Duration;
|
|
use swap::asb::FixedRate;
|
|
use swap::bitcoin::{CancelTimelock, PunishTimelock};
|
|
use swap::database::Database;
|
|
use swap::env::{Config, GetConfig};
|
|
use swap::network::swarm;
|
|
use swap::protocol::alice::{AliceState, Swap};
|
|
use swap::protocol::bob::BobState;
|
|
use swap::protocol::{alice, bob};
|
|
use swap::seed::Seed;
|
|
use swap::{bitcoin, env, monero};
|
|
use tempfile::tempdir;
|
|
use testcontainers::clients::Cli;
|
|
use testcontainers::{Container, Docker, RunArgs};
|
|
use tokio::sync::mpsc;
|
|
use tokio::task::JoinHandle;
|
|
use tokio::time::interval;
|
|
use tracing::dispatcher::DefaultGuard;
|
|
use tracing_log::LogTracer;
|
|
use url::Url;
|
|
use uuid::Uuid;
|
|
|
|
const MONERO_WALLET_NAME_BOB: &str = "bob";
|
|
const MONERO_WALLET_NAME_ALICE: &str = "alice";
|
|
const BITCOIN_TEST_WALLET_NAME: &str = "testwallet";
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct StartingBalances {
|
|
pub xmr: monero::Amount,
|
|
pub btc: bitcoin::Amount,
|
|
}
|
|
|
|
struct BobParams {
|
|
seed: Seed,
|
|
db_path: PathBuf,
|
|
swap_id: Uuid,
|
|
bitcoin_wallet: Arc<bitcoin::Wallet>,
|
|
monero_wallet: Arc<monero::Wallet>,
|
|
alice_address: Multiaddr,
|
|
alice_peer_id: PeerId,
|
|
env_config: Config,
|
|
}
|
|
|
|
impl BobParams {
|
|
pub async fn builder(&self, event_loop_handle: bob::EventLoopHandle) -> Result<bob::Builder> {
|
|
let receive_address = self.monero_wallet.get_main_address();
|
|
|
|
Ok(bob::Builder::new(
|
|
Database::open(&self.db_path.clone().as_path()).unwrap(),
|
|
self.swap_id,
|
|
self.bitcoin_wallet.clone(),
|
|
self.monero_wallet.clone(),
|
|
self.env_config,
|
|
event_loop_handle,
|
|
receive_address,
|
|
))
|
|
}
|
|
|
|
pub fn new_eventloop(&self) -> Result<(bob::EventLoop, bob::EventLoopHandle)> {
|
|
let mut swarm = swarm::new::<bob::Behaviour>(&self.seed)?;
|
|
swarm.add_address(self.alice_peer_id, self.alice_address.clone());
|
|
|
|
bob::EventLoop::new(swarm, self.alice_peer_id, self.bitcoin_wallet.clone())
|
|
}
|
|
}
|
|
|
|
pub struct BobEventLoopJoinHandle(JoinHandle<()>);
|
|
|
|
impl BobEventLoopJoinHandle {
|
|
pub fn abort(&self) {
|
|
self.0.abort()
|
|
}
|
|
}
|
|
|
|
pub struct AliceEventLoopJoinHandle(JoinHandle<()>);
|
|
|
|
pub struct TestContext {
|
|
btc_amount: bitcoin::Amount,
|
|
xmr_amount: monero::Amount,
|
|
|
|
alice_starting_balances: StartingBalances,
|
|
alice_bitcoin_wallet: Arc<bitcoin::Wallet>,
|
|
alice_monero_wallet: Arc<monero::Wallet>,
|
|
alice_swap_handle: mpsc::Receiver<Swap>,
|
|
|
|
bob_params: BobParams,
|
|
bob_starting_balances: StartingBalances,
|
|
bob_bitcoin_wallet: Arc<bitcoin::Wallet>,
|
|
bob_monero_wallet: Arc<monero::Wallet>,
|
|
}
|
|
|
|
impl TestContext {
|
|
pub async fn alice_next_swap(&mut self) -> alice::Swap {
|
|
self.alice_swap_handle.recv().await.unwrap()
|
|
}
|
|
|
|
pub async fn bob_swap(&mut self) -> (bob::Swap, BobEventLoopJoinHandle) {
|
|
let (event_loop, event_loop_handle) = self.bob_params.new_eventloop().unwrap();
|
|
|
|
let swap = self
|
|
.bob_params
|
|
.builder(event_loop_handle)
|
|
.await
|
|
.unwrap()
|
|
.with_init_params(self.btc_amount)
|
|
.build()
|
|
.unwrap();
|
|
|
|
let join_handle = tokio::spawn(event_loop.run());
|
|
|
|
(swap, BobEventLoopJoinHandle(join_handle))
|
|
}
|
|
|
|
pub async fn stop_and_resume_bob_from_db(
|
|
&mut self,
|
|
join_handle: BobEventLoopJoinHandle,
|
|
) -> (bob::Swap, BobEventLoopJoinHandle) {
|
|
join_handle.abort();
|
|
|
|
let (event_loop, event_loop_handle) = self.bob_params.new_eventloop().unwrap();
|
|
|
|
let swap = self
|
|
.bob_params
|
|
.builder(event_loop_handle)
|
|
.await
|
|
.unwrap()
|
|
.build()
|
|
.unwrap();
|
|
|
|
let join_handle = tokio::spawn(event_loop.run());
|
|
|
|
(swap, BobEventLoopJoinHandle(join_handle))
|
|
}
|
|
|
|
pub async fn assert_alice_redeemed(&mut self, state: AliceState) {
|
|
assert!(matches!(state, AliceState::BtcRedeemed));
|
|
|
|
self.alice_bitcoin_wallet.sync().await.unwrap();
|
|
|
|
let btc_balance_after_swap = self.alice_bitcoin_wallet.as_ref().balance().await.unwrap();
|
|
assert_eq!(
|
|
btc_balance_after_swap,
|
|
self.alice_starting_balances.btc + self.btc_amount
|
|
- bitcoin::Amount::from_sat(bitcoin::TX_FEE)
|
|
);
|
|
|
|
let xmr_balance_after_swap = self
|
|
.alice_monero_wallet
|
|
.as_ref()
|
|
.get_balance()
|
|
.await
|
|
.unwrap();
|
|
assert!(
|
|
xmr_balance_after_swap <= self.alice_starting_balances.xmr - self.xmr_amount,
|
|
"{} !< {} - {}",
|
|
xmr_balance_after_swap,
|
|
self.alice_starting_balances.xmr,
|
|
self.xmr_amount
|
|
);
|
|
}
|
|
|
|
pub async fn assert_alice_refunded(&mut self, state: AliceState) {
|
|
assert!(matches!(state, AliceState::XmrRefunded));
|
|
|
|
self.alice_bitcoin_wallet.sync().await.unwrap();
|
|
|
|
let btc_balance_after_swap = self.alice_bitcoin_wallet.as_ref().balance().await.unwrap();
|
|
assert_eq!(btc_balance_after_swap, self.alice_starting_balances.btc);
|
|
|
|
// Ensure that Alice's balance is refreshed as we use a newly created wallet
|
|
self.alice_monero_wallet.as_ref().refresh().await.unwrap();
|
|
let xmr_balance_after_swap = self
|
|
.alice_monero_wallet
|
|
.as_ref()
|
|
.get_balance()
|
|
.await
|
|
.unwrap();
|
|
|
|
// Alice pays fees - comparison does not take exact lock fee into account
|
|
assert!(
|
|
xmr_balance_after_swap > self.alice_starting_balances.xmr - self.xmr_amount,
|
|
"{} > {} - {}",
|
|
xmr_balance_after_swap,
|
|
self.alice_starting_balances.xmr,
|
|
self.xmr_amount
|
|
);
|
|
}
|
|
|
|
pub async fn assert_alice_punished(&self, state: AliceState) {
|
|
assert!(matches!(state, AliceState::BtcPunished));
|
|
|
|
self.alice_bitcoin_wallet.sync().await.unwrap();
|
|
|
|
let btc_balance_after_swap = self.alice_bitcoin_wallet.as_ref().balance().await.unwrap();
|
|
assert_eq!(
|
|
btc_balance_after_swap,
|
|
self.alice_starting_balances.btc + self.btc_amount
|
|
- bitcoin::Amount::from_sat(2 * bitcoin::TX_FEE)
|
|
);
|
|
|
|
let xmr_balance_after_swap = self
|
|
.alice_monero_wallet
|
|
.as_ref()
|
|
.get_balance()
|
|
.await
|
|
.unwrap();
|
|
assert!(xmr_balance_after_swap <= self.alice_starting_balances.xmr - self.xmr_amount);
|
|
}
|
|
|
|
pub async fn assert_bob_redeemed(&self, state: BobState) {
|
|
self.bob_bitcoin_wallet.sync().await.unwrap();
|
|
|
|
let lock_tx_id = if let BobState::XmrRedeemed { tx_lock_id } = state {
|
|
tx_lock_id
|
|
} else {
|
|
panic!("Bob in not in xmr redeemed state: {:?}", state);
|
|
};
|
|
|
|
let lock_tx_bitcoin_fee = self
|
|
.bob_bitcoin_wallet
|
|
.transaction_fee(lock_tx_id)
|
|
.await
|
|
.unwrap();
|
|
|
|
let btc_balance_after_swap = self.bob_bitcoin_wallet.as_ref().balance().await.unwrap();
|
|
assert_eq!(
|
|
btc_balance_after_swap,
|
|
self.bob_starting_balances.btc - self.btc_amount - lock_tx_bitcoin_fee
|
|
);
|
|
|
|
// unload the generated wallet by opening the original wallet
|
|
self.bob_monero_wallet.re_open().await.unwrap();
|
|
// refresh the original wallet to make sure the balance is caught up
|
|
self.bob_monero_wallet.refresh().await.unwrap();
|
|
|
|
// Ensure that Bob's balance is refreshed as we use a newly created wallet
|
|
self.bob_monero_wallet.as_ref().refresh().await.unwrap();
|
|
let xmr_balance_after_swap = self.bob_monero_wallet.as_ref().get_balance().await.unwrap();
|
|
assert!(xmr_balance_after_swap > self.bob_starting_balances.xmr);
|
|
}
|
|
|
|
pub async fn assert_bob_refunded(&self, state: BobState) {
|
|
self.bob_bitcoin_wallet.sync().await.unwrap();
|
|
|
|
let lock_tx_id = if let BobState::BtcRefunded(state4) = state {
|
|
state4.tx_lock_id()
|
|
} else {
|
|
panic!("Bob in not in btc refunded state: {:?}", state);
|
|
};
|
|
let lock_tx_bitcoin_fee = self
|
|
.bob_bitcoin_wallet
|
|
.transaction_fee(lock_tx_id)
|
|
.await
|
|
.unwrap();
|
|
|
|
let btc_balance_after_swap = self.bob_bitcoin_wallet.as_ref().balance().await.unwrap();
|
|
|
|
let alice_submitted_cancel = btc_balance_after_swap
|
|
== self.bob_starting_balances.btc
|
|
- lock_tx_bitcoin_fee
|
|
- bitcoin::Amount::from_sat(bitcoin::TX_FEE);
|
|
|
|
let bob_submitted_cancel = btc_balance_after_swap
|
|
== self.bob_starting_balances.btc
|
|
- lock_tx_bitcoin_fee
|
|
- bitcoin::Amount::from_sat(2 * bitcoin::TX_FEE);
|
|
|
|
// The cancel tx can be submitted by both Alice and Bob.
|
|
// Since we cannot be sure who submitted it we have to assert accordingly
|
|
assert!(alice_submitted_cancel || bob_submitted_cancel);
|
|
|
|
let xmr_balance_after_swap = self.bob_monero_wallet.as_ref().get_balance().await.unwrap();
|
|
assert_eq!(xmr_balance_after_swap, self.bob_starting_balances.xmr);
|
|
}
|
|
|
|
pub async fn assert_bob_punished(&self, state: BobState) {
|
|
self.bob_bitcoin_wallet.sync().await.unwrap();
|
|
|
|
let lock_tx_id = if let BobState::BtcPunished { tx_lock_id } = state {
|
|
tx_lock_id
|
|
} else {
|
|
panic!("Bob in not in btc punished state: {:?}", state);
|
|
};
|
|
|
|
let lock_tx_bitcoin_fee = self
|
|
.bob_bitcoin_wallet
|
|
.transaction_fee(lock_tx_id)
|
|
.await
|
|
.unwrap();
|
|
|
|
let btc_balance_after_swap = self.bob_bitcoin_wallet.as_ref().balance().await.unwrap();
|
|
assert_eq!(
|
|
btc_balance_after_swap,
|
|
self.bob_starting_balances.btc - self.btc_amount - lock_tx_bitcoin_fee
|
|
);
|
|
|
|
let xmr_balance_after_swap = self.bob_monero_wallet.as_ref().get_balance().await.unwrap();
|
|
assert_eq!(xmr_balance_after_swap, self.bob_starting_balances.xmr);
|
|
}
|
|
}
|
|
|
|
pub async fn setup_test<T, F, C>(_config: C, testfn: T)
|
|
where
|
|
T: Fn(TestContext) -> F,
|
|
F: Future<Output = Result<()>>,
|
|
C: GetConfig,
|
|
{
|
|
let cli = Cli::default();
|
|
|
|
let _guard = init_tracing();
|
|
|
|
let env_config = C::get_config();
|
|
|
|
let (monero, containers) = testutils::init_containers(&cli).await;
|
|
|
|
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 {
|
|
xmr: xmr_amount * 10,
|
|
btc: bitcoin::Amount::ZERO,
|
|
};
|
|
|
|
let port = get_port().expect("Failed to find a free port");
|
|
|
|
let alice_listen_address: Multiaddr = format!("/ip4/127.0.0.1/tcp/{}", port)
|
|
.parse()
|
|
.expect("failed to parse Alice's address");
|
|
|
|
let electrs_rpc_port = containers
|
|
.electrs
|
|
.get_host_port(testutils::electrs::RPC_PORT)
|
|
.expect("Could not map electrs rpc port");
|
|
|
|
let alice_seed = Seed::random().unwrap();
|
|
let bob_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 db_path = tempdir().unwrap();
|
|
let alice_db = Arc::new(Database::open(db_path.path()).unwrap());
|
|
|
|
let alice_seed = Seed::random().unwrap();
|
|
|
|
let bob_starting_balances = StartingBalances {
|
|
xmr: monero::Amount::ZERO,
|
|
btc: btc_amount * 10,
|
|
};
|
|
|
|
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 mut alice_swarm = swarm::new::<alice::Behaviour>(&alice_seed).unwrap();
|
|
Swarm::listen_on(&mut alice_swarm, alice_listen_address.clone()).unwrap();
|
|
|
|
let (alice_event_loop, alice_swap_handle) = alice::EventLoop::new(
|
|
alice_swarm,
|
|
env_config,
|
|
alice_bitcoin_wallet.clone(),
|
|
alice_monero_wallet.clone(),
|
|
alice_db,
|
|
FixedRate::default(),
|
|
bitcoin::Amount::ONE_BTC,
|
|
)
|
|
.unwrap();
|
|
|
|
let alice_peer_id = alice_event_loop.peer_id();
|
|
|
|
tokio::spawn(alice_event_loop.run());
|
|
|
|
let bob_params = BobParams {
|
|
seed: Seed::random().unwrap(),
|
|
db_path: tempdir().unwrap().path().to_path_buf(),
|
|
swap_id: Uuid::new_v4(),
|
|
bitcoin_wallet: bob_bitcoin_wallet.clone(),
|
|
monero_wallet: bob_monero_wallet.clone(),
|
|
alice_address: alice_listen_address,
|
|
alice_peer_id,
|
|
env_config,
|
|
};
|
|
|
|
let test = TestContext {
|
|
btc_amount,
|
|
xmr_amount,
|
|
alice_starting_balances,
|
|
alice_bitcoin_wallet,
|
|
alice_monero_wallet,
|
|
alice_swap_handle,
|
|
bob_params,
|
|
bob_starting_balances,
|
|
bob_bitcoin_wallet,
|
|
bob_monero_wallet,
|
|
};
|
|
|
|
testfn(test).await.unwrap()
|
|
}
|
|
|
|
fn random_prefix() -> String {
|
|
use rand::distributions::Alphanumeric;
|
|
use rand::{thread_rng, Rng};
|
|
use std::iter;
|
|
const LEN: usize = 8;
|
|
let mut rng = thread_rng();
|
|
let chars: String = iter::repeat(())
|
|
.map(|()| rng.sample(Alphanumeric))
|
|
.map(char::from)
|
|
.take(LEN)
|
|
.collect();
|
|
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(testutils::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<Container<'_, Cli, electrs::Electrs>> {
|
|
let bitcoind_rpc_addr = format!(
|
|
"{}:{}",
|
|
bitcoind_container_name,
|
|
testutils::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;
|
|
bitcoind_client
|
|
.generatetoaddress(1, reward_address.clone(), None)
|
|
.await?;
|
|
}
|
|
}
|
|
|
|
async fn init_bitcoind(node_url: Url, spendable_quantity: u32) -> Result<Client> {
|
|
let bitcoind_client = Client::new(node_url.clone());
|
|
|
|
bitcoind_client
|
|
.createwallet(BITCOIN_TEST_WALLET_NAME, None, None, None, None)
|
|
.await?;
|
|
|
|
let reward_address = bitcoind_client
|
|
.with_wallet(BITCOIN_TEST_WALLET_NAME)?
|
|
.getnewaddress(None, None)
|
|
.await?;
|
|
|
|
bitcoind_client
|
|
.generatetoaddress(101 + spendable_quantity, reward_address.clone(), None)
|
|
.await?;
|
|
let _ = tokio::spawn(mine(bitcoind_client.clone(), reward_address));
|
|
Ok(bitcoind_client)
|
|
}
|
|
|
|
/// Send Bitcoin to the specified address, limited to the spendable bitcoin
|
|
/// quantity.
|
|
pub async fn mint(node_url: Url, address: bitcoin::Address, amount: bitcoin::Amount) -> Result<()> {
|
|
let bitcoind_client = Client::new(node_url.clone());
|
|
|
|
bitcoind_client
|
|
.send_to_address(BITCOIN_TEST_WALLET_NAME, address.clone(), amount)
|
|
.await?;
|
|
|
|
// Confirm the transaction
|
|
let reward_address = bitcoind_client
|
|
.with_wallet(BITCOIN_TEST_WALLET_NAME)?
|
|
.getnewaddress(None, None)
|
|
.await?;
|
|
bitcoind_client
|
|
.generatetoaddress(1, reward_address, None)
|
|
.await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn init_monero_container(
|
|
cli: &Cli,
|
|
) -> (
|
|
Monero,
|
|
Vec<Container<'_, Cli, monero_harness::image::Monero>>,
|
|
) {
|
|
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<bitcoin::Wallet>, Arc<monero::Wallet>) {
|
|
monero
|
|
.init(vec![(name, starting_balances.xmr.as_piconero())])
|
|
.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> {
|
|
bitcoind_url: Url,
|
|
bitcoind: Container<'a, Cli, bitcoind::Bitcoind>,
|
|
monerods: Vec<Container<'a, Cli, image::Monero>>,
|
|
electrs: Container<'a, Cli, electrs::Electrs>,
|
|
}
|
|
|
|
/// Utility function to initialize logging in the test environment.
|
|
/// Note that you have to keep the `_guard` in scope after calling in test:
|
|
///
|
|
/// ```rust
|
|
/// let _guard = init_tracing();
|
|
/// ```
|
|
pub fn init_tracing() -> DefaultGuard {
|
|
// converts all log records into tracing events
|
|
// Note: Make sure to initialize without unwrapping, otherwise this causes
|
|
// trouble when running multiple tests.
|
|
let _ = LogTracer::init();
|
|
|
|
use tracing_subscriber::util::SubscriberInitExt as _;
|
|
tracing_subscriber::fmt()
|
|
.with_env_filter("warn,swap=debug,monero_harness=debug,monero_rpc=info,bitcoin_harness=info,testcontainers=info")
|
|
.set_default()
|
|
}
|
|
|
|
pub mod alice_run_until {
|
|
use swap::protocol::alice::AliceState;
|
|
|
|
pub fn is_xmr_locked(state: &AliceState) -> bool {
|
|
matches!(state, AliceState::XmrLocked { .. })
|
|
}
|
|
|
|
pub fn is_encsig_learned(state: &AliceState) -> bool {
|
|
matches!(state, AliceState::EncSigLearned { .. })
|
|
}
|
|
}
|
|
|
|
pub mod bob_run_until {
|
|
use swap::protocol::bob::BobState;
|
|
|
|
pub fn is_btc_locked(state: &BobState) -> bool {
|
|
matches!(state, BobState::BtcLocked(..))
|
|
}
|
|
|
|
pub fn is_lock_proof_received(state: &BobState) -> bool {
|
|
matches!(state, BobState::XmrLockProofReceived { .. })
|
|
}
|
|
|
|
pub fn is_xmr_locked(state: &BobState) -> bool {
|
|
matches!(state, BobState::XmrLocked(..))
|
|
}
|
|
|
|
pub fn is_encsig_sent(state: &BobState) -> bool {
|
|
matches!(state, BobState::EncSigSent(..))
|
|
}
|
|
}
|
|
|
|
pub struct SlowCancelConfig;
|
|
|
|
impl GetConfig for SlowCancelConfig {
|
|
fn get_config() -> Config {
|
|
Config {
|
|
bitcoin_cancel_timelock: CancelTimelock::new(180),
|
|
..env::Regtest::get_config()
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct FastCancelConfig;
|
|
|
|
impl GetConfig for FastCancelConfig {
|
|
fn get_config() -> Config {
|
|
Config {
|
|
bitcoin_cancel_timelock: CancelTimelock::new(1),
|
|
..env::Regtest::get_config()
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct FastPunishConfig;
|
|
|
|
impl GetConfig for FastPunishConfig {
|
|
fn get_config() -> Config {
|
|
Config {
|
|
bitcoin_cancel_timelock: CancelTimelock::new(1),
|
|
bitcoin_punish_timelock: PunishTimelock::new(1),
|
|
..env::Regtest::get_config()
|
|
}
|
|
}
|
|
}
|