mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2025-05-02 23:05:10 -04:00
Replace bitcoind wallet with bdk wallet
The bitcoind wallet required the user to run a bitcoind node. It was replaced with a bdk wallet which allows the user to connect to an electrum instance hosted remotely. An electrum and bitcoind testcontainer were created to the test the bdk wallet. The electrum container reads the blockdata from the bitcoind testcontainer through a shared volume. bitcoind-harness was removed as bitcoind initialisation code was moved into test_utils. The bdk wallet differs from the bitcoind wallet in that it needs to be manually synced with an electrum node. We synchronise the wallet once upon initialisation to prevent a potentially long running blocking task from interrupting protocol execution. The electrum HTTP API was used to get the latest block height and the transaction block height as this functionality was not present in the bdk wallet API or it required the bdk wallet to be re-synced to get an up to date value.
This commit is contained in:
parent
4d8e801c1e
commit
a0ef1f96ec
13 changed files with 1010 additions and 225 deletions
|
@ -1,10 +1,18 @@
|
|||
mod bitcoind;
|
||||
mod electrs;
|
||||
|
||||
use crate::testutils;
|
||||
use bitcoin_harness::Bitcoind;
|
||||
use anyhow::{Context, Result};
|
||||
use bitcoin_harness::{BitcoindRpcApi, Client};
|
||||
use futures::{future::RemoteHandle, Future};
|
||||
use get_port::get_port;
|
||||
use libp2p::{core::Multiaddr, PeerId};
|
||||
use monero_harness::{image, Monero};
|
||||
use std::{path::PathBuf, sync::Arc};
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
use swap::{
|
||||
bitcoin,
|
||||
bitcoin::{CancelTimelock, PunishTimelock},
|
||||
|
@ -21,19 +29,22 @@ use swap::{
|
|||
seed::Seed,
|
||||
};
|
||||
use tempfile::tempdir;
|
||||
use testcontainers::{clients::Cli, Container};
|
||||
use tokio::{sync::mpsc, task::JoinHandle};
|
||||
use testcontainers::{clients::Cli, Container, Docker, RunArgs};
|
||||
use tokio::{sync::mpsc, task::JoinHandle, time::sleep};
|
||||
use tracing_core::dispatcher::DefaultGuard;
|
||||
use tracing_log::LogTracer;
|
||||
use url::Url;
|
||||
use uuid::Uuid;
|
||||
|
||||
const TEST_WALLET_NAME: &str = "testwallet";
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct StartingBalances {
|
||||
pub xmr: monero::Amount,
|
||||
pub btc: bitcoin::Amount,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Clone)]
|
||||
struct BobParams {
|
||||
seed: Seed,
|
||||
db_path: PathBuf,
|
||||
|
@ -119,6 +130,11 @@ impl TestContext {
|
|||
|
||||
assert!(matches!(state, AliceState::BtcRedeemed));
|
||||
|
||||
self.alice_bitcoin_wallet
|
||||
.sync_wallet()
|
||||
.await
|
||||
.expect("Could not sync wallet");
|
||||
|
||||
let btc_balance_after_swap = self.alice_bitcoin_wallet.as_ref().balance().await.unwrap();
|
||||
assert_eq!(
|
||||
btc_balance_after_swap,
|
||||
|
@ -145,6 +161,11 @@ impl TestContext {
|
|||
state
|
||||
);
|
||||
|
||||
self.alice_bitcoin_wallet
|
||||
.sync_wallet()
|
||||
.await
|
||||
.expect("Could not sync wallet");
|
||||
|
||||
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);
|
||||
|
||||
|
@ -167,6 +188,11 @@ impl TestContext {
|
|||
pub async fn assert_alice_punished(&self, state: AliceState) {
|
||||
assert!(matches!(state, AliceState::BtcPunished));
|
||||
|
||||
self.alice_bitcoin_wallet
|
||||
.sync_wallet()
|
||||
.await
|
||||
.expect("Could not sync wallet");
|
||||
|
||||
let btc_balance_after_swap = self.alice_bitcoin_wallet.as_ref().balance().await.unwrap();
|
||||
assert_eq!(
|
||||
btc_balance_after_swap,
|
||||
|
@ -184,6 +210,11 @@ impl TestContext {
|
|||
}
|
||||
|
||||
pub async fn assert_bob_redeemed(&self, state: BobState) {
|
||||
self.bob_bitcoin_wallet
|
||||
.sync_wallet()
|
||||
.await
|
||||
.expect("Could not sync wallet");
|
||||
|
||||
let lock_tx_id = if let BobState::XmrRedeemed { tx_lock_id } = state {
|
||||
tx_lock_id
|
||||
} else {
|
||||
|
@ -217,6 +248,11 @@ impl TestContext {
|
|||
}
|
||||
|
||||
pub async fn assert_bob_refunded(&self, state: BobState) {
|
||||
self.bob_bitcoin_wallet
|
||||
.sync_wallet()
|
||||
.await
|
||||
.expect("Could not sync wallet");
|
||||
|
||||
let lock_tx_id = if let BobState::BtcRefunded(state4) = state {
|
||||
state4.tx_lock_id()
|
||||
} else {
|
||||
|
@ -249,6 +285,11 @@ impl TestContext {
|
|||
}
|
||||
|
||||
pub async fn assert_bob_punished(&self, state: BobState) {
|
||||
self.bob_bitcoin_wallet
|
||||
.sync_wallet()
|
||||
.await
|
||||
.expect("Could not sync wallet");
|
||||
|
||||
let lock_tx_id = if let BobState::BtcPunished { tx_lock_id } = state {
|
||||
tx_lock_id
|
||||
} else {
|
||||
|
@ -300,11 +341,23 @@ where
|
|||
.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 electrs_http_port = containers
|
||||
.electrs
|
||||
.get_host_port(testutils::electrs::HTTP_PORT)
|
||||
.expect("Could not map electrs http port");
|
||||
|
||||
let (alice_bitcoin_wallet, alice_monero_wallet) = init_test_wallets(
|
||||
"alice",
|
||||
&containers.bitcoind,
|
||||
containers.bitcoind_url.clone(),
|
||||
&monero,
|
||||
alice_starting_balances.clone(),
|
||||
tempdir().unwrap().path(),
|
||||
electrs_rpc_port,
|
||||
electrs_http_port,
|
||||
)
|
||||
.await;
|
||||
|
||||
|
@ -320,9 +373,12 @@ where
|
|||
|
||||
let (bob_bitcoin_wallet, bob_monero_wallet) = init_test_wallets(
|
||||
"bob",
|
||||
&containers.bitcoind,
|
||||
containers.bitcoind_url,
|
||||
&monero,
|
||||
bob_starting_balances.clone(),
|
||||
tempdir().unwrap().path(),
|
||||
electrs_rpc_port,
|
||||
electrs_http_port,
|
||||
)
|
||||
.await;
|
||||
|
||||
|
@ -369,56 +425,219 @@ where
|
|||
testfn(test).await;
|
||||
}
|
||||
|
||||
fn random_prefix() -> String {
|
||||
use rand::{distributions::Alphanumeric, 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 bitcoind = Bitcoind::new(&cli, "0.19.1").unwrap();
|
||||
let _ = bitcoind.init(5).await;
|
||||
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)
|
||||
.with_tag("0.19.1");
|
||||
|
||||
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(TEST_WALLET_NAME, None, None, None, None)
|
||||
.await?;
|
||||
|
||||
let reward_address = bitcoind_client
|
||||
.with_wallet(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(TEST_WALLET_NAME, address.clone(), amount)
|
||||
.await?;
|
||||
|
||||
// Confirm the transaction
|
||||
let reward_address = bitcoind_client
|
||||
.with_wallet(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, None, vec!["alice".to_string(), "bob".to_string()])
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
(monero, Containers { bitcoind, monerods })
|
||||
(monero, monerods)
|
||||
}
|
||||
|
||||
async fn init_test_wallets(
|
||||
name: &str,
|
||||
bitcoind: &Bitcoind<'_>,
|
||||
bitcoind_url: Url,
|
||||
monero: &Monero,
|
||||
starting_balances: StartingBalances,
|
||||
datadir: &Path,
|
||||
electrum_rpc_port: u16,
|
||||
electrum_http_port: u16,
|
||||
) -> (Arc<bitcoin::Wallet>, Arc<monero::Wallet>) {
|
||||
monero
|
||||
.init(vec![(name, starting_balances.xmr.as_piconero())])
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let xmr_wallet = Arc::new(swap::monero::Wallet {
|
||||
let xmr_wallet = swap::monero::Wallet {
|
||||
inner: monero.wallet(name).unwrap().client(),
|
||||
network: monero::Network::default(),
|
||||
});
|
||||
};
|
||||
|
||||
let btc_wallet = Arc::new(
|
||||
swap::bitcoin::Wallet::new(name, bitcoind.node_url.clone(), bitcoin::Network::Regtest)
|
||||
.await
|
||||
.unwrap(),
|
||||
);
|
||||
let electrum_rpc_url = {
|
||||
let input = format!("tcp://@localhost:{}", electrum_rpc_port);
|
||||
Url::parse(&input).unwrap()
|
||||
};
|
||||
let electrum_http_url = {
|
||||
let input = format!("http://@localhost:{}", electrum_http_port);
|
||||
Url::parse(&input).unwrap()
|
||||
};
|
||||
|
||||
let btc_wallet = swap::bitcoin::Wallet::new(
|
||||
electrum_rpc_url,
|
||||
electrum_http_url,
|
||||
bitcoin::Network::Regtest,
|
||||
datadir,
|
||||
)
|
||||
.await
|
||||
.expect("could not init btc wallet");
|
||||
|
||||
if starting_balances.btc != bitcoin::Amount::ZERO {
|
||||
bitcoind
|
||||
.mint(
|
||||
btc_wallet.inner.new_address().await.unwrap(),
|
||||
starting_balances.btc,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
mint(
|
||||
bitcoind_url,
|
||||
btc_wallet.new_address().await.unwrap(),
|
||||
starting_balances.btc,
|
||||
)
|
||||
.await
|
||||
.expect("could not mint btc starting balance");
|
||||
}
|
||||
|
||||
(btc_wallet, xmr_wallet)
|
||||
sleep(Duration::from_secs(5)).await;
|
||||
|
||||
btc_wallet
|
||||
.sync_wallet()
|
||||
.await
|
||||
.expect("Could not sync btc wallet");
|
||||
|
||||
(Arc::new(btc_wallet), Arc::new(xmr_wallet))
|
||||
}
|
||||
|
||||
// This is just to keep the containers alive
|
||||
#[allow(dead_code)]
|
||||
struct Containers<'a> {
|
||||
bitcoind: Bitcoind<'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.
|
||||
|
@ -427,7 +646,7 @@ struct Containers<'a> {
|
|||
/// ```rust
|
||||
/// let _guard = init_tracing();
|
||||
/// ```
|
||||
fn init_tracing() -> DefaultGuard {
|
||||
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.
|
||||
|
@ -438,16 +657,18 @@ fn init_tracing() -> DefaultGuard {
|
|||
let xmr_btc_filter = tracing::Level::DEBUG;
|
||||
let monero_harness_filter = tracing::Level::INFO;
|
||||
let bitcoin_harness_filter = tracing::Level::INFO;
|
||||
let testcontainers_filter = tracing::Level::DEBUG;
|
||||
|
||||
use tracing_subscriber::util::SubscriberInitExt as _;
|
||||
tracing_subscriber::fmt()
|
||||
.with_env_filter(format!(
|
||||
"{},swap={},xmr_btc={},monero_harness={},bitcoin_harness={}",
|
||||
"{},swap={},xmr_btc={},monero_harness={},bitcoin_harness={},testcontainers={}",
|
||||
global_filter,
|
||||
swap_filter,
|
||||
xmr_btc_filter,
|
||||
monero_harness_filter,
|
||||
bitcoin_harness_filter,
|
||||
testcontainers_filter
|
||||
))
|
||||
.set_default()
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue