2021-05-03 07:35:41 +00:00
use crate ::bitcoin ::{ ExpiredTimelocks , TxCancel , TxRefund } ;
2021-06-25 03:40:33 +00:00
use crate ::cli ::EventLoopHandle ;
use crate ::network ::swap_setup ::bob ::NewSwap ;
2021-03-04 00:28:58 +00:00
use crate ::protocol ::bob ;
use crate ::protocol ::bob ::state ::* ;
use crate ::{ bitcoin , monero } ;
2021-03-11 07:16:00 +00:00
use anyhow ::{ bail , Context , Result } ;
2021-01-21 00:20:57 +00:00
use tokio ::select ;
2021-04-08 05:35:21 +00:00
use uuid ::Uuid ;
2020-12-02 01:09:43 +00:00
pub fn is_complete ( state : & BobState ) -> bool {
2020-12-02 01:36:47 +00:00
matches! (
state ,
2020-12-11 05:49:19 +00:00
BobState ::BtcRefunded ( .. )
2021-01-18 03:45:47 +00:00
| BobState ::XmrRedeemed { .. }
| BobState ::BtcPunished { .. }
2020-12-02 01:36:47 +00:00
| BobState ::SafelyAborted
)
}
2021-01-18 08:56:43 +00:00
#[ allow(clippy::too_many_arguments) ]
pub async fn run ( swap : bob ::Swap ) -> Result < BobState > {
run_until ( swap , is_complete ) . await
}
pub async fn run_until (
2021-03-26 04:01:17 +00:00
mut swap : bob ::Swap ,
2021-01-18 08:56:43 +00:00
is_target_state : fn ( & BobState ) -> bool ,
) -> Result < BobState > {
2021-03-26 04:01:17 +00:00
let mut current_state = swap . state ;
while ! is_target_state ( & current_state ) {
current_state = next_state (
2021-04-16 01:48:15 +00:00
swap . id ,
2021-09-28 00:15:31 +00:00
current_state . clone ( ) ,
2021-03-26 04:01:17 +00:00
& mut swap . event_loop_handle ,
swap . bitcoin_wallet . as_ref ( ) ,
swap . monero_wallet . as_ref ( ) ,
2021-07-06 08:34:09 +00:00
swap . monero_receive_address ,
2021-03-26 04:01:17 +00:00
)
. await ? ;
swap . db
2021-09-28 00:15:31 +00:00
. insert_latest_state ( swap . id , current_state . clone ( ) . into ( ) )
2021-03-26 04:01:17 +00:00
. await ? ;
}
Ok ( current_state )
2021-01-18 08:56:43 +00:00
}
2021-03-26 04:01:17 +00:00
async fn next_state (
2021-04-08 05:35:21 +00:00
swap_id : Uuid ,
2020-11-12 00:06:34 +00:00
state : BobState ,
2021-03-26 04:01:17 +00:00
event_loop_handle : & mut EventLoopHandle ,
bitcoin_wallet : & bitcoin ::Wallet ,
monero_wallet : & monero ::Wallet ,
2021-07-06 08:34:09 +00:00
monero_receive_address : monero ::Address ,
2021-02-03 04:25:05 +00:00
) -> Result < BobState > {
2021-09-11 12:17:32 +00:00
tracing ::debug! ( % state , " Advancing state " ) ;
2021-02-15 02:01:38 +00:00
2021-03-26 04:01:17 +00:00
Ok ( match state {
2021-07-06 09:17:17 +00:00
BobState ::Started {
btc_amount ,
change_address ,
} = > {
2021-05-04 01:34:00 +00:00
let tx_refund_fee = bitcoin_wallet
. estimate_fee ( TxRefund ::weight ( ) , btc_amount )
. await ? ;
let tx_cancel_fee = bitcoin_wallet
. estimate_fee ( TxCancel ::weight ( ) , btc_amount )
. await ? ;
2020-12-18 06:39:04 +00:00
2021-06-24 03:54:26 +00:00
let state2 = event_loop_handle
. setup_swap ( NewSwap {
swap_id ,
btc : btc_amount ,
tx_refund_fee ,
tx_cancel_fee ,
2021-07-06 09:17:17 +00:00
bitcoin_refund_address : change_address ,
2021-06-24 03:54:26 +00:00
} )
. await ? ;
2021-03-18 02:16:21 +00:00
2021-09-09 06:58:17 +00:00
tracing ::info! ( % swap_id , " Starting new swap " ) ;
2021-06-24 03:54:26 +00:00
BobState ::SwapSetupCompleted ( state2 )
2021-03-18 02:16:21 +00:00
}
2021-06-24 03:54:26 +00:00
BobState ::SwapSetupCompleted ( state2 ) = > {
2021-12-20 03:58:11 +00:00
// Record the current monero wallet block height so we don't have to scan from
// block 0 once we create the redeem wallet.
// This has to be done **before** the Bitcoin is locked in order to ensure that
// if Bob goes offline the recorded wallet-height is correct.
// If we only record this later, it can happen that Bob publishes the Bitcoin
// transaction, goes offline, while offline Alice publishes Monero.
// If the Monero transaction gets confirmed before Bob comes online again then
// Bob would record a wallet-height that is past the lock transaction height,
// which can lead to the wallet not detect the transaction.
let monero_wallet_restore_blockheight = monero_wallet . block_height ( ) . await ? ;
2021-03-18 02:16:21 +00:00
// Alice and Bob have exchanged info
let ( state3 , tx_lock ) = state2 . lock_btc ( ) . await ? ;
let signed_tx = bitcoin_wallet
. sign_and_finalize ( tx_lock . clone ( ) . into ( ) )
2020-12-02 01:36:47 +00:00
. await
2021-03-18 02:16:21 +00:00
. context ( " Failed to sign Bitcoin lock transaction " ) ? ;
let ( .. ) = bitcoin_wallet . broadcast ( signed_tx , " lock " ) . await ? ;
2021-12-20 03:58:11 +00:00
BobState ::BtcLocked {
state3 ,
monero_wallet_restore_blockheight ,
}
2021-03-18 02:16:21 +00:00
}
// Bob has locked Btc
// Watch for Alice to Lock Xmr or for cancel timelock to elapse
2021-12-20 03:58:11 +00:00
BobState ::BtcLocked {
state3 ,
monero_wallet_restore_blockheight ,
} = > {
2021-03-23 02:49:57 +00:00
let tx_lock_status = bitcoin_wallet . subscribe_to ( state3 . tx_lock . clone ( ) ) . await ;
2021-03-26 04:01:17 +00:00
if let ExpiredTimelocks ::None = state3 . current_epoch ( bitcoin_wallet ) . await ? {
2021-03-18 02:16:21 +00:00
let transfer_proof_watcher = event_loop_handle . recv_transfer_proof ( ) ;
let cancel_timelock_expires =
2021-03-23 02:49:57 +00:00
tx_lock_status . wait_until_confirmed_with ( state3 . cancel_timelock ) ;
2021-03-18 02:16:21 +00:00
tracing ::info! ( " Waiting for Alice to lock Monero " ) ;
select! {
transfer_proof = transfer_proof_watcher = > {
2021-04-08 08:56:26 +00:00
let transfer_proof = transfer_proof ? ;
2021-03-18 02:16:21 +00:00
tracing ::info! ( txid = % transfer_proof . tx_hash ( ) , " Alice locked Monero " ) ;
BobState ::XmrLockProofReceived {
state : state3 ,
lock_transfer_proof : transfer_proof ,
monero_wallet_restore_blockheight
2020-12-22 06:08:17 +00:00
}
2021-03-18 02:16:21 +00:00
} ,
2021-09-02 03:29:52 +00:00
result = cancel_timelock_expires = > {
2022-10-19 12:55:39 +00:00
result ? ;
2021-03-18 02:16:21 +00:00
tracing ::info! ( " Alice took too long to lock Monero, cancelling the swap " ) ;
let state4 = state3 . cancel ( ) ;
BobState ::CancelTimelockExpired ( state4 )
2021-08-30 02:07:01 +00:00
} ,
2021-03-18 02:16:21 +00:00
}
} else {
let state4 = state3 . cancel ( ) ;
BobState ::CancelTimelockExpired ( state4 )
2021-03-18 02:18:48 +00:00
}
2021-03-18 02:16:21 +00:00
}
BobState ::XmrLockProofReceived {
state ,
lock_transfer_proof ,
monero_wallet_restore_blockheight ,
} = > {
2021-03-23 02:49:57 +00:00
let tx_lock_status = bitcoin_wallet . subscribe_to ( state . tx_lock . clone ( ) ) . await ;
2021-03-26 04:01:17 +00:00
if let ExpiredTimelocks ::None = state . current_epoch ( bitcoin_wallet ) . await ? {
2021-03-18 02:42:19 +00:00
let watch_request = state . lock_xmr_watch_request ( lock_transfer_proof ) ;
2021-03-18 02:16:21 +00:00
select! {
2021-03-18 02:42:19 +00:00
received_xmr = monero_wallet . watch_for_transfer ( watch_request ) = > {
match received_xmr {
Ok ( ( ) ) = > BobState ::XmrLocked ( state . xmr_locked ( monero_wallet_restore_blockheight ) ) ,
2021-07-06 06:42:05 +00:00
Err ( monero ::InsufficientFunds { expected , actual } ) = > {
2021-07-07 04:31:10 +00:00
tracing ::warn! ( % expected , % actual , " Insufficient Monero have been locked! " ) ;
tracing ::info! ( timelock = % state . cancel_timelock , " Waiting for cancel timelock to expire " ) ;
2021-03-18 02:42:19 +00:00
2021-07-06 06:42:05 +00:00
tx_lock_status . wait_until_confirmed_with ( state . cancel_timelock ) . await ? ;
BobState ::CancelTimelockExpired ( state . cancel ( ) )
2021-03-18 02:16:21 +00:00
} ,
2021-01-21 05:39:30 +00:00
}
2021-03-18 02:42:19 +00:00
}
2021-08-30 02:07:01 +00:00
result = tx_lock_status . wait_until_confirmed_with ( state . cancel_timelock ) = > {
2022-10-19 12:55:39 +00:00
result ? ;
2021-03-18 02:42:19 +00:00
BobState ::CancelTimelockExpired ( state . cancel ( ) )
2021-01-21 05:39:30 +00:00
}
2021-03-18 02:16:21 +00:00
}
} else {
2021-03-18 02:42:19 +00:00
BobState ::CancelTimelockExpired ( state . cancel ( ) )
2021-03-18 02:18:48 +00:00
}
2021-03-18 02:16:21 +00:00
}
BobState ::XmrLocked ( state ) = > {
2021-03-23 02:49:57 +00:00
let tx_lock_status = bitcoin_wallet . subscribe_to ( state . tx_lock . clone ( ) ) . await ;
2021-03-26 04:01:17 +00:00
if let ExpiredTimelocks ::None = state . expired_timelock ( bitcoin_wallet ) . await ? {
2021-03-18 02:16:21 +00:00
// Alice has locked Xmr
// Bob sends Alice his key
select! {
2021-08-31 05:21:44 +00:00
result = event_loop_handle . send_encrypted_signature ( state . tx_redeem_encsig ( ) ) = > {
match result {
Ok ( _ ) = > BobState ::EncSigSent ( state ) ,
Err ( bmrng ::error ::RequestError ::RecvError | bmrng ::error ::RequestError ::SendError ( _ ) ) = > bail! ( " Failed to communicate encrypted signature through event loop channel " ) ,
Err ( bmrng ::error ::RequestError ::RecvTimeoutError ) = > unreachable! ( " We construct the channel with no timeout " ) ,
}
2021-03-18 02:16:21 +00:00
} ,
2021-08-30 02:07:01 +00:00
result = tx_lock_status . wait_until_confirmed_with ( state . cancel_timelock ) = > {
2022-10-19 12:55:39 +00:00
result ? ;
2021-03-18 03:23:55 +00:00
BobState ::CancelTimelockExpired ( state . cancel ( ) )
2020-12-17 04:17:15 +00:00
}
2021-03-18 02:16:21 +00:00
}
} else {
2021-03-18 03:23:55 +00:00
BobState ::CancelTimelockExpired ( state . cancel ( ) )
2021-03-18 02:18:48 +00:00
}
2021-03-18 02:16:21 +00:00
}
BobState ::EncSigSent ( state ) = > {
2021-03-23 02:49:57 +00:00
let tx_lock_status = bitcoin_wallet . subscribe_to ( state . tx_lock . clone ( ) ) . await ;
2021-03-26 04:01:17 +00:00
if let ExpiredTimelocks ::None = state . expired_timelock ( bitcoin_wallet ) . await ? {
2021-03-18 02:16:21 +00:00
select! {
2021-03-26 04:01:17 +00:00
state5 = state . watch_for_redeem_btc ( bitcoin_wallet ) = > {
2021-03-18 02:16:21 +00:00
BobState ::BtcRedeemed ( state5 ? )
} ,
2021-08-30 02:07:01 +00:00
result = tx_lock_status . wait_until_confirmed_with ( state . cancel_timelock ) = > {
2022-10-19 12:55:39 +00:00
result ? ;
2021-03-18 03:23:55 +00:00
BobState ::CancelTimelockExpired ( state . cancel ( ) )
2020-12-02 01:36:47 +00:00
}
2021-03-18 02:16:21 +00:00
}
} else {
2021-03-18 03:23:55 +00:00
BobState ::CancelTimelockExpired ( state . cancel ( ) )
2021-03-18 02:18:48 +00:00
}
2021-03-18 02:16:21 +00:00
}
BobState ::BtcRedeemed ( state ) = > {
2021-03-29 01:08:12 +00:00
let ( spend_key , view_key ) = state . xmr_keys ( ) ;
2021-05-20 23:53:06 +00:00
let wallet_file_name = swap_id . to_string ( ) ;
2021-04-15 08:39:59 +00:00
if let Err ( e ) = monero_wallet
2021-04-08 05:35:21 +00:00
. create_from_and_load (
2021-05-20 23:53:06 +00:00
wallet_file_name . clone ( ) ,
2021-04-08 05:35:21 +00:00
spend_key ,
view_key ,
state . monero_wallet_restore_blockheight ,
)
2021-04-08 05:27:00 +00:00
. await
{
// In case we failed to refresh/sweep, when resuming the wallet might already
// exist! This is a very unlikely scenario, but if we don't take care of it we
// might not be able to ever transfer the Monero.
2021-04-15 08:39:59 +00:00
tracing ::warn! ( " Failed to generate monero wallet from keys: {:#} " , e ) ;
2021-05-20 23:53:06 +00:00
tracing ::info! ( % wallet_file_name ,
" Falling back to trying to open the the wallet if it already exists " ,
2021-04-15 08:39:59 +00:00
) ;
2021-05-20 23:53:06 +00:00
monero_wallet . open ( wallet_file_name ) . await ? ;
2021-04-08 05:27:00 +00:00
}
2021-03-04 02:54:13 +00:00
2021-03-18 02:16:21 +00:00
// Ensure that the generated wallet is synced so we have a proper balance
monero_wallet . refresh ( ) . await ? ;
// Sweep (transfer all funds) to the given address
2021-07-06 08:34:09 +00:00
let tx_hashes = monero_wallet . sweep_all ( monero_receive_address ) . await ? ;
2021-03-02 11:07:48 +00:00
2021-03-18 02:16:21 +00:00
for tx_hash in tx_hashes {
2021-07-06 06:42:05 +00:00
tracing ::info! ( % monero_receive_address , txid = % tx_hash . 0 , " Successfully transferred XMR to wallet " ) ;
2020-12-02 01:36:47 +00:00
}
2020-12-17 04:17:15 +00:00
2021-03-18 02:18:48 +00:00
BobState ::XmrRedeemed {
2021-03-18 02:16:21 +00:00
tx_lock_id : state . tx_lock_id ( ) ,
2021-03-18 02:18:48 +00:00
}
2021-03-18 02:16:21 +00:00
}
BobState ::CancelTimelockExpired ( state4 ) = > {
2021-03-26 04:01:17 +00:00
if state4 . check_for_tx_cancel ( bitcoin_wallet ) . await . is_err ( ) {
state4 . submit_tx_cancel ( bitcoin_wallet ) . await ? ;
2020-12-02 01:36:47 +00:00
}
2021-03-18 02:16:21 +00:00
2021-03-18 02:18:48 +00:00
BobState ::BtcCancelled ( state4 )
2021-03-18 02:16:21 +00:00
}
BobState ::BtcCancelled ( state ) = > {
// Bob has cancelled the swap
2021-03-26 04:01:17 +00:00
match state . expired_timelock ( bitcoin_wallet ) . await ? {
2021-03-18 02:16:21 +00:00
ExpiredTimelocks ::None = > {
bail! (
" Internal error: canceled state reached before cancel timelock was expired "
) ;
}
ExpiredTimelocks ::Cancel = > {
2021-05-03 07:35:41 +00:00
state . publish_refund_btc ( bitcoin_wallet ) . await ? ;
2021-03-18 02:16:21 +00:00
BobState ::BtcRefunded ( state )
}
2022-01-07 00:17:36 +00:00
ExpiredTimelocks ::Punish = > {
tracing ::info! ( " You have been punished for not refunding in time " ) ;
BobState ::BtcPunished {
tx_lock_id : state . tx_lock_id ( ) ,
}
}
2021-03-18 02:18:48 +00:00
}
2020-11-12 00:06:34 +00:00
}
2021-03-18 02:18:48 +00:00
BobState ::BtcRefunded ( state4 ) = > BobState ::BtcRefunded ( state4 ) ,
BobState ::BtcPunished { tx_lock_id } = > BobState ::BtcPunished { tx_lock_id } ,
BobState ::SafelyAborted = > BobState ::SafelyAborted ,
BobState ::XmrRedeemed { tx_lock_id } = > BobState ::XmrRedeemed { tx_lock_id } ,
2021-03-26 04:01:17 +00:00
} )
2020-11-12 00:06:34 +00:00
}