2021-02-01 18:39:34 -05:00
mod bitcoind ;
mod electrs ;
2021-03-25 21:44:40 -04:00
use anyhow ::{ bail , Context , Result } ;
use async_trait ::async_trait ;
2021-02-01 18:39:34 -05:00
use bitcoin_harness ::{ BitcoindRpcApi , Client } ;
2021-03-03 19:28:58 -05:00
use futures ::Future ;
2021-01-14 19:26:32 -05:00
use get_port ::get_port ;
2021-03-03 19:28:58 -05:00
use libp2p ::core ::Multiaddr ;
2021-04-15 03:44:21 -04:00
use libp2p ::PeerId ;
2020-12-09 22:59:09 -05:00
use monero_harness ::{ image , Monero } ;
2021-03-25 21:44:40 -04:00
use std ::cmp ::Ordering ;
use std ::fmt ;
2021-03-03 19:28:58 -05:00
use std ::path ::{ Path , PathBuf } ;
use std ::sync ::Arc ;
use std ::time ::Duration ;
2021-06-24 23:40:33 -04:00
use swap ::asb ::FixedRate ;
2021-05-03 03:35:41 -04:00
use swap ::bitcoin ::{ CancelTimelock , PunishTimelock , TxCancel , TxPunish , TxRedeem , TxRefund } ;
2021-10-06 03:58:30 -04:00
use swap ::database ::SqliteDatabase ;
2021-03-16 23:55:42 -04:00
use swap ::env ::{ Config , GetConfig } ;
2021-10-06 03:58:13 -04:00
use swap ::fs ::ensure_directory_exists ;
2021-03-23 01:56:04 -04:00
use swap ::network ::swarm ;
2021-03-16 02:08:19 -04:00
use swap ::protocol ::alice ::{ AliceState , Swap } ;
2021-03-03 19:28:58 -05:00
use swap ::protocol ::bob ::BobState ;
use swap ::protocol ::{ alice , bob } ;
use swap ::seed ::Seed ;
2021-06-24 23:40:33 -04:00
use swap ::{ asb , bitcoin , cli , env , monero } ;
2021-10-06 03:58:30 -04:00
use tempfile ::{ tempdir , NamedTempFile } ;
2021-03-03 19:28:58 -05:00
use testcontainers ::clients ::Cli ;
use testcontainers ::{ Container , Docker , RunArgs } ;
use tokio ::sync ::mpsc ;
2021-03-26 00:38:30 -04:00
use tokio ::sync ::mpsc ::Receiver ;
2021-03-03 19:28:58 -05:00
use tokio ::task ::JoinHandle ;
2021-03-26 00:38:30 -04:00
use tokio ::time ::{ interval , timeout } ;
2021-03-28 18:54:48 -04:00
use tracing_subscriber ::util ::SubscriberInitExt ;
2021-02-01 18:39:34 -05:00
use url ::Url ;
2021-01-14 19:26:32 -05:00
use uuid ::Uuid ;
2021-04-15 21:12:27 -04:00
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 = tracing_subscriber ::fmt ( )
2021-04-18 20:10:46 -04:00
. 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
2021-04-15 21:12:27 -04:00
. with_test_writer ( )
. set_default ( ) ;
let env_config = C ::get_config ( ) ;
2021-04-15 21:14:29 -04:00
let ( monero , containers ) = init_containers ( & cli ) . await ;
2021-04-15 21:12:27 -04:00
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
2021-04-15 21:14:29 -04:00
. get_host_port ( electrs ::RPC_PORT )
2021-04-15 21:12:27 -04:00
. 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 " ) ;
2021-10-06 03:58:30 -04:00
let alice_db_path = NamedTempFile ::new ( ) . unwrap ( ) . path ( ) . to_path_buf ( ) ;
2021-04-15 21:12:27 -04:00
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 ( ) ,
2021-05-04 00:57:50 -04:00
)
. await ;
2021-04-15 21:12:27 -04:00
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 ( ) ,
2021-10-06 03:58:30 -04:00
db_path : NamedTempFile ::new ( ) . unwrap ( ) . path ( ) . to_path_buf ( ) ,
2021-04-15 21:12:27 -04:00
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 ) =
2022-03-12 06:05:36 -05:00
init_bitcoind_container ( cli , prefix . clone ( ) , bitcoind_name . clone ( ) , prefix . clone ( ) )
2021-04-15 21:12:27 -04:00
. await
. expect ( " could not init bitcoind " ) ;
2022-03-12 06:05:36 -05:00
let electrs = init_electrs_container ( cli , prefix . clone ( ) , bitcoind_name , prefix )
2021-04-15 21:12:27 -04:00
. await
. expect ( " could not init electrs " ) ;
2021-04-19 03:26:11 -04:00
let ( monero , monerod_container , monero_wallet_rpc_containers ) =
2022-03-12 06:05:36 -05:00
Monero ::new ( cli , vec! [ MONERO_WALLET_NAME_ALICE , MONERO_WALLET_NAME_BOB ] )
2021-04-19 03:26:11 -04:00
. await
. unwrap ( ) ;
2021-10-06 03:58:30 -04:00
( monero , Containers {
bitcoind_url ,
bitcoind ,
monerod_container ,
monero_wallet_rpc_containers ,
electrs ,
} )
2021-04-15 21:12:27 -04:00
}
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
2021-04-15 21:14:29 -04:00
. get_host_port ( bitcoind ::RPC_PORT )
2021-04-15 21:12:27 -04:00
. 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 > > {
2021-04-15 21:14:29 -04:00
let bitcoind_rpc_addr = format! ( " {} : {} " , bitcoind_container_name , bitcoind ::RPC_PORT ) ;
2021-04-15 21:12:27 -04:00
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 )
}
2021-05-04 00:57:50 -04:00
async fn start_alice (
2021-04-15 21:12:27 -04:00
seed : & Seed ,
db_path : PathBuf ,
listen_address : Multiaddr ,
env_config : Config ,
bitcoin_wallet : Arc < bitcoin ::Wallet > ,
monero_wallet : Arc < monero ::Wallet > ,
) -> ( AliceApplicationHandle , Receiver < alice ::Swap > ) {
2021-10-06 03:58:30 -04:00
if let Some ( parent_dir ) = db_path . parent ( ) {
ensure_directory_exists ( parent_dir ) . unwrap ( ) ;
}
if ! & db_path . exists ( ) {
tokio ::fs ::File ::create ( & db_path ) . await . unwrap ( ) ;
}
let db = Arc ::new ( SqliteDatabase ::open ( db_path . as_path ( ) ) . await . unwrap ( ) ) ;
2021-04-15 21:12:27 -04:00
2021-05-07 05:06:58 -04:00
let min_buy = bitcoin ::Amount ::from_sat ( u64 ::MIN ) ;
2021-05-04 00:57:50 -04:00
let max_buy = bitcoin ::Amount ::from_sat ( u64 ::MAX ) ;
let latest_rate = FixedRate ::default ( ) ;
let resume_only = false ;
2021-06-08 22:03:06 -04:00
let mut swarm = swarm ::asb (
2022-03-12 06:05:36 -05:00
seed ,
2021-05-07 05:06:58 -04:00
min_buy ,
2021-05-04 00:57:50 -04:00
max_buy ,
latest_rate ,
resume_only ,
2021-05-11 08:22:59 -04:00
env_config ,
2021-06-29 00:53:26 -04:00
None ,
2021-05-04 00:57:50 -04:00
)
. unwrap ( ) ;
2021-04-15 21:12:27 -04:00
swarm . listen_on ( listen_address ) . unwrap ( ) ;
2021-06-24 23:40:33 -04:00
let ( event_loop , swap_handle ) = asb ::EventLoop ::new (
2021-04-15 21:12:27 -04:00
swarm ,
env_config ,
bitcoin_wallet ,
monero_wallet ,
db ,
FixedRate ::default ( ) ,
2021-05-07 05:06:58 -04:00
min_buy ,
max_buy ,
2021-04-15 21:12:27 -04:00
)
. 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 < bitcoin ::Wallet > , Arc < monero ::Wallet > ) {
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 (
2021-04-18 20:10:46 -04:00
monero . wallet ( name ) . unwrap ( ) . client ( ) . clone ( ) ,
2021-04-15 21:12:27 -04:00
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 ,
2021-04-30 20:08:54 -04:00
1 ,
2021-04-15 21:12:27 -04:00
)
. 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 ( 1 u64 ) ) ;
let mut retries = 0 u8 ;
let max_retries = 30 u8 ;
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 ) )
}
2021-03-02 18:58:47 -05:00
const MONERO_WALLET_NAME_BOB : & str = " bob " ;
const MONERO_WALLET_NAME_ALICE : & str = " alice " ;
const BITCOIN_TEST_WALLET_NAME : & str = " testwallet " ;
2021-02-01 18:39:34 -05:00
2021-01-18 22:48:07 -05:00
#[ derive(Debug, Clone) ]
pub struct StartingBalances {
pub xmr : monero ::Amount ,
2021-04-08 04:56:26 -04:00
pub xmr_outputs : Vec < monero ::Amount > ,
2021-01-18 22:48:07 -05:00
pub btc : bitcoin ::Amount ,
}
2021-04-08 04:56:26 -04:00
impl StartingBalances {
/// If monero_outputs is specified the monero balance will be:
/// monero_outputs * new_xmr = self_xmr
pub fn new ( btc : bitcoin ::Amount , xmr : monero ::Amount , monero_outputs : Option < u64 > ) -> Self {
match monero_outputs {
None = > {
if xmr = = monero ::Amount ::ZERO {
return Self {
xmr ,
xmr_outputs : vec ! [ ] ,
btc ,
} ;
}
Self {
xmr ,
xmr_outputs : vec ! [ xmr ] ,
btc ,
}
}
Some ( outputs ) = > {
let mut xmr_outputs = Vec ::new ( ) ;
let mut sum_xmr = monero ::Amount ::ZERO ;
for _ in 0 .. outputs {
xmr_outputs . push ( xmr ) ;
sum_xmr = sum_xmr + xmr ;
}
Self {
xmr : sum_xmr ,
xmr_outputs ,
btc ,
}
}
}
}
}
2021-01-20 05:42:35 -05:00
struct BobParams {
seed : Seed ,
db_path : PathBuf ,
bitcoin_wallet : Arc < bitcoin ::Wallet > ,
monero_wallet : Arc < monero ::Wallet > ,
alice_address : Multiaddr ,
alice_peer_id : PeerId ,
2021-03-16 23:55:42 -04:00
env_config : Config ,
2021-01-20 05:42:35 -05:00
}
impl BobParams {
2021-06-24 23:40:33 -04:00
pub async fn new_swap_from_db ( & self , swap_id : Uuid ) -> Result < ( bob ::Swap , cli ::EventLoop ) > {
2021-04-21 21:33:36 -04:00
let ( event_loop , handle ) = self . new_eventloop ( swap_id ) . await ? ;
2021-10-06 03:58:30 -04:00
if let Some ( parent_dir ) = self . db_path . parent ( ) {
ensure_directory_exists ( parent_dir ) ? ;
}
if ! self . db_path . exists ( ) {
tokio ::fs ::File ::create ( & self . db_path ) . await ? ;
}
let db = Arc ::new ( SqliteDatabase ::open ( & self . db_path ) . await ? ) ;
2021-04-15 22:00:11 -04:00
let swap = bob ::Swap ::from_db (
db ,
swap_id ,
self . bitcoin_wallet . clone ( ) ,
self . monero_wallet . clone ( ) ,
self . env_config ,
handle ,
self . monero_wallet . get_main_address ( ) ,
2021-09-27 20:15:31 -04:00
)
. await ? ;
2021-04-15 22:00:11 -04:00
Ok ( ( swap , event_loop ) )
}
2021-04-21 21:33:36 -04:00
pub async fn new_swap (
& self ,
btc_amount : bitcoin ::Amount ,
2021-06-24 23:40:33 -04:00
) -> Result < ( bob ::Swap , cli ::EventLoop ) > {
2021-04-15 22:00:11 -04:00
let swap_id = Uuid ::new_v4 ( ) ;
2021-03-02 06:07:48 -05:00
2021-04-21 21:33:36 -04:00
let ( event_loop , handle ) = self . new_eventloop ( swap_id ) . await ? ;
2021-10-06 03:58:30 -04:00
if let Some ( parent_dir ) = self . db_path . parent ( ) {
ensure_directory_exists ( parent_dir ) ? ;
}
if ! self . db_path . exists ( ) {
tokio ::fs ::File ::create ( & self . db_path ) . await ? ;
}
let db = Arc ::new ( SqliteDatabase ::open ( & self . db_path ) . await ? ) ;
2021-04-15 22:00:11 -04:00
let swap = bob ::Swap ::new (
db ,
2021-04-08 04:56:26 -04:00
swap_id ,
2021-01-20 05:42:35 -05:00
self . bitcoin_wallet . clone ( ) ,
self . monero_wallet . clone ( ) ,
2021-03-16 23:55:42 -04:00
self . env_config ,
2021-04-15 22:00:11 -04:00
handle ,
self . monero_wallet . get_main_address ( ) ,
2021-07-06 05:17:17 -04:00
self . bitcoin_wallet . new_address ( ) . await ? ,
2021-04-15 22:00:11 -04:00
btc_amount ,
) ;
Ok ( ( swap , event_loop ) )
2021-03-02 21:56:25 -05:00
}
2021-04-21 21:33:36 -04:00
pub async fn new_eventloop (
& self ,
swap_id : Uuid ,
2021-06-24 23:40:33 -04:00
) -> Result < ( cli ::EventLoop , cli ::EventLoopHandle ) > {
2021-04-21 21:33:36 -04:00
let tor_socks5_port = get_port ( )
. expect ( " We don't care about Tor in the tests so we get a free port to disable it. " ) ;
2021-06-28 06:49:02 -04:00
let behaviour = cli ::Behaviour ::new (
2021-06-23 23:54:26 -04:00
self . alice_peer_id ,
self . env_config ,
self . bitcoin_wallet . clone ( ) ,
2021-06-28 06:49:02 -04:00
) ;
let mut swarm = swarm ::cli (
self . seed . derive_libp2p_identity ( ) ,
tor_socks5_port ,
behaviour ,
2021-06-23 23:54:26 -04:00
)
. await ? ;
2021-04-15 03:44:21 -04:00
swarm
. behaviour_mut ( )
. add_address ( self . alice_peer_id , self . alice_address . clone ( ) ) ;
2021-03-23 01:56:04 -04:00
2022-03-12 06:05:36 -05:00
cli ::EventLoop ::new ( swap_id , swarm , self . alice_peer_id )
2021-01-20 05:42:35 -05:00
}
}
2021-03-26 00:38:30 -04:00
pub struct BobApplicationHandle ( JoinHandle < ( ) > ) ;
2021-01-21 21:33:31 -05:00
2021-03-26 00:38:30 -04:00
impl BobApplicationHandle {
2021-02-08 19:15:55 -05:00
pub fn abort ( & self ) {
self . 0. abort ( )
}
}
2021-03-26 00:38:30 -04:00
pub struct AliceApplicationHandle {
handle : JoinHandle < ( ) > ,
peer_id : PeerId ,
}
impl AliceApplicationHandle {
pub fn abort ( & self ) {
self . handle . abort ( )
}
}
2021-01-21 21:33:31 -05:00
2021-01-19 18:40:40 -05:00
pub struct TestContext {
2021-03-26 00:38:30 -04:00
env_config : Config ,
2021-02-14 21:01:38 -05:00
btc_amount : bitcoin ::Amount ,
xmr_amount : monero ::Amount ,
2021-01-18 23:03:30 -05:00
2021-03-26 00:38:30 -04:00
alice_seed : Seed ,
alice_db_path : PathBuf ,
alice_listen_address : Multiaddr ,
2021-01-18 22:48:07 -05:00
alice_starting_balances : StartingBalances ,
2021-01-18 23:03:30 -05:00
alice_bitcoin_wallet : Arc < bitcoin ::Wallet > ,
alice_monero_wallet : Arc < monero ::Wallet > ,
2021-03-16 02:08:19 -04:00
alice_swap_handle : mpsc ::Receiver < Swap > ,
2021-03-26 00:38:30 -04:00
alice_handle : AliceApplicationHandle ,
2021-01-18 23:03:30 -05:00
2021-01-20 05:42:35 -05:00
bob_params : BobParams ,
2021-01-18 22:36:24 -05:00
bob_starting_balances : StartingBalances ,
2021-01-18 23:09:05 -05:00
bob_bitcoin_wallet : Arc < bitcoin ::Wallet > ,
bob_monero_wallet : Arc < monero ::Wallet > ,
2021-01-18 03:56:43 -05:00
}
2021-01-19 18:40:40 -05:00
impl TestContext {
2021-03-26 00:38:30 -04:00
pub async fn restart_alice ( & mut self ) {
self . alice_handle . abort ( ) ;
let ( alice_handle , alice_swap_handle ) = start_alice (
& self . alice_seed ,
self . alice_db_path . clone ( ) ,
self . alice_listen_address . clone ( ) ,
self . env_config ,
self . alice_bitcoin_wallet . clone ( ) ,
self . alice_monero_wallet . clone ( ) ,
2021-05-04 00:57:50 -04:00
)
. await ;
2021-03-26 00:38:30 -04:00
self . alice_handle = alice_handle ;
self . alice_swap_handle = alice_swap_handle ;
}
2021-03-16 02:08:19 -04:00
pub async fn alice_next_swap ( & mut self ) -> alice ::Swap {
2021-04-08 04:56:26 -04:00
timeout ( Duration ::from_secs ( 20 ) , self . alice_swap_handle . recv ( ) )
2021-03-26 00:38:30 -04:00
. await
2021-04-08 04:56:26 -04:00
. expect ( " No Alice swap within 20 seconds, aborting because this test is likely waiting for a swap forever... " )
2021-03-26 00:38:30 -04:00
. unwrap ( )
2021-03-16 02:08:19 -04:00
}
2021-03-26 00:38:30 -04:00
pub async fn bob_swap ( & mut self ) -> ( bob ::Swap , BobApplicationHandle ) {
2021-04-21 21:33:36 -04:00
let ( swap , event_loop ) = self . bob_params . new_swap ( self . btc_amount ) . await . unwrap ( ) ;
2021-01-18 03:56:43 -05:00
2021-04-08 04:56:26 -04:00
// ensure the wallet is up to date for concurrent swap tests
swap . bitcoin_wallet . sync ( ) . await . unwrap ( ) ;
2021-03-02 21:36:37 -05:00
let join_handle = tokio ::spawn ( event_loop . run ( ) ) ;
2021-01-18 03:56:43 -05:00
2021-03-26 00:38:30 -04:00
( swap , BobApplicationHandle ( join_handle ) )
2021-01-18 03:56:43 -05:00
}
2021-01-21 21:33:31 -05:00
pub async fn stop_and_resume_bob_from_db (
& mut self ,
2021-03-26 00:38:30 -04:00
join_handle : BobApplicationHandle ,
2021-04-08 04:56:26 -04:00
swap_id : Uuid ,
2021-03-26 00:38:30 -04:00
) -> ( bob ::Swap , BobApplicationHandle ) {
2021-02-08 19:15:55 -05:00
join_handle . abort ( ) ;
2021-01-21 21:33:31 -05:00
2021-04-21 21:33:36 -04:00
let ( swap , event_loop ) = self . bob_params . new_swap_from_db ( swap_id ) . await . unwrap ( ) ;
2021-01-18 03:56:43 -05:00
2021-03-02 21:36:37 -05:00
let join_handle = tokio ::spawn ( event_loop . run ( ) ) ;
2021-01-18 03:56:43 -05:00
2021-03-26 00:38:30 -04:00
( swap , BobApplicationHandle ( join_handle ) )
2021-01-18 03:56:43 -05:00
}
2021-03-16 02:08:19 -04:00
pub async fn assert_alice_redeemed ( & mut self , state : AliceState ) {
2021-01-18 03:56:43 -05:00
assert! ( matches! ( state , AliceState ::BtcRedeemed ) ) ;
2021-03-25 21:44:40 -04:00
assert_eventual_balance (
self . alice_bitcoin_wallet . as_ref ( ) ,
Ordering ::Equal ,
2021-04-30 20:08:54 -04:00
self . alice_redeemed_btc_balance ( ) . await ,
2021-03-25 21:44:40 -04:00
)
. await
. unwrap ( ) ;
assert_eventual_balance (
self . alice_monero_wallet . as_ref ( ) ,
Ordering ::Less ,
self . alice_redeemed_xmr_balance ( ) ,
)
. await
. unwrap ( ) ;
2021-01-18 03:56:43 -05:00
}
2021-03-16 02:08:19 -04:00
pub async fn assert_alice_refunded ( & mut self , state : AliceState ) {
2021-02-15 00:13:29 -05:00
assert! ( matches! ( state , AliceState ::XmrRefunded ) ) ;
2021-01-18 03:56:43 -05:00
2021-03-25 21:44:40 -04:00
assert_eventual_balance (
self . alice_bitcoin_wallet . as_ref ( ) ,
Ordering ::Equal ,
self . alice_refunded_btc_balance ( ) ,
)
. await
. unwrap ( ) ;
2021-03-16 04:24:41 -04:00
// Alice pays fees - comparison does not take exact lock fee into account
2021-03-25 21:44:40 -04:00
assert_eventual_balance (
self . alice_monero_wallet . as_ref ( ) ,
Ordering ::Greater ,
self . alice_refunded_xmr_balance ( ) ,
)
. await
. unwrap ( ) ;
2021-01-18 03:56:43 -05:00
}
pub async fn assert_alice_punished ( & self , state : AliceState ) {
assert! ( matches! ( state , AliceState ::BtcPunished ) ) ;
2021-03-25 21:44:40 -04:00
assert_eventual_balance (
self . alice_bitcoin_wallet . as_ref ( ) ,
Ordering ::Equal ,
2021-04-30 20:08:54 -04:00
self . alice_punished_btc_balance ( ) . await ,
2021-03-25 21:44:40 -04:00
)
. await
. unwrap ( ) ;
assert_eventual_balance (
self . alice_monero_wallet . as_ref ( ) ,
Ordering ::Less ,
self . alice_punished_xmr_balance ( ) ,
)
. await
. unwrap ( ) ;
2021-01-18 03:56:43 -05:00
}
pub async fn assert_bob_redeemed ( & self , state : BobState ) {
2021-03-25 21:44:40 -04:00
assert_eventual_balance (
self . bob_bitcoin_wallet . as_ref ( ) ,
Ordering ::Equal ,
self . bob_redeemed_btc_balance ( state ) . await . unwrap ( ) ,
)
. await
. unwrap ( ) ;
2021-01-18 03:56:43 -05:00
2021-03-02 06:07:48 -05:00
// unload the generated wallet by opening the original wallet
2021-03-16 04:24:41 -04:00
self . bob_monero_wallet . re_open ( ) . await . unwrap ( ) ;
2021-03-02 06:07:48 -05:00
2021-03-25 21:44:40 -04:00
assert_eventual_balance (
self . bob_monero_wallet . as_ref ( ) ,
Ordering ::Greater ,
self . bob_redeemed_xmr_balance ( ) ,
)
. await
. unwrap ( ) ;
2021-01-18 03:56:43 -05:00
}
pub async fn assert_bob_refunded ( & self , state : BobState ) {
2021-03-04 01:07:02 -05:00
self . bob_bitcoin_wallet . sync ( ) . await . unwrap ( ) ;
2021-02-01 18:39:34 -05:00
2021-01-18 03:56:43 -05:00
let lock_tx_id = if let BobState ::BtcRefunded ( state4 ) = state {
state4 . tx_lock_id ( )
} else {
2021-01-26 21:41:08 -05:00
panic! ( " Bob in not in btc refunded state: {:?} " , state ) ;
2021-01-18 03:56:43 -05:00
} ;
let lock_tx_bitcoin_fee = self
2021-01-18 23:09:05 -05:00
. bob_bitcoin_wallet
2021-01-18 03:56:43 -05:00
. transaction_fee ( lock_tx_id )
. await
. unwrap ( ) ;
2021-03-25 20:54:28 -04:00
let btc_balance_after_swap = self . bob_bitcoin_wallet . balance ( ) . await . unwrap ( ) ;
2021-01-18 03:56:43 -05:00
2021-04-30 20:08:54 -04:00
let cancel_fee = self
. alice_bitcoin_wallet
2021-05-03 21:34:00 -04:00
. estimate_fee ( TxCancel ::weight ( ) , self . btc_amount )
2021-04-30 20:08:54 -04:00
. await
. expect ( " To estimate fee correctly " ) ;
let refund_fee = self
. alice_bitcoin_wallet
2021-05-03 21:34:00 -04:00
. estimate_fee ( TxRefund ::weight ( ) , self . btc_amount )
2021-04-30 20:08:54 -04:00
. await
. expect ( " To estimate fee correctly " ) ;
2021-04-28 20:40:04 -04:00
let bob_cancelled_and_refunded = btc_balance_after_swap
2021-04-30 20:08:54 -04:00
= = self . bob_starting_balances . btc - lock_tx_bitcoin_fee - cancel_fee - refund_fee ;
2021-01-18 03:56:43 -05:00
2021-04-28 20:40:04 -04:00
assert! ( bob_cancelled_and_refunded ) ;
2021-01-18 03:56:43 -05:00
2021-03-25 21:44:40 -04:00
assert_eventual_balance (
self . bob_monero_wallet . as_ref ( ) ,
Ordering ::Equal ,
self . bob_refunded_xmr_balance ( ) ,
)
. await
. unwrap ( ) ;
2021-01-18 03:56:43 -05:00
}
pub async fn assert_bob_punished ( & self , state : BobState ) {
2021-03-25 21:44:40 -04:00
assert_eventual_balance (
self . bob_bitcoin_wallet . as_ref ( ) ,
Ordering ::Equal ,
self . bob_punished_btc_balance ( state ) . await . unwrap ( ) ,
)
. await
. unwrap ( ) ;
assert_eventual_balance (
self . bob_monero_wallet . as_ref ( ) ,
Ordering ::Equal ,
self . bob_punished_xmr_balance ( ) ,
)
. await
. unwrap ( ) ;
}
fn alice_redeemed_xmr_balance ( & self ) -> monero ::Amount {
self . alice_starting_balances . xmr - self . xmr_amount
}
2021-04-30 20:08:54 -04:00
async fn alice_redeemed_btc_balance ( & self ) -> bitcoin ::Amount {
let fee = self
. alice_bitcoin_wallet
2021-05-03 21:34:00 -04:00
. estimate_fee ( TxRedeem ::weight ( ) , self . btc_amount )
2021-04-30 20:08:54 -04:00
. await
. expect ( " To estimate fee correctly " ) ;
self . alice_starting_balances . btc + self . btc_amount - fee
2021-03-25 21:44:40 -04:00
}
fn bob_redeemed_xmr_balance ( & self ) -> monero ::Amount {
self . bob_starting_balances . xmr
}
async fn bob_redeemed_btc_balance ( & self , state : BobState ) -> Result < bitcoin ::Amount > {
self . bob_bitcoin_wallet . sync ( ) . await ? ;
let lock_tx_id = if let BobState ::XmrRedeemed { tx_lock_id } = state {
tx_lock_id
} else {
bail! ( " Bob in not in xmr redeemed state: {:?} " , state ) ;
} ;
let lock_tx_bitcoin_fee = self . bob_bitcoin_wallet . transaction_fee ( lock_tx_id ) . await ? ;
Ok ( self . bob_starting_balances . btc - self . btc_amount - lock_tx_bitcoin_fee )
}
fn alice_refunded_xmr_balance ( & self ) -> monero ::Amount {
self . alice_starting_balances . xmr - self . xmr_amount
}
fn alice_refunded_btc_balance ( & self ) -> bitcoin ::Amount {
self . alice_starting_balances . btc
}
fn bob_refunded_xmr_balance ( & self ) -> monero ::Amount {
self . bob_starting_balances . xmr
}
fn alice_punished_xmr_balance ( & self ) -> monero ::Amount {
self . alice_starting_balances . xmr - self . xmr_amount
}
2021-04-30 20:08:54 -04:00
async fn alice_punished_btc_balance ( & self ) -> bitcoin ::Amount {
let cancel_fee = self
. alice_bitcoin_wallet
2021-05-03 21:34:00 -04:00
. estimate_fee ( TxCancel ::weight ( ) , self . btc_amount )
2021-04-30 20:08:54 -04:00
. await
. expect ( " To estimate fee correctly " ) ;
let punish_fee = self
. alice_bitcoin_wallet
2021-05-03 21:34:00 -04:00
. estimate_fee ( TxPunish ::weight ( ) , self . btc_amount )
2021-04-30 20:08:54 -04:00
. await
. expect ( " To estimate fee correctly " ) ;
self . alice_starting_balances . btc + self . btc_amount - cancel_fee - punish_fee
2021-03-25 21:44:40 -04:00
}
fn bob_punished_xmr_balance ( & self ) -> monero ::Amount {
self . bob_starting_balances . xmr
}
async fn bob_punished_btc_balance ( & self , state : BobState ) -> Result < bitcoin ::Amount > {
self . bob_bitcoin_wallet . sync ( ) . await ? ;
2021-02-01 18:39:34 -05:00
2021-01-18 03:56:43 -05:00
let lock_tx_id = if let BobState ::BtcPunished { tx_lock_id } = state {
tx_lock_id
} else {
2021-03-25 21:44:40 -04:00
bail! ( " Bob in not in btc punished state: {:?} " , state ) ;
2021-01-18 03:56:43 -05:00
} ;
2021-03-25 21:44:40 -04:00
let lock_tx_bitcoin_fee = self . bob_bitcoin_wallet . transaction_fee ( lock_tx_id ) . await ? ;
2021-01-18 03:56:43 -05:00
2021-03-25 21:44:40 -04:00
Ok ( self . bob_starting_balances . btc - self . btc_amount - lock_tx_bitcoin_fee )
}
}
async fn assert_eventual_balance < A : fmt ::Display + PartialOrd > (
wallet : & impl Wallet < Amount = A > ,
ordering : Ordering ,
expected : A ,
) -> Result < ( ) > {
let ordering_str = match ordering {
Ordering ::Less = > " less than " ,
Ordering ::Equal = > " equal to " ,
Ordering ::Greater = > " greater than " ,
} ;
let mut current_balance = wallet . get_balance ( ) . await ? ;
let assertion = async {
while current_balance . partial_cmp ( & expected ) . unwrap ( ) ! = ordering {
tokio ::time ::sleep ( Duration ::from_millis ( 500 ) ) . await ;
wallet . refresh ( ) . await ? ;
current_balance = wallet . get_balance ( ) . await ? ;
}
tracing ::debug! (
" Assertion successful! Balance {} is {} {} " ,
current_balance ,
2021-04-15 21:12:27 -04:00
ordering_str ,
expected
) ;
Result ::< _ , anyhow ::Error > ::Ok ( ( ) )
2021-01-20 05:42:35 -05:00
} ;
2021-01-17 21:03:56 -05:00
2021-04-15 21:12:27 -04:00
let timeout = Duration ::from_secs ( 10 ) ;
2021-04-08 04:56:26 -04:00
2021-04-15 21:12:27 -04:00
tokio ::time ::timeout ( timeout , assertion )
. await
. with_context ( | | {
format! (
" Expected balance to be {} {} after at most {}s but was {} " ,
ordering_str ,
expected ,
timeout . as_secs ( ) ,
current_balance
)
} ) ? ? ;
2021-01-15 02:34:46 -05:00
2021-04-15 21:12:27 -04:00
Ok ( ( ) )
2021-01-14 19:26:32 -05:00
}
2021-04-15 21:12:27 -04:00
#[ async_trait ]
trait Wallet {
type Amount ;
2021-03-26 00:38:30 -04:00
2021-04-15 21:12:27 -04:00
async fn refresh ( & self ) -> Result < ( ) > ;
async fn get_balance ( & self ) -> Result < Self ::Amount > ;
}
2021-03-26 00:38:30 -04:00
2021-04-15 21:12:27 -04:00
#[ async_trait ]
impl Wallet for monero ::Wallet {
type Amount = monero ::Amount ;
2021-03-26 00:38:30 -04:00
2021-04-15 21:12:27 -04:00
async fn refresh ( & self ) -> Result < ( ) > {
self . refresh ( ) . await ? ;
2021-03-26 00:38:30 -04:00
2021-04-15 21:12:27 -04:00
Ok ( ( ) )
}
async fn get_balance ( & self ) -> Result < Self ::Amount > {
self . get_balance ( ) . await
}
}
#[ async_trait ]
impl Wallet for bitcoin ::Wallet {
type Amount = bitcoin ::Amount ;
async fn refresh ( & self ) -> Result < ( ) > {
self . sync ( ) . await
}
async fn get_balance ( & self ) -> Result < Self ::Amount > {
self . balance ( ) . await
}
2021-03-26 00:38:30 -04:00
}
2021-02-01 18:39:34 -05:00
fn random_prefix ( ) -> String {
2021-03-03 19:28:58 -05:00
use rand ::distributions ::Alphanumeric ;
use rand ::{ thread_rng , Rng } ;
2021-02-01 18:39:34 -05:00
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 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
2021-03-02 18:58:47 -05:00
. createwallet ( BITCOIN_TEST_WALLET_NAME , None , None , None , None )
2021-02-01 18:39:34 -05:00
. await ? ;
let reward_address = bitcoind_client
2021-03-02 18:58:47 -05:00
. with_wallet ( BITCOIN_TEST_WALLET_NAME ) ?
2021-02-01 18:39:34 -05:00
. 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
2021-03-02 18:58:47 -05:00
. send_to_address ( BITCOIN_TEST_WALLET_NAME , address . clone ( ) , amount )
2021-02-01 18:39:34 -05:00
. await ? ;
// Confirm the transaction
let reward_address = bitcoind_client
2021-03-02 18:58:47 -05:00
. with_wallet ( BITCOIN_TEST_WALLET_NAME ) ?
2021-02-01 18:39:34 -05:00
. getnewaddress ( None , None )
. await ? ;
bitcoind_client
. generatetoaddress ( 1 , reward_address , None )
. await ? ;
Ok ( ( ) )
}
2020-12-09 22:59:09 -05:00
// This is just to keep the containers alive
#[ allow(dead_code) ]
2021-01-17 20:57:27 -05:00
struct Containers < ' a > {
2021-02-01 18:39:34 -05:00
bitcoind_url : Url ,
bitcoind : Container < ' a , Cli , bitcoind ::Bitcoind > ,
2021-04-19 03:26:11 -04:00
monerod_container : Container < ' a , Cli , image ::Monerod > ,
monero_wallet_rpc_containers : Vec < Container < ' a , Cli , image ::MoneroWalletRpc > > ,
2021-02-01 18:39:34 -05:00
electrs : Container < ' a , Cli , electrs ::Electrs > ,
2020-12-09 22:59:09 -05:00
}
2021-01-21 03:37:52 -05:00
pub mod alice_run_until {
use swap ::protocol ::alice ::AliceState ;
2021-03-25 04:55:54 -04:00
pub fn is_xmr_lock_transaction_sent ( state : & AliceState ) -> bool {
matches! ( state , AliceState ::XmrLockTransactionSent { .. } )
2021-01-21 03:37:52 -05:00
}
pub fn is_encsig_learned ( state : & AliceState ) -> bool {
2021-02-03 00:20:59 -05:00
matches! ( state , AliceState ::EncSigLearned { .. } )
2021-01-21 03:37:52 -05:00
}
}
pub mod bob_run_until {
use swap ::protocol ::bob ::BobState ;
pub fn is_btc_locked ( state : & BobState ) -> bool {
2021-12-19 22:58:11 -05:00
matches! ( state , BobState ::BtcLocked { .. } )
2021-01-21 03:37:52 -05:00
}
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 ( .. ) )
}
}
2021-01-27 01:03:52 -05:00
pub struct SlowCancelConfig ;
2021-03-16 23:55:42 -04:00
impl GetConfig for SlowCancelConfig {
fn get_config ( ) -> Config {
Config {
2021-02-14 20:19:43 -05:00
bitcoin_cancel_timelock : CancelTimelock ::new ( 180 ) ,
2021-03-16 23:55:42 -04:00
.. env ::Regtest ::get_config ( )
2021-01-27 01:03:52 -05:00
}
}
}
pub struct FastCancelConfig ;
2021-03-16 23:55:42 -04:00
impl GetConfig for FastCancelConfig {
fn get_config ( ) -> Config {
Config {
2021-03-24 20:16:34 -04:00
bitcoin_cancel_timelock : CancelTimelock ::new ( 10 ) ,
2021-03-16 23:55:42 -04:00
.. env ::Regtest ::get_config ( )
2021-01-27 01:03:52 -05:00
}
}
}
pub struct FastPunishConfig ;
2021-03-16 23:55:42 -04:00
impl GetConfig for FastPunishConfig {
fn get_config ( ) -> Config {
Config {
2021-03-24 20:16:34 -04:00
bitcoin_cancel_timelock : CancelTimelock ::new ( 10 ) ,
2021-03-26 00:38:30 -04:00
bitcoin_punish_timelock : PunishTimelock ::new ( 10 ) ,
2021-03-16 23:55:42 -04:00
.. env ::Regtest ::get_config ( )
2021-01-27 01:03:52 -05:00
}
}
}