2020-11-27 00:30:07 +00:00
//! Run an XMR/BTC swap in the role of Alice.
//! Alice holds XMR and wishes receive BTC.
2021-01-05 03:08:36 +00:00
use crate ::{
bitcoin ,
2021-01-21 02:43:25 +00:00
bitcoin ::{
2021-02-15 01:19:43 +00:00
ExpiredTimelocks , TransactionBlockHeight , WaitForTransactionFinality ,
2021-01-21 02:43:25 +00:00
WatchForRawTransaction ,
} ,
2021-01-18 08:56:43 +00:00
database ,
database ::Database ,
2021-01-29 06:27:50 +00:00
execution_params ::ExecutionParams ,
2021-01-05 03:08:36 +00:00
monero ,
2020-11-27 00:30:07 +00:00
monero ::CreateWalletForOutput ,
2021-01-18 08:56:43 +00:00
protocol ::{
alice ,
alice ::{
event_loop ::EventLoopHandle ,
steps ::{
build_bitcoin_punish_transaction , build_bitcoin_redeem_transaction ,
2021-02-08 05:53:05 +00:00
extract_monero_private_key , lock_xmr , publish_bitcoin_punish_transaction ,
publish_bitcoin_redeem_transaction , publish_cancel_transaction ,
wait_for_bitcoin_encrypted_signature , wait_for_bitcoin_refund ,
wait_for_locked_bitcoin ,
2021-01-18 08:56:43 +00:00
} ,
AliceState ,
2021-01-05 03:08:36 +00:00
} ,
} ,
2020-11-27 00:30:07 +00:00
} ;
2021-01-21 00:20:57 +00:00
use anyhow ::{ bail , Result } ;
use async_recursion ::async_recursion ;
use futures ::{
future ::{ select , Either } ,
pin_mut ,
} ;
use rand ::{ CryptoRng , RngCore } ;
use std ::sync ::Arc ;
use tracing ::{ error , info } ;
use uuid ::Uuid ;
2020-11-27 00:30:07 +00:00
trait Rng : RngCore + CryptoRng + Send { }
impl < T > Rng for T where T : RngCore + CryptoRng + Send { }
2020-12-02 01:34:47 +00:00
pub fn is_complete ( state : & AliceState ) -> bool {
2020-12-02 01:36:47 +00:00
matches! (
state ,
AliceState ::XmrRefunded
| AliceState ::BtcRedeemed
2020-12-22 04:49:30 +00:00
| AliceState ::BtcPunished
2020-12-02 01:36:47 +00:00
| AliceState ::SafelyAborted
)
2020-12-02 01:34:47 +00:00
}
2021-01-18 08:56:43 +00:00
pub async fn run ( swap : alice ::Swap ) -> Result < AliceState > {
run_until ( swap , is_complete ) . await
}
2021-01-27 07:04:16 +00:00
#[ tracing::instrument(name = " swap " , skip(swap,is_target_state), fields(id = %swap.swap_id)) ]
2021-01-18 08:56:43 +00:00
pub async fn run_until (
swap : alice ::Swap ,
is_target_state : fn ( & AliceState ) -> bool ,
) -> Result < AliceState > {
2021-01-19 23:37:16 +00:00
run_until_internal (
2021-01-18 08:56:43 +00:00
swap . state ,
is_target_state ,
swap . event_loop_handle ,
swap . bitcoin_wallet ,
swap . monero_wallet ,
2021-01-27 02:33:32 +00:00
swap . execution_params ,
2021-01-18 08:56:43 +00:00
swap . swap_id ,
swap . db ,
)
. await
}
2020-11-27 00:30:07 +00:00
// State machine driver for swap execution
#[ async_recursion ]
2020-12-07 01:47:21 +00:00
#[ allow(clippy::too_many_arguments) ]
2021-01-19 23:37:16 +00:00
async fn run_until_internal (
2020-11-27 00:30:07 +00:00
state : AliceState ,
2020-12-02 01:36:47 +00:00
is_target_state : fn ( & AliceState ) -> bool ,
2020-12-10 02:55:29 +00:00
mut event_loop_handle : EventLoopHandle ,
2021-01-14 22:45:00 +00:00
bitcoin_wallet : Arc < bitcoin ::Wallet > ,
monero_wallet : Arc < monero ::Wallet > ,
2021-01-27 02:33:32 +00:00
execution_params : ExecutionParams ,
2020-12-07 01:47:21 +00:00
swap_id : Uuid ,
2021-02-09 00:15:55 +00:00
db : Arc < Database > ,
2020-12-14 10:30:45 +00:00
) -> Result < AliceState > {
2021-02-16 04:19:11 +00:00
info! ( " Current state: {} " , state ) ;
2020-12-02 01:36:47 +00:00
if is_target_state ( & state ) {
2020-12-14 10:30:45 +00:00
Ok ( state )
2020-12-02 01:34:47 +00:00
} else {
match state {
2021-02-09 00:15:55 +00:00
AliceState ::Started {
2020-12-02 01:36:47 +00:00
state3 ,
2021-01-22 03:21:27 +00:00
bob_peer_id ,
2020-12-02 01:36:47 +00:00
} = > {
2021-01-27 02:33:32 +00:00
let _ = wait_for_locked_bitcoin (
state3 . tx_lock . txid ( ) ,
bitcoin_wallet . clone ( ) ,
execution_params ,
)
. await ? ;
2020-11-27 00:30:07 +00:00
2021-01-22 03:56:11 +00:00
let state = AliceState ::BtcLocked {
bob_peer_id ,
state3 ,
2020-12-07 01:47:21 +00:00
} ;
2020-12-08 06:02:58 +00:00
let db_state = ( & state ) . into ( ) ;
2021-01-18 08:56:43 +00:00
db . insert_latest_state ( swap_id , database ::Swap ::Alice ( db_state ) )
2020-12-08 06:02:58 +00:00
. await ? ;
2021-01-19 23:37:16 +00:00
run_until_internal (
2020-12-07 01:47:21 +00:00
state ,
is_target_state ,
event_loop_handle ,
bitcoin_wallet ,
monero_wallet ,
2021-01-27 02:33:32 +00:00
execution_params ,
2020-12-07 01:47:21 +00:00
swap_id ,
db ,
)
. await
2020-12-02 01:36:47 +00:00
}
AliceState ::BtcLocked {
2021-01-22 03:21:27 +00:00
bob_peer_id ,
2020-12-02 01:36:47 +00:00
state3 ,
2020-12-07 01:47:21 +00:00
} = > {
2021-01-22 03:56:11 +00:00
lock_xmr (
bob_peer_id ,
* state3 . clone ( ) ,
& mut event_loop_handle ,
monero_wallet . clone ( ) ,
)
. await ? ;
2020-12-07 01:47:21 +00:00
2021-01-22 03:56:11 +00:00
let state = AliceState ::XmrLocked { state3 } ;
2020-12-07 01:47:21 +00:00
2020-12-08 06:02:58 +00:00
let db_state = ( & state ) . into ( ) ;
2021-01-18 08:56:43 +00:00
db . insert_latest_state ( swap_id , database ::Swap ::Alice ( db_state ) )
2020-12-08 06:02:58 +00:00
. await ? ;
2021-01-19 23:37:16 +00:00
run_until_internal (
2020-12-07 01:47:21 +00:00
state ,
is_target_state ,
event_loop_handle ,
bitcoin_wallet ,
monero_wallet ,
2021-01-27 02:33:32 +00:00
execution_params ,
2020-12-07 01:47:21 +00:00
swap_id ,
db ,
)
. await
}
2020-12-02 01:36:47 +00:00
AliceState ::XmrLocked { state3 } = > {
2020-12-22 04:47:09 +00:00
let state = match state3 . expired_timelocks ( bitcoin_wallet . as_ref ( ) ) . await ? {
ExpiredTimelocks ::None = > {
2021-01-07 00:53:07 +00:00
let wait_for_enc_sig =
wait_for_bitcoin_encrypted_signature ( & mut event_loop_handle ) ;
2020-12-07 01:47:21 +00:00
let state3_clone = state3 . clone ( ) ;
2020-12-22 04:47:09 +00:00
let cancel_timelock_expires = state3_clone
. wait_for_cancel_timelock_to_expire ( bitcoin_wallet . as_ref ( ) ) ;
2020-12-07 01:47:21 +00:00
pin_mut! ( wait_for_enc_sig ) ;
2020-12-22 04:47:09 +00:00
pin_mut! ( cancel_timelock_expires ) ;
2020-12-07 01:47:21 +00:00
2020-12-22 04:47:09 +00:00
match select ( cancel_timelock_expires , wait_for_enc_sig ) . await {
Either ::Left ( _ ) = > AliceState ::CancelTimelockExpired { state3 } ,
2020-12-22 03:53:21 +00:00
Either ::Right ( ( enc_sig , _ ) ) = > AliceState ::EncSigLearned {
2020-12-07 01:47:21 +00:00
state3 ,
2021-02-04 06:10:18 +00:00
encrypted_signature : Box ::new ( enc_sig ? ) ,
2020-12-07 01:47:21 +00:00
} ,
2020-12-11 05:49:19 +00:00
}
2020-12-02 01:36:47 +00:00
}
2020-12-22 04:47:09 +00:00
_ = > AliceState ::CancelTimelockExpired { state3 } ,
2020-12-07 01:47:21 +00:00
} ;
2020-12-11 05:49:19 +00:00
2020-12-08 06:02:58 +00:00
let db_state = ( & state ) . into ( ) ;
2021-01-18 08:56:43 +00:00
db . insert_latest_state ( swap_id , database ::Swap ::Alice ( db_state ) )
2020-12-08 06:02:58 +00:00
. await ? ;
2021-01-19 23:37:16 +00:00
run_until_internal (
2020-12-07 01:47:21 +00:00
state ,
is_target_state ,
event_loop_handle ,
bitcoin_wallet . clone ( ) ,
monero_wallet ,
2021-01-27 02:33:32 +00:00
execution_params ,
2020-12-07 01:47:21 +00:00
swap_id ,
db ,
)
. await
}
2020-12-22 03:53:21 +00:00
AliceState ::EncSigLearned {
2020-12-02 01:36:47 +00:00
state3 ,
2020-11-25 05:27:57 +00:00
encrypted_signature ,
2020-12-02 01:36:47 +00:00
} = > {
2021-01-19 03:44:54 +00:00
let state = match state3 . expired_timelocks ( bitcoin_wallet . as_ref ( ) ) . await ? {
ExpiredTimelocks ::None = > {
match build_bitcoin_redeem_transaction (
2021-02-04 06:10:18 +00:00
* encrypted_signature ,
2021-01-19 03:44:54 +00:00
& state3 . tx_lock ,
state3 . a . clone ( ) ,
state3 . s_a ,
state3 . B ,
& state3 . redeem_address ,
) {
Ok ( tx ) = > {
match publish_bitcoin_redeem_transaction ( tx , bitcoin_wallet . clone ( ) )
. await
{
Ok ( txid ) = > {
let publishded_redeem_tx = bitcoin_wallet
2021-01-27 02:33:32 +00:00
. wait_for_transaction_finality ( txid , execution_params )
2021-01-19 03:44:54 +00:00
. await ;
2020-12-21 06:33:18 +00:00
2021-01-19 03:44:54 +00:00
match publishded_redeem_tx {
2021-02-03 05:20:59 +00:00
Ok ( _ ) = > AliceState ::BtcRedeemed ,
2021-01-19 03:44:54 +00:00
Err ( e ) = > {
bail! ( " Waiting for Bitcoin transaction finality failed with {}! The redeem transaction was published, but it is not ensured that the transaction was included! You're screwed. " , e )
}
}
}
Err ( e ) = > {
error! ( " Publishing the redeem transaction failed with {}, attempting to wait for cancellation now. If you restart the application before the timelock is expired publishing the redeem transaction will be retried. " , e ) ;
state3
. wait_for_cancel_timelock_to_expire (
bitcoin_wallet . as_ref ( ) ,
)
. await ? ;
2020-12-14 23:26:53 +00:00
2021-01-19 03:44:54 +00:00
AliceState ::CancelTimelockExpired { state3 }
}
}
}
Err ( e ) = > {
error! ( " Constructing the redeem transaction failed with {}, attempting to wait for cancellation now. " , e ) ;
state3
. wait_for_cancel_timelock_to_expire ( bitcoin_wallet . as_ref ( ) )
. await ? ;
AliceState ::CancelTimelockExpired { state3 }
}
}
2020-12-02 01:36:47 +00:00
}
2021-01-19 03:44:54 +00:00
_ = > AliceState ::CancelTimelockExpired { state3 } ,
2020-12-02 01:36:47 +00:00
} ;
2020-11-27 00:30:07 +00:00
2020-12-08 06:02:58 +00:00
let db_state = ( & state ) . into ( ) ;
2021-01-18 08:56:43 +00:00
db . insert_latest_state ( swap_id , database ::Swap ::Alice ( db_state ) )
2020-12-08 06:02:58 +00:00
. await ? ;
2021-01-19 23:37:16 +00:00
run_until_internal (
2020-12-07 01:47:21 +00:00
state ,
2020-12-02 01:36:47 +00:00
is_target_state ,
2020-12-10 02:55:29 +00:00
event_loop_handle ,
2020-12-02 01:36:47 +00:00
bitcoin_wallet ,
monero_wallet ,
2021-01-27 02:33:32 +00:00
execution_params ,
2020-12-07 01:47:21 +00:00
swap_id ,
db ,
2020-12-02 01:36:47 +00:00
)
. await
}
2020-12-22 04:47:09 +00:00
AliceState ::CancelTimelockExpired { state3 } = > {
2020-12-02 01:36:47 +00:00
let tx_cancel = publish_cancel_transaction (
state3 . tx_lock . clone ( ) ,
state3 . a . clone ( ) ,
state3 . B ,
2020-12-22 04:47:09 +00:00
state3 . cancel_timelock ,
2020-12-02 01:36:47 +00:00
state3 . tx_cancel_sig_bob . clone ( ) ,
bitcoin_wallet . clone ( ) ,
)
. await ? ;
2020-11-27 00:30:07 +00:00
2021-02-08 03:18:33 +00:00
let state = AliceState ::BtcCancelled {
state3 ,
tx_cancel : Box ::new ( tx_cancel ) ,
} ;
2020-12-08 06:02:58 +00:00
let db_state = ( & state ) . into ( ) ;
2021-01-18 08:56:43 +00:00
db . insert_latest_state ( swap_id , database ::Swap ::Alice ( db_state ) )
2020-12-08 06:02:58 +00:00
. await ? ;
2021-01-19 23:37:16 +00:00
run_until_internal (
2020-12-07 01:47:21 +00:00
state ,
2020-12-02 01:36:47 +00:00
is_target_state ,
2020-12-10 02:55:29 +00:00
event_loop_handle ,
2020-12-02 01:36:47 +00:00
bitcoin_wallet ,
monero_wallet ,
2021-01-27 02:33:32 +00:00
execution_params ,
2020-12-07 01:47:21 +00:00
swap_id ,
db ,
2020-12-02 01:36:47 +00:00
)
. await
}
AliceState ::BtcCancelled { state3 , tx_cancel } = > {
let tx_cancel_height = bitcoin_wallet
. transaction_block_height ( tx_cancel . txid ( ) )
2021-02-15 05:13:29 +00:00
. await ? ;
2020-11-27 00:30:07 +00:00
2020-12-02 01:36:47 +00:00
let ( tx_refund , published_refund_tx ) = wait_for_bitcoin_refund (
& tx_cancel ,
tx_cancel_height ,
state3 . punish_timelock ,
& state3 . refund_address ,
bitcoin_wallet . clone ( ) ,
)
. await ? ;
2020-11-27 00:30:07 +00:00
2020-12-02 01:36:47 +00:00
// TODO(Franck): Review error handling
match published_refund_tx {
None = > {
2020-12-07 01:47:21 +00:00
let state = AliceState ::BtcPunishable { tx_refund , state3 } ;
2020-12-08 06:02:58 +00:00
let db_state = ( & state ) . into ( ) ;
2021-01-18 08:56:43 +00:00
db . insert_latest_state ( swap_id , database ::Swap ::Alice ( db_state ) )
2020-12-08 06:02:58 +00:00
. await ? ;
2021-01-18 07:08:13 +00:00
2021-01-19 23:37:16 +00:00
run_until_internal (
2020-12-07 01:47:21 +00:00
state ,
2021-01-18 07:08:13 +00:00
is_target_state ,
2020-12-10 02:55:29 +00:00
event_loop_handle ,
2020-12-02 01:36:47 +00:00
bitcoin_wallet . clone ( ) ,
monero_wallet ,
2021-01-27 02:33:32 +00:00
execution_params ,
2020-12-07 01:47:21 +00:00
swap_id ,
db ,
2020-12-02 01:36:47 +00:00
)
. await
}
Some ( published_refund_tx ) = > {
2020-12-02 22:48:18 +00:00
let spend_key = extract_monero_private_key (
published_refund_tx ,
tx_refund ,
state3 . s_a ,
state3 . a . clone ( ) ,
state3 . S_b_bitcoin ,
) ? ;
2020-12-07 01:47:21 +00:00
let state = AliceState ::BtcRefunded { spend_key , state3 } ;
2020-12-08 06:02:58 +00:00
let db_state = ( & state ) . into ( ) ;
2021-01-18 08:56:43 +00:00
db . insert_latest_state ( swap_id , database ::Swap ::Alice ( db_state ) )
2020-12-08 06:02:58 +00:00
. await ? ;
2021-01-19 23:37:16 +00:00
run_until_internal (
2020-12-07 01:47:21 +00:00
state ,
2020-12-02 01:36:47 +00:00
is_target_state ,
2020-12-10 02:55:29 +00:00
event_loop_handle ,
2020-12-02 01:36:47 +00:00
bitcoin_wallet . clone ( ) ,
monero_wallet ,
2021-01-27 02:33:32 +00:00
execution_params ,
2020-12-07 01:47:21 +00:00
swap_id ,
db ,
2020-12-02 01:36:47 +00:00
)
. await
}
2020-11-27 00:30:07 +00:00
}
}
2020-12-02 22:48:18 +00:00
AliceState ::BtcRefunded { spend_key , state3 } = > {
2020-12-02 01:36:47 +00:00
let view_key = state3 . v ;
2020-11-27 00:30:07 +00:00
2020-12-02 01:36:47 +00:00
monero_wallet
2021-01-06 05:31:47 +00:00
. create_and_load_wallet_for_output ( spend_key , view_key , None )
2020-12-02 01:36:47 +00:00
. await ? ;
2020-11-27 00:30:07 +00:00
2020-12-07 01:47:21 +00:00
let state = AliceState ::XmrRefunded ;
2020-12-08 06:02:58 +00:00
let db_state = ( & state ) . into ( ) ;
2021-01-18 08:56:43 +00:00
db . insert_latest_state ( swap_id , database ::Swap ::Alice ( db_state ) )
2020-12-08 06:02:58 +00:00
. await ? ;
2020-12-14 10:30:45 +00:00
Ok ( state )
2020-12-02 01:36:47 +00:00
}
AliceState ::BtcPunishable { tx_refund , state3 } = > {
let signed_tx_punish = build_bitcoin_punish_transaction (
& state3 . tx_lock ,
2020-12-22 04:47:09 +00:00
state3 . cancel_timelock ,
2020-12-02 01:36:47 +00:00
& state3 . punish_address ,
state3 . punish_timelock ,
state3 . tx_punish_sig_bob . clone ( ) ,
state3 . a . clone ( ) ,
state3 . B ,
) ? ;
2020-11-27 00:30:07 +00:00
2020-12-02 01:36:47 +00:00
let punish_tx_finalised = publish_bitcoin_punish_transaction (
signed_tx_punish ,
bitcoin_wallet . clone ( ) ,
2021-01-27 02:33:32 +00:00
execution_params ,
2020-12-02 01:36:47 +00:00
) ;
2020-11-27 00:30:07 +00:00
2020-12-02 01:36:47 +00:00
let refund_tx_seen = bitcoin_wallet . watch_for_raw_transaction ( tx_refund . txid ( ) ) ;
2020-11-27 00:30:07 +00:00
2020-12-02 01:36:47 +00:00
pin_mut! ( punish_tx_finalised ) ;
pin_mut! ( refund_tx_seen ) ;
2020-11-27 00:30:07 +00:00
2021-02-09 01:13:43 +00:00
match select ( refund_tx_seen , punish_tx_finalised ) . await {
Either ::Left ( ( published_refund_tx , _ ) ) = > {
let spend_key = extract_monero_private_key (
2021-02-15 05:13:29 +00:00
published_refund_tx ? ,
2021-02-09 01:13:43 +00:00
tx_refund ,
state3 . s_a ,
state3 . a . clone ( ) ,
state3 . S_b_bitcoin ,
) ? ;
let state = AliceState ::BtcRefunded { spend_key , state3 } ;
2020-12-08 06:02:58 +00:00
let db_state = ( & state ) . into ( ) ;
2021-01-18 08:56:43 +00:00
db . insert_latest_state ( swap_id , database ::Swap ::Alice ( db_state ) )
2020-12-08 06:02:58 +00:00
. await ? ;
2021-01-19 23:37:16 +00:00
run_until_internal (
2020-12-07 01:47:21 +00:00
state ,
2020-12-02 01:36:47 +00:00
is_target_state ,
2020-12-10 02:55:29 +00:00
event_loop_handle ,
2020-12-02 01:36:47 +00:00
bitcoin_wallet . clone ( ) ,
monero_wallet ,
2021-01-27 02:33:32 +00:00
execution_params ,
2020-12-07 01:47:21 +00:00
swap_id ,
db ,
2020-12-02 01:36:47 +00:00
)
. await
}
2021-02-09 01:13:43 +00:00
Either ::Right ( _ ) = > {
let state = AliceState ::BtcPunished ;
2020-12-08 06:02:58 +00:00
let db_state = ( & state ) . into ( ) ;
2021-01-18 08:56:43 +00:00
db . insert_latest_state ( swap_id , database ::Swap ::Alice ( db_state ) )
2020-12-08 06:02:58 +00:00
. await ? ;
2021-01-19 23:37:16 +00:00
run_until_internal (
2020-12-07 01:47:21 +00:00
state ,
2020-12-02 01:36:47 +00:00
is_target_state ,
2020-12-10 02:55:29 +00:00
event_loop_handle ,
2020-12-02 01:36:47 +00:00
bitcoin_wallet . clone ( ) ,
monero_wallet ,
2021-01-27 02:33:32 +00:00
execution_params ,
2020-12-07 01:47:21 +00:00
swap_id ,
db ,
2020-12-02 01:36:47 +00:00
)
. await
}
2020-11-27 00:30:07 +00:00
}
}
2020-12-14 10:30:45 +00:00
AliceState ::XmrRefunded = > Ok ( AliceState ::XmrRefunded ) ,
AliceState ::BtcRedeemed = > Ok ( AliceState ::BtcRedeemed ) ,
2020-12-22 04:49:30 +00:00
AliceState ::BtcPunished = > Ok ( AliceState ::BtcPunished ) ,
2020-12-14 10:30:45 +00:00
AliceState ::SafelyAborted = > Ok ( AliceState ::SafelyAborted ) ,
2020-11-27 00:30:07 +00:00
}
2020-12-02 01:34:47 +00:00
}
2020-11-27 00:30:07 +00:00
}