diff --git a/monero-rpc/src/rpc/wallet.rs b/monero-rpc/src/rpc/wallet.rs index cd92588c..67298b61 100644 --- a/monero-rpc/src/rpc/wallet.rs +++ b/monero-rpc/src/rpc/wallet.rs @@ -268,14 +268,8 @@ impl Client { address: &str, spend_key: &str, view_key: &str, - restore_height: Option, + restore_height: u32, ) -> Result { - let restore_height = if let Some(restore_height) = restore_height { - restore_height - } else { - 0 - }; - let params = GenerateFromKeysParams { restore_height, filename: view_key.into(), diff --git a/swap/src/database/alice.rs b/swap/src/database/alice.rs index 22b90afe..61a785af 100644 --- a/swap/src/database/alice.rs +++ b/swap/src/database/alice.rs @@ -6,6 +6,7 @@ use crate::{ }; use ::bitcoin::hashes::core::fmt::Display; use libp2p::PeerId; +use monero_rpc::wallet::BlockHeight; use serde::{Deserialize, Serialize}; // Large enum variant is fine because this is only used for database @@ -23,15 +24,29 @@ pub enum Alice { #[serde(with = "crate::serde_peer_id")] bob_peer_id: PeerId, }, - XmrLocked(alice::State3), + XmrLocked { + monero_wallet_restore_blockheight: BlockHeight, + state3: alice::State3, + }, EncSigLearned { + monero_wallet_restore_blockheight: BlockHeight, encrypted_signature: EncryptedSignature, state3: alice::State3, }, - CancelTimelockExpired(alice::State3), - BtcCancelled(alice::State3), - BtcPunishable(alice::State3), + CancelTimelockExpired { + monero_wallet_restore_blockheight: BlockHeight, + state3: alice::State3, + }, + BtcCancelled { + monero_wallet_restore_blockheight: BlockHeight, + state3: alice::State3, + }, + BtcPunishable { + monero_wallet_restore_blockheight: BlockHeight, + state3: alice::State3, + }, BtcRefunded { + monero_wallet_restore_blockheight: BlockHeight, state3: alice::State3, #[serde(with = "monero_private_key")] spend_key: monero::PrivateKey, @@ -53,7 +68,6 @@ impl From<&AliceState> for Alice { AliceState::Started { state3, bob_peer_id, - .. } => Alice::Started { state3: state3.as_ref().clone(), bob_peer_id: *bob_peer_id, @@ -61,32 +75,60 @@ impl From<&AliceState> for Alice { AliceState::BtcLocked { state3, bob_peer_id, - .. } => Alice::BtcLocked { state3: state3.as_ref().clone(), bob_peer_id: *bob_peer_id, }, - AliceState::XmrLocked { state3 } => Alice::XmrLocked(state3.as_ref().clone()), + AliceState::XmrLocked { + monero_wallet_restore_blockheight, + state3, + } => Alice::XmrLocked { + monero_wallet_restore_blockheight: *monero_wallet_restore_blockheight, + state3: state3.as_ref().clone(), + }, AliceState::EncSigLearned { + monero_wallet_restore_blockheight, state3, encrypted_signature, } => Alice::EncSigLearned { + monero_wallet_restore_blockheight: *monero_wallet_restore_blockheight, state3: state3.as_ref().clone(), encrypted_signature: *encrypted_signature.clone(), }, AliceState::BtcRedeemed => Alice::Done(AliceEndState::BtcRedeemed), - AliceState::BtcCancelled { state3, .. } => Alice::BtcCancelled(state3.as_ref().clone()), - AliceState::BtcRefunded { spend_key, state3 } => Alice::BtcRefunded { + AliceState::BtcCancelled { + monero_wallet_restore_blockheight, + state3, + .. + } => Alice::BtcCancelled { + monero_wallet_restore_blockheight: *monero_wallet_restore_blockheight, + state3: state3.as_ref().clone(), + }, + AliceState::BtcRefunded { + monero_wallet_restore_blockheight, + spend_key, + state3, + } => Alice::BtcRefunded { + monero_wallet_restore_blockheight: *monero_wallet_restore_blockheight, spend_key: *spend_key, state3: state3.as_ref().clone(), }, - AliceState::BtcPunishable { state3, .. } => { - Alice::BtcPunishable(state3.as_ref().clone()) - } + AliceState::BtcPunishable { + monero_wallet_restore_blockheight, + state3, + .. + } => Alice::BtcPunishable { + monero_wallet_restore_blockheight: *monero_wallet_restore_blockheight, + state3: state3.as_ref().clone(), + }, AliceState::XmrRefunded => Alice::Done(AliceEndState::XmrRefunded), - AliceState::CancelTimelockExpired { state3 } => { - Alice::CancelTimelockExpired(state3.as_ref().clone()) - } + AliceState::CancelTimelockExpired { + monero_wallet_restore_blockheight, + state3, + } => Alice::CancelTimelockExpired { + monero_wallet_restore_blockheight: *monero_wallet_restore_blockheight, + state3: state3.as_ref().clone(), + }, AliceState::BtcPunished => Alice::Done(AliceEndState::BtcPunished), AliceState::SafelyAborted => Alice::Done(AliceEndState::SafelyAborted), } @@ -110,33 +152,50 @@ impl From for AliceState { bob_peer_id, state3: Box::new(state3), }, - Alice::XmrLocked(state3) => AliceState::XmrLocked { + Alice::XmrLocked { + monero_wallet_restore_blockheight, + state3, + } => AliceState::XmrLocked { + monero_wallet_restore_blockheight, state3: Box::new(state3), }, Alice::EncSigLearned { + monero_wallet_restore_blockheight, state3: state, encrypted_signature, } => AliceState::EncSigLearned { + monero_wallet_restore_blockheight, state3: Box::new(state), encrypted_signature: Box::new(encrypted_signature), }, - Alice::CancelTimelockExpired(state3) => AliceState::CancelTimelockExpired { + Alice::CancelTimelockExpired { + monero_wallet_restore_blockheight, + state3, + } => AliceState::CancelTimelockExpired { + monero_wallet_restore_blockheight, state3: Box::new(state3), }, - Alice::BtcCancelled(state) => { + Alice::BtcCancelled { + monero_wallet_restore_blockheight, + state3, + } => { let tx_cancel = TxCancel::new( - &state.tx_lock, - state.cancel_timelock, - state.a.public(), - state.B, + &state3.tx_lock, + state3.cancel_timelock, + state3.a.public(), + state3.B, ); AliceState::BtcCancelled { - state3: Box::new(state), + monero_wallet_restore_blockheight, + state3: Box::new(state3), tx_cancel: Box::new(tx_cancel), } } - Alice::BtcPunishable(state3) => { + Alice::BtcPunishable { + monero_wallet_restore_blockheight, + state3, + } => { let tx_cancel = TxCancel::new( &state3.tx_lock, state3.cancel_timelock, @@ -145,13 +204,17 @@ impl From for AliceState { ); let tx_refund = TxRefund::new(&tx_cancel, &state3.refund_address); AliceState::BtcPunishable { + monero_wallet_restore_blockheight, tx_refund, state3: Box::new(state3), } } Alice::BtcRefunded { - state3, spend_key, .. + monero_wallet_restore_blockheight, + state3, + spend_key, } => AliceState::BtcRefunded { + monero_wallet_restore_blockheight, spend_key, state3: Box::new(state3), }, @@ -170,10 +233,10 @@ impl Display for Alice { match self { Alice::Started { .. } => write!(f, "Started"), Alice::BtcLocked { .. } => f.write_str("Bitcoin locked"), - Alice::XmrLocked(_) => f.write_str("Monero locked"), - Alice::CancelTimelockExpired(_) => f.write_str("Cancel timelock is expired"), - Alice::BtcCancelled(_) => f.write_str("Bitcoin cancel transaction published"), - Alice::BtcPunishable(_) => f.write_str("Bitcoin punishable"), + Alice::XmrLocked { .. } => f.write_str("Monero locked"), + Alice::CancelTimelockExpired { .. } => f.write_str("Cancel timelock is expired"), + Alice::BtcCancelled { .. } => f.write_str("Bitcoin cancel transaction published"), + Alice::BtcPunishable { .. } => f.write_str("Bitcoin punishable"), Alice::BtcRefunded { .. } => f.write_str("Monero refundable"), Alice::Done(end_state) => write!(f, "Done: {}", end_state), Alice::EncSigLearned { .. } => f.write_str("Encrypted signature learned"), diff --git a/swap/src/monero.rs b/swap/src/monero.rs index 469de5d4..ccaffa3b 100644 --- a/swap/src/monero.rs +++ b/swap/src/monero.rs @@ -216,7 +216,7 @@ pub trait CreateWalletForOutput { &self, private_spend_key: PrivateKey, private_view_key: PrivateViewKey, - restore_height: Option, + restore_height: BlockHeight, ) -> Result<()>; } diff --git a/swap/src/monero/wallet.rs b/swap/src/monero/wallet.rs index f3a061c9..1c38de31 100644 --- a/swap/src/monero/wallet.rs +++ b/swap/src/monero/wallet.rs @@ -80,7 +80,7 @@ impl CreateWalletForOutput for Wallet { &self, private_spend_key: PrivateKey, private_view_key: PrivateViewKey, - restore_height: Option, + restore_height: BlockHeight, ) -> Result<()> { let public_spend_key = PublicKey::from_private_key(&private_spend_key); let public_view_key = PublicKey::from_private_key(&private_view_key.into()); @@ -95,7 +95,7 @@ impl CreateWalletForOutput for Wallet { &address.to_string(), &private_spend_key.to_string(), &PrivateKey::from(private_view_key).to_string(), - restore_height, + restore_height.height, ) .await?; diff --git a/swap/src/protocol/alice/state.rs b/swap/src/protocol/alice/state.rs index 35fd8d1f..ade62fe1 100644 --- a/swap/src/protocol/alice/state.rs +++ b/swap/src/protocol/alice/state.rs @@ -15,6 +15,7 @@ use crate::{ }; use anyhow::{anyhow, bail, Context, Result}; use libp2p::PeerId; +use monero_rpc::wallet::BlockHeight; use rand::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; use sigma_fun::ext::dl_secp256k1_ed25519_eq::CrossCurveDLEQProof; @@ -31,27 +32,33 @@ pub enum AliceState { state3: Box, }, XmrLocked { + monero_wallet_restore_blockheight: BlockHeight, state3: Box, }, EncSigLearned { + monero_wallet_restore_blockheight: BlockHeight, encrypted_signature: Box, state3: Box, }, BtcRedeemed, BtcCancelled { + monero_wallet_restore_blockheight: BlockHeight, tx_cancel: Box, state3: Box, }, BtcRefunded { + monero_wallet_restore_blockheight: BlockHeight, spend_key: monero::PrivateKey, state3: Box, }, BtcPunishable { + monero_wallet_restore_blockheight: BlockHeight, tx_refund: TxRefund, state3: Box, }, XmrRefunded, CancelTimelockExpired { + monero_wallet_restore_blockheight: BlockHeight, state3: Box, }, BtcPunished, diff --git a/swap/src/protocol/alice/swap.rs b/swap/src/protocol/alice/swap.rs index e24808fe..0f480dfd 100644 --- a/swap/src/protocol/alice/swap.rs +++ b/swap/src/protocol/alice/swap.rs @@ -10,7 +10,7 @@ use crate::{ database::Database, execution_params::ExecutionParams, monero, - monero::CreateWalletForOutput, + monero::{CreateWalletForOutput, WalletBlockHeight}, monero_ext::ScalarExt, protocol::{ alice, @@ -127,6 +127,10 @@ async fn run_until_internal( bob_peer_id, state3, } => { + // Record the current monero wallet block height so we don't have to scan from + // block 0 for scenarios where we create a refund wallet. + let monero_wallet_restore_blockheight = monero_wallet.block_height().await?; + lock_xmr( bob_peer_id, *state3.clone(), @@ -135,7 +139,10 @@ async fn run_until_internal( ) .await?; - let state = AliceState::XmrLocked { state3 }; + let state = AliceState::XmrLocked { + state3, + monero_wallet_restore_blockheight, + }; let db_state = (&state).into(); db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) @@ -152,7 +159,10 @@ async fn run_until_internal( ) .await } - AliceState::XmrLocked { state3 } => { + AliceState::XmrLocked { + state3, + monero_wallet_restore_blockheight, + } => { let state = match state3.expired_timelocks(bitcoin_wallet.as_ref()).await? { ExpiredTimelocks::None => { let wait_for_enc_sig = @@ -165,14 +175,21 @@ async fn run_until_internal( pin_mut!(cancel_timelock_expires); match select(cancel_timelock_expires, wait_for_enc_sig).await { - Either::Left(_) => AliceState::CancelTimelockExpired { state3 }, + Either::Left(_) => AliceState::CancelTimelockExpired { + state3, + monero_wallet_restore_blockheight, + }, Either::Right((enc_sig, _)) => AliceState::EncSigLearned { state3, encrypted_signature: Box::new(enc_sig?), + monero_wallet_restore_blockheight, }, } } - _ => AliceState::CancelTimelockExpired { state3 }, + _ => AliceState::CancelTimelockExpired { + state3, + monero_wallet_restore_blockheight, + }, }; let db_state = (&state).into(); @@ -193,6 +210,7 @@ async fn run_until_internal( AliceState::EncSigLearned { state3, encrypted_signature, + monero_wallet_restore_blockheight, } => { let state = match state3.expired_timelocks(bitcoin_wallet.as_ref()).await? { ExpiredTimelocks::None => { @@ -228,7 +246,10 @@ async fn run_until_internal( ) .await?; - AliceState::CancelTimelockExpired { state3 } + AliceState::CancelTimelockExpired { + state3, + monero_wallet_restore_blockheight, + } } } } @@ -238,11 +259,17 @@ async fn run_until_internal( .wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref()) .await?; - AliceState::CancelTimelockExpired { state3 } + AliceState::CancelTimelockExpired { + state3, + monero_wallet_restore_blockheight, + } } } } - _ => AliceState::CancelTimelockExpired { state3 }, + _ => AliceState::CancelTimelockExpired { + state3, + monero_wallet_restore_blockheight, + }, }; let db_state = (&state).into(); @@ -260,7 +287,10 @@ async fn run_until_internal( ) .await } - AliceState::CancelTimelockExpired { state3 } => { + AliceState::CancelTimelockExpired { + state3, + monero_wallet_restore_blockheight, + } => { let tx_cancel = publish_cancel_transaction( state3.tx_lock.clone(), state3.a.clone(), @@ -274,6 +304,7 @@ async fn run_until_internal( let state = AliceState::BtcCancelled { state3, tx_cancel: Box::new(tx_cancel), + monero_wallet_restore_blockheight, }; let db_state = (&state).into(); db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) @@ -290,7 +321,11 @@ async fn run_until_internal( ) .await } - AliceState::BtcCancelled { state3, tx_cancel } => { + AliceState::BtcCancelled { + state3, + tx_cancel, + monero_wallet_restore_blockheight, + } => { let tx_cancel_height = bitcoin_wallet .transaction_block_height(tx_cancel.txid()) .await?; @@ -307,7 +342,11 @@ async fn run_until_internal( // TODO(Franck): Review error handling match published_refund_tx { None => { - let state = AliceState::BtcPunishable { tx_refund, state3 }; + let state = AliceState::BtcPunishable { + tx_refund, + state3, + monero_wallet_restore_blockheight, + }; let db_state = (&state).into(); db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) .await?; @@ -333,7 +372,11 @@ async fn run_until_internal( state3.S_b_bitcoin, )?; - let state = AliceState::BtcRefunded { spend_key, state3 }; + let state = AliceState::BtcRefunded { + spend_key, + state3, + monero_wallet_restore_blockheight, + }; let db_state = (&state).into(); db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) .await?; @@ -351,11 +394,19 @@ async fn run_until_internal( } } } - AliceState::BtcRefunded { spend_key, state3 } => { + AliceState::BtcRefunded { + spend_key, + state3, + monero_wallet_restore_blockheight, + } => { let view_key = state3.v; monero_wallet - .create_and_load_wallet_for_output(spend_key, view_key, None) + .create_and_load_wallet_for_output( + spend_key, + view_key, + monero_wallet_restore_blockheight, + ) .await?; let state = AliceState::XmrRefunded; @@ -364,7 +415,11 @@ async fn run_until_internal( .await?; Ok(state) } - AliceState::BtcPunishable { tx_refund, state3 } => { + AliceState::BtcPunishable { + tx_refund, + state3, + monero_wallet_restore_blockheight, + } => { let signed_tx_punish = build_bitcoin_punish_transaction( &state3.tx_lock, state3.cancel_timelock, @@ -395,7 +450,11 @@ async fn run_until_internal( state3.a.clone(), state3.S_b_bitcoin, )?; - let state = AliceState::BtcRefunded { spend_key, state3 }; + let state = AliceState::BtcRefunded { + spend_key, + state3, + monero_wallet_restore_blockheight, + }; let db_state = (&state).into(); db.insert_latest_state(swap_id, database::Swap::Alice(db_state)) .await?; diff --git a/swap/src/protocol/bob/state.rs b/swap/src/protocol/bob/state.rs index 91930cdf..3387eb35 100644 --- a/swap/src/protocol/bob/state.rs +++ b/swap/src/protocol/bob/state.rs @@ -328,7 +328,7 @@ impl State3 { self, xmr_wallet: &W, transfer_proof: TransferProof, - monero_wallet_restore_blockheight: u32, + monero_wallet_restore_blockheight: BlockHeight, ) -> Result> where W: monero::WatchForTransfer, @@ -393,7 +393,9 @@ impl State3 { tx_lock: self.tx_lock.clone(), tx_cancel_sig_a: self.tx_cancel_sig_a.clone(), tx_refund_encsig: self.tx_refund_encsig.clone(), - monero_wallet_restore_blockheight: 0u32, + // For cancel scenarios the monero wallet rescan blockchain height is irrelevant for + // Bob, because Bob's cancel can only lead to refunding on Bitcoin + monero_wallet_restore_blockheight: BlockHeight { height: 0 }, } } @@ -429,7 +431,7 @@ pub struct State4 { tx_lock: bitcoin::TxLock, tx_cancel_sig_a: Signature, tx_refund_encsig: bitcoin::EncryptedSignature, - monero_wallet_restore_blockheight: u32, + monero_wallet_restore_blockheight: BlockHeight, } impl State4 { @@ -589,7 +591,7 @@ pub struct State5 { s_b: monero::Scalar, v: monero::PrivateViewKey, tx_lock: bitcoin::TxLock, - monero_wallet_restore_blockheight: u32, + monero_wallet_restore_blockheight: BlockHeight, } impl State5 { @@ -604,11 +606,7 @@ impl State5 { // NOTE: This actually generates and opens a new wallet, closing the currently // open one. monero_wallet - .create_and_load_wallet_for_output( - s, - self.v, - Some(self.monero_wallet_restore_blockheight), - ) + .create_and_load_wallet_for_output(s, self.v, self.monero_wallet_restore_blockheight) .await?; Ok(()) diff --git a/swap/src/protocol/bob/swap.rs b/swap/src/protocol/bob/swap.rs index ba91dbfa..485e8f74 100644 --- a/swap/src/protocol/bob/swap.rs +++ b/swap/src/protocol/bob/swap.rs @@ -176,7 +176,7 @@ async fn run_until_internal( let xmr_lock_watcher = state.clone().watch_for_lock_xmr( monero_wallet.as_ref(), lock_transfer_proof, - monero_wallet_restore_blockheight.height, + monero_wallet_restore_blockheight, ); let cancel_timelock_expires = state.wait_for_cancel_timelock_to_expire(bitcoin_wallet.as_ref());