diff --git a/mm2src/coins/coin_errors.rs b/mm2src/coins/coin_errors.rs index 479ed10257..363fb66bed 100644 --- a/mm2src/coins/coin_errors.rs +++ b/mm2src/coins/coin_errors.rs @@ -11,7 +11,7 @@ pub enum ValidatePaymentError { InternalError(String), // Problem with deserializing the transaction, or one of the transaction parts is invalid. TxDeserializationError(String), - InvalidInput(String), + InvalidParameter(String), InvalidRpcResponse(String), SPVError(SPVError), UnexpectedPaymentState(String), diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index d0119fff60..2b43c6ac9e 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -68,8 +68,8 @@ use super::{coin_conf, AsyncMutex, BalanceError, BalanceFut, CoinBalance, CoinFu TradePreimageResult, TradePreimageValue, Transaction, TransactionDetails, TransactionEnum, TransactionErr, TransactionFut, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, VerificationError, - VerificationResult, WatcherValidatePaymentInput, WithdrawError, WithdrawFee, WithdrawFut, WithdrawRequest, - WithdrawResult}; + VerificationResult, WatcherOps, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, + WithdrawError, WithdrawFee, WithdrawFut, WithdrawRequest, WithdrawResult}; pub use rlp; @@ -104,6 +104,8 @@ const ETH_DECIMALS: u8 = 18; /// Take into account that the dynamic fee may increase by 3% during the swap. const GAS_PRICE_APPROXIMATION_PERCENT_ON_START_SWAP: u64 = 3; +/// Take into account that the dynamic fee may increase until the locktime is expired +const GAS_PRICE_APPROXIMATION_PERCENT_ON_WATCHER_PREIMAGE: u64 = 3; /// Take into account that the dynamic fee may increase at each of the following stages: /// - it may increase by 2% until a swap is started; /// - it may increase by 3% during the swap. @@ -798,21 +800,6 @@ impl SwapOps for EthCoin { ) } - fn send_taker_spends_maker_payment_preimage(&self, _preimage: &[u8], _secret: &[u8]) -> TransactionFut { - unimplemented!(); - } - - fn create_taker_spends_maker_payment_preimage( - &self, - _maker_payment_tx: &[u8], - _time_lock: u32, - _maker_pub: &[u8], - _secret_hash: &[u8], - _swap_unique_data: &[u8], - ) -> TransactionFut { - unimplemented!(); - } - fn send_taker_spends_maker_payment( &self, maker_payment_tx: &[u8], @@ -981,7 +968,7 @@ impl SwapOps for EthCoin { let swap_contract_address = try_f!(input .swap_contract_address .try_to_address() - .map_to_mm(ValidatePaymentError::InvalidInput)); + .map_to_mm(ValidatePaymentError::InvalidParameter)); self.validate_payment( &input.payment_tx, input.time_lock, @@ -996,7 +983,7 @@ impl SwapOps for EthCoin { let swap_contract_address = try_f!(input .swap_contract_address .try_to_address() - .map_to_mm(ValidatePaymentError::InvalidInput)); + .map_to_mm(ValidatePaymentError::InvalidParameter)); self.validate_payment( &input.payment_tx, input.time_lock, @@ -1007,13 +994,6 @@ impl SwapOps for EthCoin { ) } - fn watcher_validate_taker_payment( - &self, - _input: WatcherValidatePaymentInput, - ) -> Box> + Send> { - unimplemented!(); - } - fn check_if_my_payment_sent( &self, time_lock: u32, @@ -1113,6 +1093,10 @@ impl SwapOps for EthCoin { .await } + fn check_tx_signed_by_pub(&self, _tx: &[u8], _expected_pub: &[u8]) -> Result { + unimplemented!(); + } + fn extract_secret(&self, _secret_hash: &[u8], spend_tx: &[u8]) -> Result, String> { let unverified: UnverifiedTransaction = try_s!(rlp::decode(spend_tx)); let function = try_s!(SWAP_CONTRACT.function("receiverSpend")); @@ -1155,6 +1139,7 @@ impl SwapOps for EthCoin { } } + #[inline] fn derive_htlc_key_pair(&self, _swap_unique_data: &[u8]) -> keys::KeyPair { key_pair_from_secret(self.key_pair.secret()).expect("valid key") } @@ -1167,6 +1152,55 @@ impl SwapOps for EthCoin { } } +#[async_trait] +impl WatcherOps for EthCoin { + fn send_taker_spends_maker_payment_preimage(&self, _preimage: &[u8], _secret: &[u8]) -> TransactionFut { + unimplemented!(); + } + + fn create_taker_spends_maker_payment_preimage( + &self, + _maker_payment_tx: &[u8], + _time_lock: u32, + _maker_pub: &[u8], + _secret_hash: &[u8], + _swap_unique_data: &[u8], + ) -> TransactionFut { + unimplemented!(); + } + + fn create_taker_refunds_payment_preimage( + &self, + _taker_payment_tx: &[u8], + _time_lock: u32, + _maker_pub: &[u8], + _secret_hash: &[u8], + _swap_contract_address: &Option, + _swap_unique_data: &[u8], + ) -> TransactionFut { + unimplemented!(); + } + + fn send_watcher_refunds_taker_payment_preimage(&self, _taker_refunds_payment: &[u8]) -> TransactionFut { + unimplemented!(); + } + + fn watcher_validate_taker_fee(&self, _taker_fee_hash: Vec, _verified_pub: Vec) -> ValidatePaymentFut<()> { + unimplemented!(); + } + + fn watcher_validate_taker_payment(&self, _input: WatcherValidatePaymentInput) -> ValidatePaymentFut<()> { + unimplemented!(); + } + + async fn watcher_search_for_swap_tx_spend( + &self, + _input: WatcherSearchForSwapTxSpendInput<'_>, + ) -> Result, String> { + unimplemented!(); + } +} + #[cfg_attr(test, mockable)] impl MarketCoinOps for EthCoin { fn ticker(&self) -> &str { &self.ticker[..] } @@ -2787,7 +2821,7 @@ impl EthCoin { let tx = try_f!(SignedEthTx::new(unsigned) .map_to_mm(|err| ValidatePaymentError::TxDeserializationError(err.to_string()))); - let sender = try_f!(addr_from_raw_pubkey(sender_pub).map_to_mm(ValidatePaymentError::InvalidInput)); + let sender = try_f!(addr_from_raw_pubkey(sender_pub).map_to_mm(ValidatePaymentError::InvalidParameter)); let expected_value = try_f!(wei_from_big_decimal(&amount, self.decimals)); let selfi = self.clone(); let secret_hash = secret_hash.to_vec(); @@ -2814,7 +2848,7 @@ impl EthCoin { let tx_from_rpc = match tx_from_rpc { Some(t) => t, None => { - return MmError::err(ValidatePaymentError::UnexpectedPaymentState(format!( + return MmError::err(ValidatePaymentError::InvalidRpcResponse(format!( "Didn't find provided tx {:?} on ETH node", tx ))) @@ -3876,5 +3910,8 @@ fn increase_gas_price_by_stage(gas_price: U256, level: &FeeApproxStage) -> U256 FeeApproxStage::TradePreimage => { increase_by_percent_one_gwei(gas_price, GAS_PRICE_APPROXIMATION_PERCENT_ON_TRADE_PREIMAGE) }, + FeeApproxStage::WatcherPreimage => { + increase_by_percent_one_gwei(gas_price, GAS_PRICE_APPROXIMATION_PERCENT_ON_WATCHER_PREIMAGE) + }, } } diff --git a/mm2src/coins/lightning.rs b/mm2src/coins/lightning.rs index 51f95ea4fd..6b500a138b 100644 --- a/mm2src/coins/lightning.rs +++ b/mm2src/coins/lightning.rs @@ -11,7 +11,7 @@ mod ln_storage; mod ln_utils; use super::{lp_coinfind_or_err, DerivationMethod, MmCoinEnum}; -use crate::coin_errors::{MyAddressError, ValidatePaymentError}; +use crate::coin_errors::MyAddressError; use crate::lightning::ln_conf::OurChannelsConfigs; use crate::lightning::ln_errors::{TrustedNodeError, TrustedNodeResult, UpdateChannelError, UpdateChannelResult}; use crate::lightning::ln_events::init_abortable_events; @@ -26,8 +26,8 @@ use crate::{BalanceFut, CoinBalance, CoinFutSpawner, FeeApproxStage, FoundSwapTx SearchForSwapTxSpendInput, SignatureError, SignatureResult, SwapOps, TradeFee, TradePreimageFut, TradePreimageResult, TradePreimageValue, TransactionEnum, TransactionFut, TxMarshalingErr, UnexpectedDerivationMethod, UtxoStandardCoin, ValidateAddressResult, ValidateOtherPubKeyErr, - ValidatePaymentFut, ValidatePaymentInput, VerificationError, VerificationResult, - WatcherValidatePaymentInput, WithdrawError, WithdrawFut, WithdrawRequest}; + ValidatePaymentFut, ValidatePaymentInput, VerificationError, VerificationResult, WatcherOps, + WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WithdrawError, WithdrawFut, WithdrawRequest}; use async_trait::async_trait; use bitcoin::hashes::Hash; use bitcoin_hashes::sha256::Hash as Sha256; @@ -319,17 +319,6 @@ impl SwapOps for LightningCoin { unimplemented!() } - fn create_taker_spends_maker_payment_preimage( - &self, - _maker_payment_tx: &[u8], - _time_lock: u32, - _maker_pub: &[u8], - _secret_hash: &[u8], - _swap_unique_data: &[u8], - ) -> TransactionFut { - unimplemented!(); - } - fn send_taker_spends_maker_payment( &self, _maker_payment_tx: &[u8], @@ -343,10 +332,6 @@ impl SwapOps for LightningCoin { unimplemented!() } - fn send_taker_spends_maker_payment_preimage(&self, _preimage: &[u8], _secret: &[u8]) -> TransactionFut { - unimplemented!(); - } - fn send_taker_refunds_payment( &self, _taker_payment_tx: &[u8], @@ -387,13 +372,6 @@ impl SwapOps for LightningCoin { fn validate_taker_payment(&self, _input: ValidatePaymentInput) -> ValidatePaymentFut<()> { unimplemented!() } - fn watcher_validate_taker_payment( - &self, - _input: WatcherValidatePaymentInput, - ) -> Box> + Send> { - unimplemented!(); - } - fn check_if_my_payment_sent( &self, _time_lock: u32, @@ -421,6 +399,10 @@ impl SwapOps for LightningCoin { unimplemented!() } + fn check_tx_signed_by_pub(&self, _tx: &[u8], _expected_pub: &[u8]) -> Result { + unimplemented!(); + } + fn extract_secret(&self, _secret_hash: &[u8], _spend_tx: &[u8]) -> Result, String> { unimplemented!() } fn negotiate_swap_contract_addr( @@ -435,6 +417,55 @@ impl SwapOps for LightningCoin { fn validate_other_pubkey(&self, _raw_pubkey: &[u8]) -> MmResult<(), ValidateOtherPubKeyErr> { unimplemented!() } } +#[async_trait] +impl WatcherOps for LightningCoin { + fn create_taker_spends_maker_payment_preimage( + &self, + _maker_payment_tx: &[u8], + _time_lock: u32, + _maker_pub: &[u8], + _secret_hash: &[u8], + _swap_unique_data: &[u8], + ) -> TransactionFut { + unimplemented!(); + } + + fn send_taker_spends_maker_payment_preimage(&self, _preimage: &[u8], _secret: &[u8]) -> TransactionFut { + unimplemented!(); + } + + fn create_taker_refunds_payment_preimage( + &self, + _taker_payment_tx: &[u8], + _time_lock: u32, + _maker_pub: &[u8], + _secret_hash: &[u8], + _swap_contract_address: &Option, + _swap_unique_data: &[u8], + ) -> TransactionFut { + unimplemented!(); + } + + fn send_watcher_refunds_taker_payment_preimage(&self, _taker_refunds_payment: &[u8]) -> TransactionFut { + unimplemented!(); + } + + fn watcher_validate_taker_fee(&self, _taker_fee_hash: Vec, _verified_pub: Vec) -> ValidatePaymentFut<()> { + unimplemented!(); + } + + fn watcher_validate_taker_payment(&self, _input: WatcherValidatePaymentInput) -> ValidatePaymentFut<()> { + unimplemented!(); + } + + async fn watcher_search_for_swap_tx_spend( + &self, + _input: WatcherSearchForSwapTxSpendInput<'_>, + ) -> Result, String> { + unimplemented!(); + } +} + impl MarketCoinOps for LightningCoin { fn ticker(&self) -> &str { &self.conf.ticker } diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 0e54ee0c02..3e05efc781 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -462,6 +462,16 @@ pub struct WatcherValidatePaymentInput { pub confirmations: u64, } +pub struct WatcherSearchForSwapTxSpendInput<'a> { + pub time_lock: u32, + pub taker_pub: &'a [u8], + pub maker_pub: &'a [u8], + pub secret_hash: &'a [u8], + pub tx: &'a [u8], + pub search_from_block: u64, + pub swap_contract_address: &'a Option, +} + #[derive(Clone, Debug)] pub struct ValidatePaymentInput { pub payment_tx: Vec, @@ -527,15 +537,6 @@ pub trait SwapOps { swap_unique_data: &[u8], ) -> TransactionFut; - fn create_taker_spends_maker_payment_preimage( - &self, - _maker_payment_tx: &[u8], - _time_lock: u32, - _maker_pub: &[u8], - _secret_hash: &[u8], - _swap_unique_data: &[u8], - ) -> TransactionFut; - #[allow(clippy::too_many_arguments)] fn send_taker_spends_maker_payment( &self, @@ -548,8 +549,6 @@ pub trait SwapOps { swap_unique_data: &[u8], ) -> TransactionFut; - fn send_taker_spends_maker_payment_preimage(&self, preimage: &[u8], secret: &[u8]) -> TransactionFut; - fn send_taker_refunds_payment( &self, taker_payment_tx: &[u8], @@ -584,11 +583,6 @@ pub trait SwapOps { fn validate_taker_payment(&self, input: ValidatePaymentInput) -> ValidatePaymentFut<()>; - fn watcher_validate_taker_payment( - &self, - _input: WatcherValidatePaymentInput, - ) -> Box> + Send>; - #[allow(clippy::too_many_arguments)] fn check_if_my_payment_sent( &self, @@ -613,6 +607,8 @@ pub trait SwapOps { fn extract_secret(&self, secret_hash: &[u8], spend_tx: &[u8]) -> Result, String>; + fn check_tx_signed_by_pub(&self, tx: &[u8], expected_pub: &[u8]) -> Result; + /// Whether the refund transaction can be sent now /// For example: there are no additional conditions for ETH, but for some UTXO coins we should wait for /// locktime < MTP @@ -636,6 +632,41 @@ pub trait SwapOps { fn validate_other_pubkey(&self, raw_pubkey: &[u8]) -> MmResult<(), ValidateOtherPubKeyErr>; } +#[async_trait] +pub trait WatcherOps { + fn send_taker_spends_maker_payment_preimage(&self, preimage: &[u8], secret: &[u8]) -> TransactionFut; + + fn send_watcher_refunds_taker_payment_preimage(&self, _taker_refunds_payment: &[u8]) -> TransactionFut; + + fn create_taker_refunds_payment_preimage( + &self, + _taker_payment_tx: &[u8], + _time_lock: u32, + _maker_pub: &[u8], + _secret_hash: &[u8], + _swap_contract_address: &Option, + _swap_unique_data: &[u8], + ) -> TransactionFut; + + fn create_taker_spends_maker_payment_preimage( + &self, + _maker_payment_tx: &[u8], + _time_lock: u32, + _maker_pub: &[u8], + _secret_hash: &[u8], + _swap_unique_data: &[u8], + ) -> TransactionFut; + + fn watcher_validate_taker_fee(&self, _taker_fee_hash: Vec, _verified_pub: Vec) -> ValidatePaymentFut<()>; + + fn watcher_validate_taker_payment(&self, _input: WatcherValidatePaymentInput) -> ValidatePaymentFut<()>; + + async fn watcher_search_for_swap_tx_spend( + &self, + input: WatcherSearchForSwapTxSpendInput<'_>, + ) -> Result, String>; +} + /// Operations that coins have independently from the MarketMaker. /// That is, things implemented by the coin wallets or public coin services. pub trait MarketCoinOps { @@ -1078,6 +1109,8 @@ pub enum FeeApproxStage { OrderIssue, /// Increase the trade fee largely. TradePreimage, + /// Increase the trade fee very largely + WatcherPreimage, } #[derive(Debug)] @@ -1720,7 +1753,7 @@ impl From for VerificationError { /// NB: Implementations are expected to follow the pImpl idiom, providing cheap reference-counted cloning and garbage collection. #[async_trait] -pub trait MmCoin: SwapOps + MarketCoinOps + Send + Sync + 'static { +pub trait MmCoin: SwapOps + WatcherOps + MarketCoinOps + Send + Sync + 'static { // `MmCoin` is an extension fulcrum for something that doesn't fit the `MarketCoinOps`. Practical examples: // name (might be required for some APIs, CoinMarketCap for instance); // coin statistics that we might want to share with UI; diff --git a/mm2src/coins/qrc20.rs b/mm2src/coins/qrc20.rs index 9eeb84309e..4bb782f9b7 100644 --- a/mm2src/coins/qrc20.rs +++ b/mm2src/coins/qrc20.rs @@ -9,7 +9,7 @@ use crate::utxo::rpc_clients::{ElectrumClient, NativeClient, UnspentInfo, UtxoRp use crate::utxo::tx_cache::{UtxoVerboseCacheOps, UtxoVerboseCacheShared}; use crate::utxo::utxo_builder::{UtxoCoinBuildError, UtxoCoinBuildResult, UtxoCoinBuilderCommonOps, UtxoCoinWithIguanaPrivKeyBuilder, UtxoFieldsWithIguanaPrivKeyBuilder}; -use crate::utxo::utxo_common::{self, big_decimal_from_sat, check_all_inputs_signed_by_pub, UtxoTxBuilder}; +use crate::utxo::utxo_common::{self, big_decimal_from_sat, check_all_utxo_inputs_signed_by_pub, UtxoTxBuilder}; use crate::utxo::{qtum, ActualTxFee, AdditionalTxData, AddrFromStrError, BroadcastTxErr, FeePolicy, GenerateTxError, GetUtxoListOps, HistoryUtxoTx, HistoryUtxoTxMap, MatureUnspentList, RecentlySpentOutPointsGuard, UtxoActivationParams, UtxoAddressFormat, UtxoCoinFields, UtxoCommonOps, UtxoFromLegacyReqErr, @@ -20,8 +20,8 @@ use crate::{BalanceError, BalanceFut, CoinBalance, CoinFutSpawner, FeeApproxStag TradePreimageFut, TradePreimageResult, TradePreimageValue, TransactionDetails, TransactionEnum, TransactionErr, TransactionFut, TransactionType, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateOtherPubKeyErr, ValidatePaymentFut, ValidatePaymentInput, - VerificationResult, WatcherValidatePaymentInput, WithdrawError, WithdrawFee, WithdrawFut, WithdrawRequest, - WithdrawResult}; + VerificationResult, WatcherOps, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, + WithdrawError, WithdrawFee, WithdrawFut, WithdrawRequest, WithdrawResult}; use async_trait::async_trait; use bitcrypto::{dhash160, sha256}; use chain::TransactionOutput; @@ -614,8 +614,8 @@ impl GetUtxoListOps for Qrc20Coin { #[async_trait] #[cfg_attr(test, mockable)] impl UtxoCommonOps for Qrc20Coin { - async fn get_htlc_spend_fee(&self, tx_size: u64) -> UtxoRpcResult { - utxo_common::get_htlc_spend_fee(self, tx_size).await + async fn get_htlc_spend_fee(&self, tx_size: u64, stage: &FeeApproxStage) -> UtxoRpcResult { + utxo_common::get_htlc_spend_fee(self, tx_size, stage).await } fn addresses_from_script(&self, script: &Script) -> Result, String> { @@ -754,6 +754,7 @@ impl SwapOps for Qrc20Coin { Box::new(fut.boxed().compat()) } + #[inline] fn send_taker_payment( &self, _time_lock_duration: u64, @@ -779,6 +780,7 @@ impl SwapOps for Qrc20Coin { Box::new(fut.boxed().compat()) } + #[inline] fn send_maker_spends_taker_payment( &self, taker_payment_tx: &[u8], @@ -802,17 +804,7 @@ impl SwapOps for Qrc20Coin { Box::new(fut.boxed().compat()) } - fn create_taker_spends_maker_payment_preimage( - &self, - _maker_payment_tx: &[u8], - _time_lock: u32, - _maker_pub: &[u8], - _secret_hash: &[u8], - _swap_unique_data: &[u8], - ) -> TransactionFut { - unimplemented!(); - } - + #[inline] fn send_taker_spends_maker_payment( &self, maker_payment_tx: &[u8], @@ -836,10 +828,7 @@ impl SwapOps for Qrc20Coin { Box::new(fut.boxed().compat()) } - fn send_taker_spends_maker_payment_preimage(&self, _preimage: &[u8], _secret: &[u8]) -> TransactionFut { - unimplemented!(); - } - + #[inline] fn send_taker_refunds_payment( &self, taker_payment_tx: &[u8], @@ -861,6 +850,7 @@ impl SwapOps for Qrc20Coin { Box::new(fut.boxed().compat()) } + #[inline] fn send_maker_refunds_payment( &self, maker_payment_tx: &[u8], @@ -882,6 +872,7 @@ impl SwapOps for Qrc20Coin { Box::new(fut.boxed().compat()) } + #[inline] fn validate_fee( &self, fee_tx: &TransactionEnum, @@ -896,7 +887,7 @@ impl SwapOps for Qrc20Coin { _ => panic!("Unexpected TransactionEnum"), }; let fee_tx_hash = fee_tx.hash().reversed().into(); - if !try_fus!(check_all_inputs_signed_by_pub(fee_tx, expected_sender)) { + if !try_fus!(check_all_utxo_inputs_signed_by_pub(fee_tx, expected_sender)) { return Box::new(futures01::future::err(ERRL!("The dex fee was sent from wrong address"))); } let fee_addr = try_fus!(self.contract_address_from_raw_pubkey(fee_addr)); @@ -911,15 +902,16 @@ impl SwapOps for Qrc20Coin { Box::new(fut.boxed().compat()) } + #[inline] fn validate_maker_payment(&self, input: ValidatePaymentInput) -> ValidatePaymentFut<()> { let payment_tx: UtxoTx = try_f!(deserialize(input.payment_tx.as_slice())); let sender = try_f!(self .contract_address_from_raw_pubkey(&input.other_pub) - .map_to_mm(ValidatePaymentError::InvalidInput)); + .map_to_mm(ValidatePaymentError::InvalidParameter)); let swap_contract_address = try_f!(input .swap_contract_address .try_to_address() - .map_to_mm(ValidatePaymentError::InvalidInput)); + .map_to_mm(ValidatePaymentError::InvalidParameter)); let selfi = self.clone(); let fut = async move { @@ -937,15 +929,16 @@ impl SwapOps for Qrc20Coin { Box::new(fut.boxed().compat()) } + #[inline] fn validate_taker_payment(&self, input: ValidatePaymentInput) -> ValidatePaymentFut<()> { let swap_contract_address = try_f!(input .swap_contract_address .try_to_address() - .map_to_mm(ValidatePaymentError::InvalidInput)); + .map_to_mm(ValidatePaymentError::InvalidParameter)); let payment_tx: UtxoTx = try_f!(deserialize(input.payment_tx.as_slice())); let sender = try_f!(self .contract_address_from_raw_pubkey(&input.other_pub) - .map_to_mm(ValidatePaymentError::InvalidInput)); + .map_to_mm(ValidatePaymentError::InvalidParameter)); let selfi = self.clone(); let fut = async move { @@ -963,13 +956,7 @@ impl SwapOps for Qrc20Coin { Box::new(fut.boxed().compat()) } - fn watcher_validate_taker_payment( - &self, - _input: WatcherValidatePaymentInput, - ) -> Box> + Send> { - unimplemented!(); - } - + #[inline] fn check_if_my_payment_sent( &self, time_lock: u32, @@ -992,6 +979,7 @@ impl SwapOps for Qrc20Coin { Box::new(fut.boxed().compat()) } + #[inline] async fn search_for_swap_tx_spend_my( &self, input: SearchForSwapTxSpendInput<'_>, @@ -1012,6 +1000,12 @@ impl SwapOps for Qrc20Coin { .await } + #[inline] + fn check_tx_signed_by_pub(&self, tx: &[u8], expected_pub: &[u8]) -> Result { + utxo_common::check_all_inputs_signed_by_pub(tx, expected_pub) + } + + #[inline] fn extract_secret(&self, secret_hash: &[u8], spend_tx: &[u8]) -> Result, String> { self.extract_secret_impl(secret_hash, spend_tx) } @@ -1042,15 +1036,66 @@ impl SwapOps for Qrc20Coin { } } + #[inline] fn derive_htlc_key_pair(&self, swap_unique_data: &[u8]) -> KeyPair { utxo_common::derive_htlc_key_pair(self.as_ref(), swap_unique_data) } + #[inline] fn validate_other_pubkey(&self, raw_pubkey: &[u8]) -> MmResult<(), ValidateOtherPubKeyErr> { utxo_common::validate_other_pubkey(raw_pubkey) } } +#[async_trait] +impl WatcherOps for Qrc20Coin { + fn create_taker_spends_maker_payment_preimage( + &self, + _maker_payment_tx: &[u8], + _time_lock: u32, + _maker_pub: &[u8], + _secret_hash: &[u8], + _swap_unique_data: &[u8], + ) -> TransactionFut { + unimplemented!(); + } + + fn send_taker_spends_maker_payment_preimage(&self, _preimage: &[u8], _secret: &[u8]) -> TransactionFut { + unimplemented!(); + } + + fn create_taker_refunds_payment_preimage( + &self, + _taker_payment_tx: &[u8], + _time_lock: u32, + _maker_pub: &[u8], + _secret_hash: &[u8], + _swap_contract_address: &Option, + _swap_unique_data: &[u8], + ) -> TransactionFut { + unimplemented!(); + } + + fn send_watcher_refunds_taker_payment_preimage(&self, _taker_refunds_payment: &[u8]) -> TransactionFut { + unimplemented!(); + } + + fn watcher_validate_taker_fee(&self, _taker_fee_hash: Vec, _verified_pub: Vec) -> ValidatePaymentFut<()> { + unimplemented!(); + } + + fn watcher_validate_taker_payment(&self, _input: WatcherValidatePaymentInput) -> ValidatePaymentFut<()> { + unimplemented!(); + } + + async fn watcher_search_for_swap_tx_spend( + &self, + _input: WatcherSearchForSwapTxSpendInput<'_>, + ) -> Result, String> { + unimplemented!(); + } +} + impl MarketCoinOps for Qrc20Coin { fn ticker(&self) -> &str { &self.utxo.conf.ticker } diff --git a/mm2src/coins/qrc20/qrc20_tests.rs b/mm2src/coins/qrc20/qrc20_tests.rs index c890a30bac..1598f52bfc 100644 --- a/mm2src/coins/qrc20/qrc20_tests.rs +++ b/mm2src/coins/qrc20/qrc20_tests.rs @@ -177,11 +177,12 @@ fn test_validate_maker_payment() { .validate_maker_payment(input.clone()) .wait() .unwrap_err() - .to_string(); + .into_inner(); log!("error: {:?}", error); - assert!( - error.contains("Payment tx 0x9e032d4b0090a11dc40fe6c47601499a35d55fbb was sent from wrong address, expected 0x783cf0be521101942da509846ea476e683aad832") - ); + match error { + ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("Payment tx 0x9e032d4b0090a11dc40fe6c47601499a35d55fbb was sent from wrong address, expected 0x783cf0be521101942da509846ea476e683aad832")), + _ => panic!("Expected `WrongPaymentTx` wrong address, found {:?}", error), + } input.other_pub = correct_maker_pub; input.amount = BigDecimal::from_str("0.3").unwrap(); @@ -189,9 +190,17 @@ fn test_validate_maker_payment() { .validate_maker_payment(input.clone()) .wait() .unwrap_err() - .to_string(); + .into_inner(); log!("error: {:?}", error); - assert!(error.contains("Unexpected 'erc20Payment' contract call bytes")); + match error { + ValidatePaymentError::WrongPaymentTx(err) => { + assert!(err.contains("Unexpected 'erc20Payment' contract call bytes")) + }, + _ => panic!( + "Expected `WrongPaymentTx` unexpected contract call bytes, found {:?}", + error + ), + } input.amount = correct_amount; input.secret_hash = vec![2; 20]; @@ -199,15 +208,31 @@ fn test_validate_maker_payment() { .validate_maker_payment(input.clone()) .wait() .unwrap_err() - .to_string(); + .into_inner(); log!("error: {:?}", error); - assert!(error.contains("Payment state is not PAYMENT_STATE_SENT, got 0")); + match error { + ValidatePaymentError::UnexpectedPaymentState(err) => { + assert!(err.contains("Payment state is not PAYMENT_STATE_SENT, got 0")) + }, + _ => panic!( + "Expected `UnexpectedPaymentState` state not PAYMENT_STATE_SENT, found {:?}", + error + ), + } input.secret_hash = vec![1; 20]; input.time_lock = 123; - let error = coin.validate_maker_payment(input).wait().unwrap_err().to_string(); + let error = coin.validate_maker_payment(input).wait().unwrap_err().into_inner(); log!("error: {:?}", error); - assert!(error.contains("Payment state is not PAYMENT_STATE_SENT, got 0")); + match error { + ValidatePaymentError::UnexpectedPaymentState(err) => { + assert!(err.contains("Payment state is not PAYMENT_STATE_SENT, got 0")) + }, + _ => panic!( + "Expected `UnexpectedPaymentState` state not PAYMENT_STATE_SENT, found {:?}", + error + ), + } } #[test] @@ -968,9 +993,14 @@ fn test_validate_maker_payment_malicious() { .wait() .err() .expect("'erc20Payment' was called from another swap contract, expected an error") - .to_string(); + .into_inner(); log!("error: {}", error); - assert!(error.contains("Unexpected amount 1000 in 'Transfer' event, expected 100000000")); + match error { + ValidatePaymentError::TxDeserializationError(err) => { + assert!(err.contains("Unexpected amount 1000 in 'Transfer' event, expected 100000000")) + }, + _ => panic!("Expected `TxDeserializationError` unexpected amount, found {:?}", error), + } } #[test] diff --git a/mm2src/coins/solana.rs b/mm2src/coins/solana.rs index a35c52b6e3..26732bd105 100644 --- a/mm2src/coins/solana.rs +++ b/mm2src/coins/solana.rs @@ -1,13 +1,13 @@ -use super::{CoinBalance, HistorySyncState, MarketCoinOps, MmCoin, SwapOps, TradeFee, TransactionEnum}; -use crate::coin_errors::{MyAddressError, ValidatePaymentError}; +use super::{CoinBalance, HistorySyncState, MarketCoinOps, MmCoin, SwapOps, TradeFee, TransactionEnum, WatcherOps}; +use crate::coin_errors::MyAddressError; use crate::solana::solana_common::{lamports_to_sol, PrepareTransferData, SufficientBalanceError}; use crate::solana::spl::SplTokenInfo; use crate::{BalanceError, BalanceFut, CoinFutSpawner, FeeApproxStage, FoundSwapTxSpend, NegotiateSwapContractAddrErr, RawTransactionFut, RawTransactionRequest, SearchForSwapTxSpendInput, SignatureResult, TradePreimageFut, TradePreimageResult, TradePreimageValue, TransactionDetails, TransactionFut, TransactionType, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateOtherPubKeyErr, - ValidatePaymentFut, ValidatePaymentInput, VerificationResult, WatcherValidatePaymentInput, WithdrawError, - WithdrawFut, WithdrawRequest, WithdrawResult}; + ValidatePaymentFut, ValidatePaymentInput, VerificationResult, WatcherSearchForSwapTxSpendInput, + WatcherValidatePaymentInput, WithdrawError, WithdrawFut, WithdrawRequest, WithdrawResult}; use async_trait::async_trait; use base58::ToBase58; use bincode::{deserialize, serialize}; @@ -506,17 +506,6 @@ impl SwapOps for SolanaCoin { unimplemented!() } - fn create_taker_spends_maker_payment_preimage( - &self, - _maker_payment_tx: &[u8], - _time_lock: u32, - _maker_pub: &[u8], - _secret_hash: &[u8], - _swap_unique_data: &[u8], - ) -> TransactionFut { - unimplemented!(); - } - fn send_taker_spends_maker_payment( &self, maker_payment_tx: &[u8], @@ -530,10 +519,6 @@ impl SwapOps for SolanaCoin { unimplemented!() } - fn send_taker_spends_maker_payment_preimage(&self, preimage: &[u8], secret: &[u8]) -> TransactionFut { - unimplemented!(); - } - fn send_taker_refunds_payment( &self, taker_payment_tx: &[u8], @@ -574,13 +559,6 @@ impl SwapOps for SolanaCoin { fn validate_taker_payment(&self, input: ValidatePaymentInput) -> ValidatePaymentFut<()> { unimplemented!() } - fn watcher_validate_taker_payment( - &self, - _input: WatcherValidatePaymentInput, - ) -> Box> + Send> { - unimplemented!(); - } - fn check_if_my_payment_sent( &self, time_lock: u32, @@ -608,6 +586,10 @@ impl SwapOps for SolanaCoin { unimplemented!() } + fn check_tx_signed_by_pub(&self, tx: &[u8], expected_pub: &[u8]) -> Result { + unimplemented!(); + } + fn extract_secret(&self, secret_hash: &[u8], spend_tx: &[u8]) -> Result, String> { unimplemented!() } fn negotiate_swap_contract_addr( @@ -622,6 +604,56 @@ impl SwapOps for SolanaCoin { fn validate_other_pubkey(&self, _raw_pubkey: &[u8]) -> MmResult<(), ValidateOtherPubKeyErr> { unimplemented!() } } +#[allow(clippy::forget_ref, clippy::forget_copy, clippy::cast_ref_to_mut)] +#[async_trait] +impl WatcherOps for SolanaCoin { + fn create_taker_spends_maker_payment_preimage( + &self, + _maker_payment_tx: &[u8], + _time_lock: u32, + _maker_pub: &[u8], + _secret_hash: &[u8], + _swap_unique_data: &[u8], + ) -> TransactionFut { + unimplemented!(); + } + + fn send_taker_spends_maker_payment_preimage(&self, preimage: &[u8], secret: &[u8]) -> TransactionFut { + unimplemented!(); + } + + fn create_taker_refunds_payment_preimage( + &self, + _taker_payment_tx: &[u8], + _time_lock: u32, + _maker_pub: &[u8], + _secret_hash: &[u8], + _swap_contract_address: &Option, + _swap_unique_data: &[u8], + ) -> TransactionFut { + unimplemented!(); + } + + fn send_watcher_refunds_taker_payment_preimage(&self, _taker_refunds_payment: &[u8]) -> TransactionFut { + unimplemented!(); + } + + fn watcher_validate_taker_fee(&self, _taker_fee_hash: Vec, _verified_pub: Vec) -> ValidatePaymentFut<()> { + unimplemented!(); + } + + fn watcher_validate_taker_payment(&self, _input: WatcherValidatePaymentInput) -> ValidatePaymentFut<()> { + unimplemented!(); + } + + async fn watcher_search_for_swap_tx_spend( + &self, + input: WatcherSearchForSwapTxSpendInput<'_>, + ) -> Result, String> { + unimplemented!(); + } +} + #[allow(clippy::forget_ref, clippy::forget_copy, clippy::cast_ref_to_mut)] #[async_trait] impl MmCoin for SolanaCoin { diff --git a/mm2src/coins/solana/spl.rs b/mm2src/coins/solana/spl.rs index 2d8e3f9c11..c1ec4808d7 100644 --- a/mm2src/coins/solana/spl.rs +++ b/mm2src/coins/solana/spl.rs @@ -1,5 +1,5 @@ -use super::{CoinBalance, HistorySyncState, MarketCoinOps, MmCoin, SwapOps, TradeFee, TransactionEnum}; -use crate::coin_errors::{MyAddressError, ValidatePaymentError}; +use super::{CoinBalance, HistorySyncState, MarketCoinOps, MmCoin, SwapOps, TradeFee, TransactionEnum, WatcherOps}; +use crate::coin_errors::MyAddressError; use crate::solana::solana_common::{ui_amount_to_amount, PrepareTransferData, SufficientBalanceError}; use crate::solana::{solana_common, AccountError, SolanaCommonOps, SolanaFeeDetails}; use crate::{BalanceFut, CoinFutSpawner, FeeApproxStage, FoundSwapTxSpend, NegotiateSwapContractAddrErr, @@ -7,7 +7,8 @@ use crate::{BalanceFut, CoinFutSpawner, FeeApproxStage, FoundSwapTxSpend, Negoti TradePreimageFut, TradePreimageResult, TradePreimageValue, TransactionDetails, TransactionFut, TransactionType, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateOtherPubKeyErr, ValidatePaymentFut, ValidatePaymentInput, VerificationResult, - WatcherValidatePaymentInput, WithdrawError, WithdrawFut, WithdrawRequest, WithdrawResult}; + WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WithdrawError, WithdrawFut, + WithdrawRequest, WithdrawResult}; use async_trait::async_trait; use bincode::serialize; use common::executor::{abortable_queue::AbortableQueue, AbortableSystem}; @@ -336,17 +337,6 @@ impl SwapOps for SplToken { unimplemented!() } - fn create_taker_spends_maker_payment_preimage( - &self, - _maker_payment_tx: &[u8], - _time_lock: u32, - _maker_pub: &[u8], - _secret_hash: &[u8], - _swap_unique_data: &[u8], - ) -> TransactionFut { - unimplemented!(); - } - fn send_taker_spends_maker_payment( &self, maker_payment_tx: &[u8], @@ -360,10 +350,6 @@ impl SwapOps for SplToken { unimplemented!() } - fn send_taker_spends_maker_payment_preimage(&self, preimage: &[u8], secret: &[u8]) -> TransactionFut { - unimplemented!(); - } - fn send_taker_refunds_payment( &self, taker_payment_tx: &[u8], @@ -404,13 +390,6 @@ impl SwapOps for SplToken { fn validate_taker_payment(&self, input: ValidatePaymentInput) -> ValidatePaymentFut<()> { unimplemented!() } - fn watcher_validate_taker_payment( - &self, - _input: WatcherValidatePaymentInput, - ) -> Box> + Send> { - unimplemented!(); - } - fn check_if_my_payment_sent( &self, time_lock: u32, @@ -438,6 +417,10 @@ impl SwapOps for SplToken { unimplemented!() } + fn check_tx_signed_by_pub(&self, tx: &[u8], expected_pub: &[u8]) -> Result { + unimplemented!(); + } + fn extract_secret(&self, secret_hash: &[u8], spend_tx: &[u8]) -> Result, String> { unimplemented!() } fn negotiate_swap_contract_addr( @@ -452,6 +435,56 @@ impl SwapOps for SplToken { fn validate_other_pubkey(&self, _raw_pubkey: &[u8]) -> MmResult<(), ValidateOtherPubKeyErr> { unimplemented!() } } +#[allow(clippy::forget_ref, clippy::forget_copy, clippy::cast_ref_to_mut)] +#[async_trait] +impl WatcherOps for SplToken { + fn send_taker_spends_maker_payment_preimage(&self, preimage: &[u8], secret: &[u8]) -> TransactionFut { + unimplemented!(); + } + + fn create_taker_refunds_payment_preimage( + &self, + _taker_payment_tx: &[u8], + _time_lock: u32, + _maker_pub: &[u8], + _secret_hash: &[u8], + _swap_contract_address: &Option, + _swap_unique_data: &[u8], + ) -> TransactionFut { + unimplemented!(); + } + + fn create_taker_spends_maker_payment_preimage( + &self, + _maker_payment_tx: &[u8], + _time_lock: u32, + _maker_pub: &[u8], + _secret_hash: &[u8], + _swap_unique_data: &[u8], + ) -> TransactionFut { + unimplemented!(); + } + + fn send_watcher_refunds_taker_payment_preimage(&self, _taker_refunds_payment: &[u8]) -> TransactionFut { + unimplemented!(); + } + + fn watcher_validate_taker_fee(&self, _taker_fee_hash: Vec, _verified_pub: Vec) -> ValidatePaymentFut<()> { + unimplemented!(); + } + + fn watcher_validate_taker_payment(&self, _input: WatcherValidatePaymentInput) -> ValidatePaymentFut<()> { + unimplemented!(); + } + + async fn watcher_search_for_swap_tx_spend( + &self, + input: WatcherSearchForSwapTxSpendInput<'_>, + ) -> Result, String> { + unimplemented!(); + } +} + #[allow(clippy::forget_ref, clippy::forget_copy, clippy::cast_ref_to_mut)] #[async_trait] impl MmCoin for SplToken { diff --git a/mm2src/coins/tendermint/tendermint_coin.rs b/mm2src/coins/tendermint/tendermint_coin.rs index 2e896bc58c..6f6f7b9f8a 100644 --- a/mm2src/coins/tendermint/tendermint_coin.rs +++ b/mm2src/coins/tendermint/tendermint_coin.rs @@ -13,9 +13,10 @@ use crate::{big_decimal_from_sat_unsigned, BalanceError, BalanceFut, BigDecimal, SearchForSwapTxSpendInput, SignatureResult, SwapOps, TradeFee, TradePreimageFut, TradePreimageResult, TradePreimageValue, TransactionDetails, TransactionEnum, TransactionErr, TransactionFut, TransactionType, TxFeeDetails, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateOtherPubKeyErr, - ValidatePaymentFut, ValidatePaymentInput, VerificationResult, WatcherValidatePaymentInput, WithdrawError, - WithdrawFut, WithdrawRequest}; + ValidatePaymentFut, ValidatePaymentInput, VerificationResult, WatcherOps, + WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WithdrawError, WithdrawFut, WithdrawRequest}; use async_std::prelude::FutureExt as AsyncStdFutureExt; + use async_trait::async_trait; use bitcrypto::{dhash160, sha256}; use common::executor::Timer; @@ -922,7 +923,7 @@ impl TendermintCoin { let sender_pubkey_hash = dhash160(&input.other_pub); let sender = AccountId::new(&coin.account_prefix, sender_pubkey_hash.as_slice()) - .map_to_mm(|e| ValidatePaymentError::InvalidInput(e.to_string()))?; + .map_to_mm(|e| ValidatePaymentError::InvalidParameter(e.to_string()))?; let amount = sat_from_big_decimal(&input.amount, decimals)?; let amount = vec![Coin { @@ -1543,17 +1544,6 @@ impl SwapOps for TendermintCoin { Box::new(fut.boxed().compat()) } - fn create_taker_spends_maker_payment_preimage( - &self, - _maker_payment_tx: &[u8], - _time_lock: u32, - _maker_pub: &[u8], - _secret_hash: &[u8], - _swap_unique_data: &[u8], - ) -> TransactionFut { - unimplemented!(); - } - fn send_taker_spends_maker_payment( &self, maker_payment_tx: &[u8], @@ -1619,10 +1609,6 @@ impl SwapOps for TendermintCoin { Box::new(fut.boxed().compat()) } - fn send_taker_spends_maker_payment_preimage(&self, preimage: &[u8], secret: &[u8]) -> TransactionFut { - unimplemented!(); - } - fn send_taker_refunds_payment( &self, taker_payment_tx: &[u8], @@ -1679,13 +1665,6 @@ impl SwapOps for TendermintCoin { self.validate_payment_for_denom(input, self.denom.clone(), self.decimals) } - fn watcher_validate_taker_payment( - &self, - _input: WatcherValidatePaymentInput, - ) -> Box> + Send> { - unimplemented!(); - } - fn check_if_my_payment_sent( &self, _time_lock: u32, @@ -1723,6 +1702,10 @@ impl SwapOps for TendermintCoin { Ok(try_s!(hex::decode(htlc.secret))) } + fn check_tx_signed_by_pub(&self, tx: &[u8], expected_pub: &[u8]) -> Result { + unimplemented!(); + } + fn negotiate_swap_contract_addr( &self, other_side_address: Option<&[u8]>, @@ -1741,6 +1724,56 @@ impl SwapOps for TendermintCoin { } } +#[async_trait] +#[allow(unused_variables)] +impl WatcherOps for TendermintCoin { + fn create_taker_spends_maker_payment_preimage( + &self, + _maker_payment_tx: &[u8], + _time_lock: u32, + _maker_pub: &[u8], + _secret_hash: &[u8], + _swap_unique_data: &[u8], + ) -> TransactionFut { + unimplemented!(); + } + + fn send_taker_spends_maker_payment_preimage(&self, preimage: &[u8], secret: &[u8]) -> TransactionFut { + unimplemented!(); + } + + fn create_taker_refunds_payment_preimage( + &self, + _taker_payment_tx: &[u8], + _time_lock: u32, + _maker_pub: &[u8], + _secret_hash: &[u8], + _swap_contract_address: &Option, + _swap_unique_data: &[u8], + ) -> TransactionFut { + unimplemented!(); + } + + fn send_watcher_refunds_taker_payment_preimage(&self, _taker_refunds_payment: &[u8]) -> TransactionFut { + unimplemented!(); + } + + fn watcher_validate_taker_fee(&self, _taker_fee_hash: Vec, _verified_pub: Vec) -> ValidatePaymentFut<()> { + unimplemented!(); + } + + async fn watcher_search_for_swap_tx_spend( + &self, + input: WatcherSearchForSwapTxSpendInput<'_>, + ) -> Result, String> { + unimplemented!(); + } + + fn watcher_validate_taker_payment(&self, _input: WatcherValidatePaymentInput) -> ValidatePaymentFut<()> { + unimplemented!(); + } +} + #[cfg(test)] pub mod tendermint_coin_tests { use super::*; diff --git a/mm2src/coins/tendermint/tendermint_token.rs b/mm2src/coins/tendermint/tendermint_token.rs index 9e3f7adbbe..c9d1ac422a 100644 --- a/mm2src/coins/tendermint/tendermint_token.rs +++ b/mm2src/coins/tendermint/tendermint_token.rs @@ -8,8 +8,8 @@ use crate::{big_decimal_from_sat_unsigned, utxo::sat_from_big_decimal, BalanceFu SignatureResult, SwapOps, TradeFee, TradePreimageFut, TradePreimageResult, TradePreimageValue, TransactionDetails, TransactionEnum, TransactionErr, TransactionFut, TransactionType, TxFeeDetails, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateOtherPubKeyErr, - ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, VerificationResult, - WatcherValidatePaymentInput, WithdrawError, WithdrawFut, WithdrawRequest}; + ValidatePaymentFut, ValidatePaymentInput, VerificationResult, WatcherOps, + WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WithdrawError, WithdrawFut, WithdrawRequest}; use async_trait::async_trait; use bitcrypto::sha256; use common::executor::abortable_queue::AbortableQueue; @@ -157,17 +157,6 @@ impl SwapOps for TendermintToken { ) } - fn create_taker_spends_maker_payment_preimage( - &self, - _maker_payment_tx: &[u8], - _time_lock: u32, - _maker_pub: &[u8], - _secret_hash: &[u8], - _swap_unique_data: &[u8], - ) -> TransactionFut { - todo!() - } - fn send_taker_spends_maker_payment( &self, maker_payment_tx: &[u8], @@ -189,8 +178,6 @@ impl SwapOps for TendermintToken { ) } - fn send_taker_spends_maker_payment_preimage(&self, preimage: &[u8], secret: &[u8]) -> TransactionFut { todo!() } - fn send_taker_refunds_payment( &self, taker_payment_tx: &[u8], @@ -249,13 +236,6 @@ impl SwapOps for TendermintToken { .validate_payment_for_denom(input, self.denom.clone(), self.decimals) } - fn watcher_validate_taker_payment( - &self, - _input: WatcherValidatePaymentInput, - ) -> Box> + Send> { - todo!() - } - fn check_if_my_payment_sent( &self, time_lock: u32, @@ -293,6 +273,10 @@ impl SwapOps for TendermintToken { self.platform_coin.extract_secret(secret_hash, spend_tx) } + fn check_tx_signed_by_pub(&self, tx: &[u8], expected_pub: &[u8]) -> Result { + unimplemented!(); + } + fn negotiate_swap_contract_addr( &self, other_side_address: Option<&[u8]>, @@ -309,6 +293,56 @@ impl SwapOps for TendermintToken { } } +#[async_trait] +#[allow(unused_variables)] +impl WatcherOps for TendermintToken { + fn create_taker_spends_maker_payment_preimage( + &self, + _maker_payment_tx: &[u8], + _time_lock: u32, + _maker_pub: &[u8], + _secret_hash: &[u8], + _swap_unique_data: &[u8], + ) -> TransactionFut { + unimplemented!(); + } + + fn send_taker_spends_maker_payment_preimage(&self, preimage: &[u8], secret: &[u8]) -> TransactionFut { + unimplemented!(); + } + + fn create_taker_refunds_payment_preimage( + &self, + _taker_payment_tx: &[u8], + _time_lock: u32, + _maker_pub: &[u8], + _secret_hash: &[u8], + _swap_contract_address: &Option, + _swap_unique_data: &[u8], + ) -> TransactionFut { + unimplemented!(); + } + + fn send_watcher_refunds_taker_payment_preimage(&self, _taker_refunds_payment: &[u8]) -> TransactionFut { + unimplemented!(); + } + + fn watcher_validate_taker_fee(&self, _taker_fee_hash: Vec, _verified_pub: Vec) -> ValidatePaymentFut<()> { + unimplemented!(); + } + + async fn watcher_search_for_swap_tx_spend( + &self, + input: WatcherSearchForSwapTxSpendInput<'_>, + ) -> Result, String> { + unimplemented!(); + } + + fn watcher_validate_taker_payment(&self, _input: WatcherValidatePaymentInput) -> ValidatePaymentFut<()> { + unimplemented!(); + } +} + impl MarketCoinOps for TendermintToken { fn ticker(&self) -> &str { &self.ticker } diff --git a/mm2src/coins/test_coin.rs b/mm2src/coins/test_coin.rs index fed923810f..6ed51a9827 100644 --- a/mm2src/coins/test_coin.rs +++ b/mm2src/coins/test_coin.rs @@ -1,11 +1,11 @@ use super::{CoinBalance, HistorySyncState, MarketCoinOps, MmCoin, RawTransactionFut, RawTransactionRequest, SwapOps, TradeFee, TransactionEnum, TransactionFut}; -use crate::{coin_errors::{MyAddressError, ValidatePaymentError}, - BalanceFut, CanRefundHtlc, CoinFutSpawner, FeeApproxStage, FoundSwapTxSpend, NegotiateSwapContractAddrErr, - SearchForSwapTxSpendInput, SignatureResult, TradePreimageFut, TradePreimageResult, TradePreimageValue, - TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateOtherPubKeyErr, - ValidatePaymentFut, ValidatePaymentInput, VerificationResult, WatcherValidatePaymentInput, WithdrawFut, - WithdrawRequest}; +use crate::{coin_errors::MyAddressError, BalanceFut, CanRefundHtlc, CoinFutSpawner, FeeApproxStage, FoundSwapTxSpend, + NegotiateSwapContractAddrErr, SearchForSwapTxSpendInput, SignatureResult, TradePreimageFut, + TradePreimageResult, TradePreimageValue, TxMarshalingErr, UnexpectedDerivationMethod, + ValidateAddressResult, ValidateOtherPubKeyErr, ValidatePaymentFut, ValidatePaymentInput, + VerificationResult, WatcherOps, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, + WithdrawFut, WithdrawRequest}; use async_trait::async_trait; use futures01::Future; use keys::KeyPair; @@ -141,17 +141,6 @@ impl SwapOps for TestCoin { unimplemented!() } - fn create_taker_spends_maker_payment_preimage( - &self, - _maker_payment_tx: &[u8], - _time_lock: u32, - _maker_pub: &[u8], - _secret_hash: &[u8], - _swap_unique_data: &[u8], - ) -> TransactionFut { - unimplemented!(); - } - fn send_taker_spends_maker_payment( &self, maker_payment_tx: &[u8], @@ -165,10 +154,6 @@ impl SwapOps for TestCoin { unimplemented!() } - fn send_taker_spends_maker_payment_preimage(&self, preimage: &[u8], secret: &[u8]) -> TransactionFut { - unimplemented!(); - } - fn send_taker_refunds_payment( &self, taker_payment_tx: &[u8], @@ -209,13 +194,6 @@ impl SwapOps for TestCoin { fn validate_taker_payment(&self, _input: ValidatePaymentInput) -> ValidatePaymentFut<()> { unimplemented!() } - fn watcher_validate_taker_payment( - &self, - _input: WatcherValidatePaymentInput, - ) -> Box> + Send> { - unimplemented!(); - } - fn check_if_my_payment_sent( &self, time_lock: u32, @@ -243,6 +221,10 @@ impl SwapOps for TestCoin { unimplemented!() } + fn check_tx_signed_by_pub(&self, tx: &[u8], expected_pub: &[u8]) -> Result { + unimplemented!(); + } + fn extract_secret(&self, secret_hash: &[u8], spend_tx: &[u8]) -> Result, String> { unimplemented!() } fn negotiate_swap_contract_addr( @@ -261,6 +243,57 @@ impl SwapOps for TestCoin { fn validate_other_pubkey(&self, raw_pubkey: &[u8]) -> MmResult<(), ValidateOtherPubKeyErr> { unimplemented!() } } +#[async_trait] +#[mockable] +#[allow(clippy::forget_ref, clippy::forget_copy, clippy::cast_ref_to_mut)] +impl WatcherOps for TestCoin { + fn create_taker_spends_maker_payment_preimage( + &self, + _maker_payment_tx: &[u8], + _time_lock: u32, + _maker_pub: &[u8], + _secret_hash: &[u8], + _swap_unique_data: &[u8], + ) -> TransactionFut { + unimplemented!(); + } + + fn send_taker_spends_maker_payment_preimage(&self, preimage: &[u8], secret: &[u8]) -> TransactionFut { + unimplemented!(); + } + + fn create_taker_refunds_payment_preimage( + &self, + _taker_payment_tx: &[u8], + _time_lock: u32, + _maker_pub: &[u8], + _secret_hash: &[u8], + _swap_contract_address: &Option, + _swap_unique_data: &[u8], + ) -> TransactionFut { + unimplemented!(); + } + + fn send_watcher_refunds_taker_payment_preimage(&self, _taker_refunds_payment: &[u8]) -> TransactionFut { + unimplemented!(); + } + + fn watcher_validate_taker_fee(&self, _taker_fee_hash: Vec, _verified_pub: Vec) -> ValidatePaymentFut<()> { + unimplemented!(); + } + + async fn watcher_search_for_swap_tx_spend( + &self, + input: WatcherSearchForSwapTxSpendInput<'_>, + ) -> Result, String> { + unimplemented!(); + } + + fn watcher_validate_taker_payment(&self, _input: WatcherValidatePaymentInput) -> ValidatePaymentFut<()> { + unimplemented!(); + } +} + #[async_trait] #[mockable] #[allow(clippy::forget_ref, clippy::forget_copy, clippy::cast_ref_to_mut)] diff --git a/mm2src/coins/utxo.rs b/mm2src/coins/utxo.rs index d50685a568..82856d8316 100644 --- a/mm2src/coins/utxo.rs +++ b/mm2src/coins/utxo.rs @@ -22,7 +22,7 @@ // pub mod bch; -mod bchd_grpc; +pub(crate) mod bchd_grpc; #[allow(clippy::all)] #[rustfmt::skip] #[path = "utxo/pb.rs"] @@ -935,7 +935,7 @@ impl MatureUnspentList { pub trait UtxoCommonOps: AsRef + UtxoTxGenerationOps + UtxoTxBroadcastOps + Clone + Send + Sync + 'static { - async fn get_htlc_spend_fee(&self, tx_size: u64) -> UtxoRpcResult; + async fn get_htlc_spend_fee(&self, tx_size: u64, stage: &FeeApproxStage) -> UtxoRpcResult; fn addresses_from_script(&self, script: &Script) -> Result, String>; diff --git a/mm2src/coins/utxo/bch.rs b/mm2src/coins/utxo/bch.rs index b70270c35d..3d4d40c0c2 100644 --- a/mm2src/coins/utxo/bch.rs +++ b/mm2src/coins/utxo/bch.rs @@ -1,5 +1,5 @@ use super::*; -use crate::coin_errors::{MyAddressError, ValidatePaymentError}; +use crate::coin_errors::MyAddressError; use crate::my_tx_history_v2::{CoinWithTxHistoryV2, MyTxHistoryErrorV2, MyTxHistoryTarget, TxDetailsBuilder, TxHistoryStorage}; use crate::tx_history_storage::{GetTxHistoryFilters, WalletId}; @@ -13,7 +13,8 @@ use crate::{BlockHeightAndTime, CanRefundHtlc, CoinBalance, CoinProtocol, CoinWi NegotiateSwapContractAddrErr, PrivKeyBuildPolicy, RawTransactionFut, RawTransactionRequest, SearchForSwapTxSpendInput, SignatureResult, SwapOps, TradePreimageValue, TransactionFut, TransactionType, TxFeeDetails, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateOtherPubKeyErr, - ValidatePaymentFut, ValidatePaymentInput, VerificationResult, WatcherValidatePaymentInput, WithdrawFut}; + ValidatePaymentFut, ValidatePaymentInput, VerificationResult, WatcherOps, + WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WithdrawFut}; use common::log::warn; use derive_more::Display; use futures::{FutureExt, TryFutureExt}; @@ -712,8 +713,8 @@ impl GetUtxoListOps for BchCoin { #[async_trait] #[cfg_attr(test, mockable)] impl UtxoCommonOps for BchCoin { - async fn get_htlc_spend_fee(&self, tx_size: u64) -> UtxoRpcResult { - utxo_common::get_htlc_spend_fee(self, tx_size).await + async fn get_htlc_spend_fee(&self, tx_size: u64, stage: &FeeApproxStage) -> UtxoRpcResult { + utxo_common::get_htlc_spend_fee(self, tx_size, stage).await } fn addresses_from_script(&self, script: &Script) -> Result, String> { @@ -811,10 +812,12 @@ impl UtxoCommonOps for BchCoin { #[async_trait] impl SwapOps for BchCoin { + #[inline] fn send_taker_fee(&self, fee_addr: &[u8], amount: BigDecimal, _uuid: &[u8]) -> TransactionFut { utxo_common::send_taker_fee(self.clone(), fee_addr, amount) } + #[inline] fn send_maker_payment( &self, _time_lock_duration: u64, @@ -835,6 +838,7 @@ impl SwapOps for BchCoin { ) } + #[inline] fn send_taker_payment( &self, _time_lock_duration: u64, @@ -855,6 +859,7 @@ impl SwapOps for BchCoin { ) } + #[inline] fn send_maker_spends_taker_payment( &self, taker_payment_tx: &[u8], @@ -876,24 +881,7 @@ impl SwapOps for BchCoin { ) } - fn create_taker_spends_maker_payment_preimage( - &self, - maker_payment_tx: &[u8], - time_lock: u32, - maker_pub: &[u8], - secret_hash: &[u8], - swap_unique_data: &[u8], - ) -> TransactionFut { - utxo_common::create_taker_spends_maker_payment_preimage( - self.clone(), - maker_payment_tx, - time_lock, - maker_pub, - secret_hash, - swap_unique_data, - ) - } - + #[inline] fn send_taker_spends_maker_payment( &self, maker_payment_tx: &[u8], @@ -915,10 +903,7 @@ impl SwapOps for BchCoin { ) } - fn send_taker_spends_maker_payment_preimage(&self, preimage: &[u8], secret: &[u8]) -> TransactionFut { - utxo_common::send_taker_spends_maker_payment_preimage(self.clone(), preimage, secret) - } - + #[inline] fn send_taker_refunds_payment( &self, taker_tx: &[u8], @@ -938,6 +923,7 @@ impl SwapOps for BchCoin { ) } + #[inline] fn send_maker_refunds_payment( &self, maker_tx: &[u8], @@ -981,21 +967,17 @@ impl SwapOps for BchCoin { ) } + #[inline] fn validate_maker_payment(&self, input: ValidatePaymentInput) -> ValidatePaymentFut<()> { utxo_common::validate_maker_payment(self, input) } + #[inline] fn validate_taker_payment(&self, input: ValidatePaymentInput) -> ValidatePaymentFut<()> { utxo_common::validate_taker_payment(self, input) } - fn watcher_validate_taker_payment( - &self, - input: WatcherValidatePaymentInput, - ) -> Box> + Send> { - utxo_common::watcher_validate_taker_payment(self, input) - } - + #[inline] fn check_if_my_payment_sent( &self, time_lock: u32, @@ -1009,6 +991,7 @@ impl SwapOps for BchCoin { utxo_common::check_if_my_payment_sent(self.clone(), time_lock, other_pub, secret_hash, swap_unique_data) } + #[inline] async fn search_for_swap_tx_spend_my( &self, input: SearchForSwapTxSpendInput<'_>, @@ -1016,6 +999,7 @@ impl SwapOps for BchCoin { utxo_common::search_for_swap_tx_spend_my(self, input, utxo_common::DEFAULT_SWAP_VOUT).await } + #[inline] async fn search_for_swap_tx_spend_other( &self, input: SearchForSwapTxSpendInput<'_>, @@ -1023,10 +1007,17 @@ impl SwapOps for BchCoin { utxo_common::search_for_swap_tx_spend_other(self, input, utxo_common::DEFAULT_SWAP_VOUT).await } + #[inline] + fn check_tx_signed_by_pub(&self, tx: &[u8], expected_pub: &[u8]) -> Result { + utxo_common::check_all_inputs_signed_by_pub(tx, expected_pub) + } + + #[inline] fn extract_secret(&self, secret_hash: &[u8], spend_tx: &[u8]) -> Result, String> { utxo_common::extract_secret(secret_hash, spend_tx) } + #[inline] fn can_refund_htlc(&self, locktime: u64) -> Box + Send + '_> { Box::new( utxo_common::can_refund_htlc(self, locktime) @@ -1036,6 +1027,7 @@ impl SwapOps for BchCoin { ) } + #[inline] fn negotiate_swap_contract_addr( &self, _other_side_address: Option<&[u8]>, @@ -1043,10 +1035,12 @@ impl SwapOps for BchCoin { Ok(None) } + #[inline] fn derive_htlc_key_pair(&self, swap_unique_data: &[u8]) -> KeyPair { utxo_common::derive_htlc_key_pair(self.as_ref(), swap_unique_data) } + #[inline] fn validate_other_pubkey(&self, raw_pubkey: &[u8]) -> MmResult<(), ValidateOtherPubKeyErr> { utxo_common::validate_other_pubkey(raw_pubkey) } @@ -1056,6 +1050,76 @@ fn total_unspent_value<'a>(unspents: impl IntoIterator) unspents.into_iter().fold(0, |cur, unspent| cur + unspent.value) } +#[async_trait] +impl WatcherOps for BchCoin { + #[inline] + fn create_taker_spends_maker_payment_preimage( + &self, + maker_payment_tx: &[u8], + time_lock: u32, + maker_pub: &[u8], + secret_hash: &[u8], + swap_unique_data: &[u8], + ) -> TransactionFut { + utxo_common::create_taker_spends_maker_payment_preimage( + self.clone(), + maker_payment_tx, + time_lock, + maker_pub, + secret_hash, + swap_unique_data, + ) + } + + #[inline] + fn send_taker_spends_maker_payment_preimage(&self, preimage: &[u8], secret: &[u8]) -> TransactionFut { + utxo_common::send_taker_spends_maker_payment_preimage(self.clone(), preimage, secret) + } + + #[inline] + fn create_taker_refunds_payment_preimage( + &self, + taker_payment_tx: &[u8], + time_lock: u32, + maker_pub: &[u8], + secret_hash: &[u8], + _swap_contract_address: &Option, + swap_unique_data: &[u8], + ) -> TransactionFut { + utxo_common::create_taker_refunds_payment_preimage( + self.clone(), + taker_payment_tx, + time_lock, + maker_pub, + secret_hash, + swap_unique_data, + ) + } + + #[inline] + fn send_watcher_refunds_taker_payment_preimage(&self, taker_refunds_payment: &[u8]) -> TransactionFut { + utxo_common::send_watcher_refunds_taker_payment_preimage(self.clone(), taker_refunds_payment) + } + + #[inline] + fn watcher_validate_taker_fee(&self, taker_fee_hash: Vec, verified_pub: Vec) -> ValidatePaymentFut<()> { + utxo_common::watcher_validate_taker_fee(self.clone(), taker_fee_hash, verified_pub) + } + + #[inline] + fn watcher_validate_taker_payment(&self, input: WatcherValidatePaymentInput) -> ValidatePaymentFut<()> { + utxo_common::watcher_validate_taker_payment(self, input) + } + + #[inline] + async fn watcher_search_for_swap_tx_spend( + &self, + input: WatcherSearchForSwapTxSpendInput<'_>, + ) -> Result, String> { + utxo_common::watcher_search_for_swap_tx_spend(self, input, utxo_common::DEFAULT_SWAP_VOUT).await + } +} + impl MarketCoinOps for BchCoin { fn ticker(&self) -> &str { &self.utxo_arc.conf.ticker } diff --git a/mm2src/coins/utxo/qtum.rs b/mm2src/coins/utxo/qtum.rs index d6ee23c017..acdaf5a033 100644 --- a/mm2src/coins/utxo/qtum.rs +++ b/mm2src/coins/utxo/qtum.rs @@ -1,7 +1,7 @@ use super::*; use crate::coin_balance::{self, EnableCoinBalanceError, EnabledCoinBalanceParams, HDAccountBalance, HDAddressBalance, HDWalletBalance, HDWalletBalanceOps}; -use crate::coin_errors::{MyAddressError, ValidatePaymentError}; +use crate::coin_errors::MyAddressError; use crate::hd_pubkey::{ExtractExtendedPubkey, HDExtractPubkeyError, HDXPubExtractor}; use crate::hd_wallet::{AccountUpdatingError, AddressDerivingResult, HDAccountMut, NewAccountCreatingError}; use crate::hd_wallet_storage::HDWalletCoinWithStorageOps; @@ -26,7 +26,8 @@ use crate::{eth, CanRefundHtlc, CoinBalance, CoinWithDerivationMethod, Delegatio GetWithdrawSenderAddress, NegotiateSwapContractAddrErr, PrivKeyBuildPolicy, SearchForSwapTxSpendInput, SignatureResult, StakingInfosFut, SwapOps, TradePreimageValue, TransactionFut, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateOtherPubKeyErr, ValidatePaymentFut, - ValidatePaymentInput, VerificationResult, WatcherValidatePaymentInput, WithdrawFut, WithdrawSenderAddress}; + ValidatePaymentInput, VerificationResult, WatcherOps, WatcherSearchForSwapTxSpendInput, + WatcherValidatePaymentInput, WithdrawFut, WithdrawSenderAddress}; use crypto::Bip44Chain; use ethereum_types::H160; use futures::{FutureExt, TryFutureExt}; @@ -379,8 +380,8 @@ impl GetUtxoMapOps for QtumCoin { #[async_trait] #[cfg_attr(test, mockable)] impl UtxoCommonOps for QtumCoin { - async fn get_htlc_spend_fee(&self, tx_size: u64) -> UtxoRpcResult { - utxo_common::get_htlc_spend_fee(self, tx_size).await + async fn get_htlc_spend_fee(&self, tx_size: u64, stage: &FeeApproxStage) -> UtxoRpcResult { + utxo_common::get_htlc_spend_fee(self, tx_size, stage).await } fn addresses_from_script(&self, script: &Script) -> Result, String> { @@ -504,10 +505,12 @@ impl UtxoStandardOps for QtumCoin { #[async_trait] impl SwapOps for QtumCoin { + #[inline] fn send_taker_fee(&self, fee_addr: &[u8], amount: BigDecimal, _uuid: &[u8]) -> TransactionFut { utxo_common::send_taker_fee(self.clone(), fee_addr, amount) } + #[inline] fn send_maker_payment( &self, _time_lock_duration: u64, @@ -528,6 +531,7 @@ impl SwapOps for QtumCoin { ) } + #[inline] fn send_taker_payment( &self, _time_lock_duration: u64, @@ -548,6 +552,7 @@ impl SwapOps for QtumCoin { ) } + #[inline] fn send_maker_spends_taker_payment( &self, taker_tx: &[u8], @@ -569,24 +574,7 @@ impl SwapOps for QtumCoin { ) } - fn create_taker_spends_maker_payment_preimage( - &self, - maker_payment_tx: &[u8], - time_lock: u32, - maker_pub: &[u8], - secret_hash: &[u8], - swap_unique_data: &[u8], - ) -> TransactionFut { - utxo_common::create_taker_spends_maker_payment_preimage( - self.clone(), - maker_payment_tx, - time_lock, - maker_pub, - secret_hash, - swap_unique_data, - ) - } - + #[inline] fn send_taker_spends_maker_payment( &self, maker_tx: &[u8], @@ -608,10 +596,7 @@ impl SwapOps for QtumCoin { ) } - fn send_taker_spends_maker_payment_preimage(&self, preimage: &[u8], secret: &[u8]) -> TransactionFut { - utxo_common::send_taker_spends_maker_payment_preimage(self.clone(), preimage, secret) - } - + #[inline] fn send_taker_refunds_payment( &self, taker_tx: &[u8], @@ -631,6 +616,7 @@ impl SwapOps for QtumCoin { ) } + #[inline] fn send_maker_refunds_payment( &self, maker_tx: &[u8], @@ -674,21 +660,17 @@ impl SwapOps for QtumCoin { ) } + #[inline] fn validate_maker_payment(&self, input: ValidatePaymentInput) -> ValidatePaymentFut<()> { utxo_common::validate_maker_payment(self, input) } + #[inline] fn validate_taker_payment(&self, input: ValidatePaymentInput) -> ValidatePaymentFut<()> { utxo_common::validate_taker_payment(self, input) } - fn watcher_validate_taker_payment( - &self, - input: WatcherValidatePaymentInput, - ) -> Box> + Send> { - utxo_common::watcher_validate_taker_payment(self, input) - } - + #[inline] fn check_if_my_payment_sent( &self, time_lock: u32, @@ -702,6 +684,7 @@ impl SwapOps for QtumCoin { utxo_common::check_if_my_payment_sent(self.clone(), time_lock, other_pub, secret_hash, swap_unique_data) } + #[inline] async fn search_for_swap_tx_spend_my( &self, input: SearchForSwapTxSpendInput<'_>, @@ -709,6 +692,7 @@ impl SwapOps for QtumCoin { utxo_common::search_for_swap_tx_spend_my(self, input, utxo_common::DEFAULT_SWAP_VOUT).await } + #[inline] async fn search_for_swap_tx_spend_other( &self, input: SearchForSwapTxSpendInput<'_>, @@ -716,10 +700,17 @@ impl SwapOps for QtumCoin { utxo_common::search_for_swap_tx_spend_other(self, input, utxo_common::DEFAULT_SWAP_VOUT).await } + #[inline] + fn check_tx_signed_by_pub(&self, tx: &[u8], expected_pub: &[u8]) -> Result { + utxo_common::check_all_inputs_signed_by_pub(tx, expected_pub) + } + + #[inline] fn extract_secret(&self, secret_hash: &[u8], spend_tx: &[u8]) -> Result, String> { utxo_common::extract_secret(secret_hash, spend_tx) } + #[inline] fn can_refund_htlc(&self, locktime: u64) -> Box + Send + '_> { Box::new( utxo_common::can_refund_htlc(self, locktime) @@ -729,6 +720,7 @@ impl SwapOps for QtumCoin { ) } + #[inline] fn negotiate_swap_contract_addr( &self, _other_side_address: Option<&[u8]>, @@ -736,15 +728,87 @@ impl SwapOps for QtumCoin { Ok(None) } + #[inline] fn derive_htlc_key_pair(&self, swap_unique_data: &[u8]) -> KeyPair { utxo_common::derive_htlc_key_pair(self.as_ref(), swap_unique_data) } + #[inline] fn validate_other_pubkey(&self, raw_pubkey: &[u8]) -> MmResult<(), ValidateOtherPubKeyErr> { utxo_common::validate_other_pubkey(raw_pubkey) } } +#[async_trait] +impl WatcherOps for QtumCoin { + #[inline] + fn create_taker_spends_maker_payment_preimage( + &self, + maker_payment_tx: &[u8], + time_lock: u32, + maker_pub: &[u8], + secret_hash: &[u8], + swap_unique_data: &[u8], + ) -> TransactionFut { + utxo_common::create_taker_spends_maker_payment_preimage( + self.clone(), + maker_payment_tx, + time_lock, + maker_pub, + secret_hash, + swap_unique_data, + ) + } + + #[inline] + fn send_taker_spends_maker_payment_preimage(&self, preimage: &[u8], secret: &[u8]) -> TransactionFut { + utxo_common::send_taker_spends_maker_payment_preimage(self.clone(), preimage, secret) + } + + #[inline] + fn create_taker_refunds_payment_preimage( + &self, + taker_payment_tx: &[u8], + time_lock: u32, + maker_pub: &[u8], + secret_hash: &[u8], + _swap_contract_address: &Option, + swap_unique_data: &[u8], + ) -> TransactionFut { + utxo_common::create_taker_refunds_payment_preimage( + self.clone(), + taker_payment_tx, + time_lock, + maker_pub, + secret_hash, + swap_unique_data, + ) + } + + #[inline] + fn send_watcher_refunds_taker_payment_preimage(&self, taker_refunds_payment: &[u8]) -> TransactionFut { + utxo_common::send_watcher_refunds_taker_payment_preimage(self.clone(), taker_refunds_payment) + } + + #[inline] + fn watcher_validate_taker_fee(&self, taker_fee_hash: Vec, verified_pub: Vec) -> ValidatePaymentFut<()> { + utxo_common::watcher_validate_taker_fee(self.clone(), taker_fee_hash, verified_pub) + } + + #[inline] + fn watcher_validate_taker_payment(&self, input: WatcherValidatePaymentInput) -> ValidatePaymentFut<()> { + utxo_common::watcher_validate_taker_payment(self, input) + } + + #[inline] + async fn watcher_search_for_swap_tx_spend( + &self, + input: WatcherSearchForSwapTxSpendInput<'_>, + ) -> Result, String> { + utxo_common::watcher_search_for_swap_tx_spend(self, input, utxo_common::DEFAULT_SWAP_VOUT).await + } +} + impl MarketCoinOps for QtumCoin { fn ticker(&self) -> &str { &self.utxo_arc.conf.ticker } diff --git a/mm2src/coins/utxo/slp.rs b/mm2src/coins/utxo/slp.rs index 75e27302b0..516cb158eb 100644 --- a/mm2src/coins/utxo/slp.rs +++ b/mm2src/coins/utxo/slp.rs @@ -19,8 +19,8 @@ use crate::{BalanceFut, CoinBalance, CoinFutSpawner, FeeApproxStage, FoundSwapTx TradePreimageError, TradePreimageFut, TradePreimageResult, TradePreimageValue, TransactionDetails, TransactionEnum, TransactionErr, TransactionFut, TxFeeDetails, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateOtherPubKeyErr, ValidatePaymentInput, - VerificationError, VerificationResult, WatcherValidatePaymentInput, WithdrawError, WithdrawFee, - WithdrawFut, WithdrawRequest}; + VerificationError, VerificationResult, WatcherOps, WatcherSearchForSwapTxSpendInput, + WatcherValidatePaymentInput, WithdrawError, WithdrawFee, WithdrawFut, WithdrawRequest}; use async_trait::async_trait; use bitcrypto::dhash160; use chain::constants::SEQUENCE_FINAL; @@ -469,7 +469,7 @@ impl SlpToken { let htlc_keypair = self.derive_htlc_key_pair(&input.unique_swap_data); let first_pub = &Public::from_slice(&input.other_pub) - .map_to_mm(|err| ValidatePaymentError::InvalidInput(err.to_string()))?; + .map_to_mm(|err| ValidatePaymentError::InvalidParameter(err.to_string()))?; let validate_fut = utxo_common::validate_payment( self.platform_coin.clone(), tx, @@ -1276,17 +1276,6 @@ impl SwapOps for SlpToken { Box::new(fut.boxed().compat()) } - fn create_taker_spends_maker_payment_preimage( - &self, - _maker_payment_tx: &[u8], - _time_lock: u32, - _maker_pub: &[u8], - _secret_hash: &[u8], - _swap_unique_data: &[u8], - ) -> TransactionFut { - unimplemented!(); - } - fn send_taker_spends_maker_payment( &self, maker_payment_tx: &[u8], @@ -1314,10 +1303,6 @@ impl SwapOps for SlpToken { Box::new(fut.boxed().compat()) } - fn send_taker_spends_maker_payment_preimage(&self, _preimage: &[u8], _secret: &[u8]) -> TransactionFut { - unimplemented!(); - } - fn send_taker_refunds_payment( &self, taker_payment_tx: &[u8], @@ -1414,13 +1399,7 @@ impl SwapOps for SlpToken { Box::new(fut.boxed().compat()) } - fn watcher_validate_taker_payment( - &self, - _input: WatcherValidatePaymentInput, - ) -> Box> + Send> { - unimplemented!(); - } - + #[inline] fn check_if_my_payment_sent( &self, time_lock: u32, @@ -1440,6 +1419,7 @@ impl SwapOps for SlpToken { ) } + #[inline] async fn search_for_swap_tx_spend_my( &self, input: SearchForSwapTxSpendInput<'_>, @@ -1447,6 +1427,7 @@ impl SwapOps for SlpToken { utxo_common::search_for_swap_tx_spend_my(&self.platform_coin, input, SLP_SWAP_VOUT).await } + #[inline] async fn search_for_swap_tx_spend_other( &self, input: SearchForSwapTxSpendInput<'_>, @@ -1454,10 +1435,16 @@ impl SwapOps for SlpToken { utxo_common::search_for_swap_tx_spend_other(&self.platform_coin, input, SLP_SWAP_VOUT).await } + fn check_tx_signed_by_pub(&self, _tx: &[u8], _expected_pub: &[u8]) -> Result { + unimplemented!(); + } + + #[inline] fn extract_secret(&self, secret_hash: &[u8], spend_tx: &[u8]) -> Result, String> { utxo_common::extract_secret(secret_hash, spend_tx) } + #[inline] fn negotiate_swap_contract_addr( &self, _other_side_address: Option<&[u8]>, @@ -1465,15 +1452,67 @@ impl SwapOps for SlpToken { Ok(None) } + #[inline] fn derive_htlc_key_pair(&self, swap_unique_data: &[u8]) -> KeyPair { utxo_common::derive_htlc_key_pair(self.platform_coin.as_ref(), swap_unique_data) } + #[inline] fn validate_other_pubkey(&self, raw_pubkey: &[u8]) -> MmResult<(), ValidateOtherPubKeyErr> { utxo_common::validate_other_pubkey(raw_pubkey) } } +#[async_trait] +impl WatcherOps for SlpToken { + fn create_taker_spends_maker_payment_preimage( + &self, + _maker_payment_tx: &[u8], + _time_lock: u32, + _maker_pub: &[u8], + _secret_hash: &[u8], + _swap_unique_data: &[u8], + ) -> TransactionFut { + unimplemented!(); + } + + fn send_taker_spends_maker_payment_preimage(&self, _preimage: &[u8], _secret: &[u8]) -> TransactionFut { + unimplemented!(); + } + + fn create_taker_refunds_payment_preimage( + &self, + _taker_payment_tx: &[u8], + _time_lock: u32, + _maker_pub: &[u8], + _secret_hash: &[u8], + _swap_contract_address: &Option, + _swap_unique_data: &[u8], + ) -> TransactionFut { + unimplemented!(); + } + + fn send_watcher_refunds_taker_payment_preimage(&self, _taker_refunds_payment: &[u8]) -> TransactionFut { + unimplemented!(); + } + + fn watcher_validate_taker_fee(&self, _taker_fee_hash: Vec, _verified_pub: Vec) -> ValidatePaymentFut<()> { + unimplemented!(); + } + + fn watcher_validate_taker_payment(&self, _input: WatcherValidatePaymentInput) -> ValidatePaymentFut<()> { + unimplemented!(); + } + + #[inline] + async fn watcher_search_for_swap_tx_spend( + &self, + input: WatcherSearchForSwapTxSpendInput<'_>, + ) -> Result, String> { + utxo_common::watcher_search_for_swap_tx_spend(&self.platform_coin, input, SLP_SWAP_VOUT).await + } +} + impl From for TradePreimageError { fn from(slp: GenSlpSpendErr) -> TradePreimageError { match slp { @@ -1718,7 +1757,10 @@ impl MmCoin for SlpToken { let coin = self.clone(); let fut = async move { - let htlc_fee = coin.platform_coin.get_htlc_spend_fee(SLP_HTLC_SPEND_SIZE).await?; + let htlc_fee = coin + .platform_coin + .get_htlc_spend_fee(SLP_HTLC_SPEND_SIZE, &FeeApproxStage::WithoutApprox) + .await?; let amount = (big_decimal_from_sat_unsigned(htlc_fee, coin.platform_decimals()) + coin.platform_dust_dec()).into(); Ok(TradeFee { diff --git a/mm2src/coins/utxo/utxo_common.rs b/mm2src/coins/utxo/utxo_common.rs index c2dc83bd2a..7fb9962f6b 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -15,8 +15,8 @@ use crate::{CanRefundHtlc, CoinBalance, CoinWithDerivationMethod, GetWithdrawSen RawTransactionError, RawTransactionRequest, RawTransactionRes, SearchForSwapTxSpendInput, SignatureError, SignatureResult, SwapOps, TradePreimageValue, TransactionFut, TxFeeDetails, TxMarshalingErr, ValidateAddressResult, ValidateOtherPubKeyErr, ValidatePaymentFut, ValidatePaymentInput, - VerificationError, VerificationResult, WatcherValidatePaymentInput, WithdrawFrom, WithdrawResult, - WithdrawSenderAddress}; + VerificationError, VerificationResult, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, + WithdrawFrom, WithdrawResult, WithdrawSenderAddress}; use bitcrypto::dhash256; pub use bitcrypto::{dhash160, sha256, ChecksumType}; use chain::constants::SEQUENCE_FINAL; @@ -539,11 +539,18 @@ where } /// returns the fee required to be paid for HTLC spend transaction -pub async fn get_htlc_spend_fee(coin: &T, tx_size: u64) -> UtxoRpcResult { +pub async fn get_htlc_spend_fee( + coin: &T, + tx_size: u64, + stage: &FeeApproxStage, +) -> UtxoRpcResult { let coin_fee = coin.get_tx_fee().await?; let mut fee = match coin_fee { // atomic swap payment spend transaction is slightly more than 300 bytes in average as of now - ActualTxFee::Dynamic(fee_per_kb) => (fee_per_kb * tx_size) / KILO_BYTE, + ActualTxFee::Dynamic(fee_per_kb) => { + let fee_per_kb = increase_dynamic_fee_by_stage(&coin, fee_per_kb, stage); + (fee_per_kb * tx_size) / KILO_BYTE + }, // return satoshis here as swap spend transaction size is always less than 1 kb ActualTxFee::FixedPerKb(satoshis) => { let tx_size_kb = if tx_size % KILO_BYTE == 0 { @@ -1244,7 +1251,10 @@ pub fn send_maker_spends_taker_payment( ) .into(); let fut = async move { - let fee = try_tx_s!(coin.get_htlc_spend_fee(DEFAULT_SWAP_TX_SPEND_SIZE).await); + let fee = try_tx_s!( + coin.get_htlc_spend_fee(DEFAULT_SWAP_TX_SPEND_SIZE, &FeeApproxStage::WithoutApprox) + .await + ); if fee >= prev_transaction.outputs[0].value { return TX_PLAIN_ERR!( "HTLC spend fee {} is greater than transaction output {}", @@ -1347,7 +1357,11 @@ pub fn create_taker_spends_maker_payment_preimage( ) .into(); let fut = async move { - let fee = try_tx_s!(coin.get_htlc_spend_fee(DEFAULT_SWAP_TX_SPEND_SIZE).await); + let fee = try_tx_s!( + coin.get_htlc_spend_fee(DEFAULT_SWAP_TX_SPEND_SIZE, &FeeApproxStage::WatcherPreimage) + .await + ); + if fee >= prev_transaction.outputs[0].value { return TX_PLAIN_ERR!( "HTLC spend fee {} is greater than transaction output {}", @@ -1377,6 +1391,66 @@ pub fn create_taker_spends_maker_payment_preimage( Box::new(fut.boxed().compat()) } +pub fn create_taker_refunds_payment_preimage( + coin: T, + taker_payment_tx: &[u8], + time_lock: u32, + maker_pub: &[u8], + secret_hash: &[u8], + swap_unique_data: &[u8], +) -> TransactionFut { + let my_address = try_tx_fus!(coin.as_ref().derivation_method.iguana_or_err()).clone(); + let mut prev_transaction: UtxoTx = + try_tx_fus!(deserialize(taker_payment_tx).map_err(|e| TransactionErr::Plain(format!("{:?}", e)))); + prev_transaction.tx_hash_algo = coin.as_ref().tx_hash_algo; + drop_mutability!(prev_transaction); + if prev_transaction.outputs.is_empty() { + return try_tx_fus!(TX_PLAIN_ERR!("Transaction doesn't have any output")); + } + + let key_pair = coin.derive_htlc_key_pair(swap_unique_data); + let script_data = Builder::default().push_opcode(Opcode::OP_1).into_script(); + let redeem_script = payment_script( + time_lock, + secret_hash, + key_pair.public(), + &try_tx_fus!(Public::from_slice(maker_pub)), + ) + .into(); + let fut = async move { + let fee = try_tx_s!( + coin.get_htlc_spend_fee(DEFAULT_SWAP_TX_SPEND_SIZE, &FeeApproxStage::WatcherPreimage) + .await + ); + if fee >= prev_transaction.outputs[0].value { + return TX_PLAIN_ERR!( + "HTLC spend fee {} is greater than transaction output {}", + fee, + prev_transaction.outputs[0].value + ); + } + let script_pubkey = output_script(&my_address, ScriptType::P2PKH).to_bytes(); + let output = TransactionOutput { + value: prev_transaction.outputs[0].value - fee, + script_pubkey, + }; + + let input = P2SHSpendingTxInput { + prev_transaction, + redeem_script, + outputs: vec![output], + script_data, + sequence: SEQUENCE_FINAL - 1, + lock_time: time_lock, + keypair: &key_pair, + }; + let transaction = try_tx_s!(coin.p2sh_spending_tx(input).await); + + Ok(transaction.into()) + }; + Box::new(fut.boxed().compat()) +} + pub fn send_taker_spends_maker_payment( coin: T, maker_payment_tx: &[u8], @@ -1408,7 +1482,10 @@ pub fn send_taker_spends_maker_payment( ) .into(); let fut = async move { - let fee = try_tx_s!(coin.get_htlc_spend_fee(DEFAULT_SWAP_TX_SPEND_SIZE).await); + let fee = try_tx_s!( + coin.get_htlc_spend_fee(DEFAULT_SWAP_TX_SPEND_SIZE, &FeeApproxStage::WithoutApprox) + .await + ); if fee >= prev_transaction.outputs[0].value { return TX_PLAIN_ERR!( "HTLC spend fee {} is greater than transaction output {}", @@ -1468,7 +1545,10 @@ pub fn send_taker_refunds_payment( ) .into(); let fut = async move { - let fee = try_tx_s!(coin.get_htlc_spend_fee(DEFAULT_SWAP_TX_SPEND_SIZE).await); + let fee = try_tx_s!( + coin.get_htlc_spend_fee(DEFAULT_SWAP_TX_SPEND_SIZE, &FeeApproxStage::WithoutApprox) + .await + ); if fee >= prev_transaction.outputs[0].value { return TX_PLAIN_ERR!( "HTLC spend fee {} is greater than transaction output {}", @@ -1501,6 +1581,23 @@ pub fn send_taker_refunds_payment( Box::new(fut.boxed().compat()) } +pub fn send_watcher_refunds_taker_payment_preimage( + coin: T, + taker_refunds_payment: &[u8], +) -> TransactionFut { + let transaction: UtxoTx = + try_tx_fus!(deserialize(taker_refunds_payment).map_err(|e| TransactionErr::Plain(format!("{:?}", e)))); + + let fut = async move { + let tx_fut = coin.as_ref().rpc_client.send_transaction(&transaction).compat(); + try_tx_s!(tx_fut.await, transaction); + + Ok(transaction.into()) + }; + + Box::new(fut.boxed().compat()) +} + pub fn send_maker_refunds_payment( coin: T, maker_payment_tx: &[u8], @@ -1527,7 +1624,10 @@ pub fn send_maker_refunds_payment( ) .into(); let fut = async move { - let fee = try_tx_s!(coin.get_htlc_spend_fee(DEFAULT_SWAP_TX_SPEND_SIZE).await); + let fee = try_tx_s!( + coin.get_htlc_spend_fee(DEFAULT_SWAP_TX_SPEND_SIZE, &FeeApproxStage::WithoutApprox) + .await + ); if fee >= prev_transaction.outputs[0].value { return TX_PLAIN_ERR!( "HTLC spend fee {} is greater than transaction output {}", @@ -1628,7 +1728,12 @@ where } } -pub fn check_all_inputs_signed_by_pub(tx: &UtxoTx, expected_pub: &[u8]) -> Result { +pub fn check_all_inputs_signed_by_pub(tx: &[u8], expected_pub: &[u8]) -> Result { + let tx: UtxoTx = try_s!(deserialize(tx).map_err(|e| ERRL!("{:?}", e))); + check_all_utxo_inputs_signed_by_pub(&tx, expected_pub) +} + +pub fn check_all_utxo_inputs_signed_by_pub(tx: &UtxoTx, expected_pub: &[u8]) -> Result { for input in &tx.inputs { let pubkey = if input.has_witness() { try_s!(pubkey_from_witness_script(&input.script_witness)) @@ -1644,6 +1749,48 @@ pub fn check_all_inputs_signed_by_pub(tx: &UtxoTx, expected_pub: &[u8]) -> Resul Ok(true) } +pub fn watcher_validate_taker_fee( + coin: T, + taker_fee_hash: Vec, + verified_pub: Vec, +) -> ValidatePaymentFut<()> { + let fut = async move { + let mut attempts = 0; + let taker_fee_hash = H256Json::from(taker_fee_hash.as_slice()); + loop { + let taker_fee_tx = match coin + .as_ref() + .rpc_client + .get_transaction_bytes(&taker_fee_hash) + .compat() + .await + { + Ok(t) => t, + Err(e) if attempts > 2 => return MmError::err(ValidatePaymentError::from(e.into_inner())), + Err(e) => { + attempts += 1; + error!("Error getting tx {:?} from rpc: {:?}", taker_fee_hash, e); + Timer::sleep(10.).await; + continue; + }, + }; + + match check_all_inputs_signed_by_pub(&*taker_fee_tx, &verified_pub) { + Ok(is_valid) if !is_valid => { + return MmError::err(ValidatePaymentError::WrongPaymentTx( + "Taker fee does not belong to the verified public key".to_string(), + )) + }, + Ok(_) => return Ok(()), + Err(e) => { + return MmError::err(ValidatePaymentError::WrongPaymentTx(e)); + }, + }; + } + }; + Box::new(fut.boxed().compat()) +} + pub fn validate_fee( coin: T, tx: UtxoTx, @@ -1663,7 +1810,7 @@ pub fn validate_fee( coin.addr_format().clone(), )); - if !try_fus!(check_all_inputs_signed_by_pub(&tx, sender_pubkey)) { + if !try_fus!(check_all_utxo_inputs_signed_by_pub(&tx, sender_pubkey)) { return Box::new(futures01::future::err(ERRL!("The dex fee was sent from wrong address"))); } let fut = async move { @@ -1728,9 +1875,9 @@ pub fn validate_maker_payment( tx.tx_hash_algo = coin.as_ref().tx_hash_algo; let htlc_keypair = coin.derive_htlc_key_pair(&input.unique_swap_data); - let other_pub = &try_f!( - Public::from_slice(&input.other_pub).map_to_mm(|err| ValidatePaymentError::InvalidInput(err.to_string())) - ); + let other_pub = + &try_f!(Public::from_slice(&input.other_pub) + .map_to_mm(|err| ValidatePaymentError::InvalidParameter(err.to_string()))); validate_payment( coin.clone(), tx, @@ -1748,15 +1895,14 @@ pub fn validate_maker_payment( pub fn watcher_validate_taker_payment( coin: &T, input: WatcherValidatePaymentInput, -) -> Box> + Send> { - let mut tx: UtxoTx = try_f!(deserialize(input.payment_tx.as_slice()) - .map_err(|err| ValidatePaymentError::TxDeserializationError(err.to_string()))); +) -> ValidatePaymentFut<()> { + let mut tx: UtxoTx = try_f!(deserialize(input.payment_tx.as_slice())); tx.tx_hash_algo = coin.as_ref().tx_hash_algo; let first_pub = &try_f!( - Public::from_slice(&input.taker_pub).map_err(|err| ValidatePaymentError::InvalidInput(err.to_string())) + Public::from_slice(&input.taker_pub).map_err(|err| ValidatePaymentError::InvalidParameter(err.to_string())) ); let second_pub = &try_f!( - Public::from_slice(&input.maker_pub).map_err(|err| ValidatePaymentError::InvalidInput(err.to_string())) + Public::from_slice(&input.maker_pub).map_err(|err| ValidatePaymentError::InvalidParameter(err.to_string())) ); validate_payment( coin.clone(), @@ -1780,9 +1926,9 @@ pub fn validate_taker_payment( tx.tx_hash_algo = coin.as_ref().tx_hash_algo; let htlc_keypair = coin.derive_htlc_key_pair(&input.unique_swap_data); - let other_pub = &try_f!( - Public::from_slice(&input.other_pub).map_to_mm(|err| ValidatePaymentError::InvalidInput(err.to_string())) - ); + let other_pub = + &try_f!(Public::from_slice(&input.other_pub) + .map_to_mm(|err| ValidatePaymentError::InvalidParameter(err.to_string()))); validate_payment( coin.clone(), @@ -1859,6 +2005,24 @@ pub fn check_if_my_payment_sent( Box::new(fut.boxed().compat()) } +pub async fn watcher_search_for_swap_tx_spend + SwapOps>( + coin: &T, + input: WatcherSearchForSwapTxSpendInput<'_>, + output_index: usize, +) -> Result, String> { + search_for_swap_output_spend( + coin.as_ref(), + input.time_lock, + &try_s!(Public::from_slice(input.taker_pub)), + &try_s!(Public::from_slice(input.maker_pub)), + input.secret_hash, + input.tx, + output_index, + input.search_from_block, + ) + .await +} + pub async fn search_for_swap_tx_spend_my + SwapOps>( coin: &T, input: SearchForSwapTxSpendInput<'_>, @@ -3124,7 +3288,7 @@ where /// The fee to spend (receive) other payment is deducted from the trading amount so we should display it pub fn get_receiver_trade_fee(coin: T) -> TradePreimageFut { let fut = async move { - let amount_sat = get_htlc_spend_fee(&coin, DEFAULT_SWAP_TX_SPEND_SIZE).await?; + let amount_sat = get_htlc_spend_fee(&coin, DEFAULT_SWAP_TX_SPEND_SIZE, &FeeApproxStage::WithoutApprox).await?; let amount = big_decimal_from_sat_unsigned(amount_sat, coin.as_ref().decimals).into(); Ok(TradeFee { coin: coin.as_ref().conf.ticker.clone(), @@ -3776,6 +3940,11 @@ where FeeApproxStage::WithoutApprox => return dynamic_fee, // Take into account that the dynamic fee may increase during the swap by [`UtxoCoinFields::tx_fee_volatility_percent`]. FeeApproxStage::StartSwap => base_percent, + // Take into account that the dynamic fee may increase after roughly the locktime is expired [`UtxoCoinFields::tx_fee_volatility_percent`]: + // - watcher can refund the taker payment after the locktime + an extra time to wait for the takers + // - the watcher can spend the taker_spends_maker_payment right after locktime/2, but the worst case should be considered which is slightly before + // the locktime is expired + FeeApproxStage::WatcherPreimage => base_percent, //This needs discussion // Take into account that the dynamic fee may increase at each of the following stages up to [`UtxoCoinFields::tx_fee_volatility_percent`]: // - until a swap is started; // - during the swap. diff --git a/mm2src/coins/utxo/utxo_standard.rs b/mm2src/coins/utxo/utxo_standard.rs index c29945737e..650a160a6f 100644 --- a/mm2src/coins/utxo/utxo_standard.rs +++ b/mm2src/coins/utxo/utxo_standard.rs @@ -1,7 +1,7 @@ use super::*; use crate::coin_balance::{self, EnableCoinBalanceError, EnabledCoinBalanceParams, HDAccountBalance, HDAddressBalance, HDWalletBalance, HDWalletBalanceOps}; -use crate::coin_errors::{MyAddressError, ValidatePaymentError}; +use crate::coin_errors::MyAddressError; use crate::hd_pubkey::{ExtractExtendedPubkey, HDExtractPubkeyError, HDXPubExtractor}; use crate::hd_wallet::{AccountUpdatingError, AddressDerivingResult, HDAccountMut, NewAccountCreatingError}; use crate::hd_wallet_storage::HDWalletCoinWithStorageOps; @@ -23,8 +23,8 @@ use crate::utxo::utxo_tx_history_v2::{UtxoMyAddressesHistoryError, UtxoTxDetails use crate::{CanRefundHtlc, CoinBalance, CoinWithDerivationMethod, GetWithdrawSenderAddress, NegotiateSwapContractAddrErr, PrivKeyBuildPolicy, SearchForSwapTxSpendInput, SignatureResult, SwapOps, TradePreimageValue, TransactionFut, TxMarshalingErr, ValidateAddressResult, ValidateOtherPubKeyErr, - ValidatePaymentFut, ValidatePaymentInput, VerificationResult, WatcherValidatePaymentInput, WithdrawFut, - WithdrawSenderAddress}; + ValidatePaymentFut, ValidatePaymentInput, VerificationResult, WatcherOps, + WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WithdrawFut, WithdrawSenderAddress}; use crypto::Bip44Chain; use futures::{FutureExt, TryFutureExt}; use mm2_metrics::MetricsArc; @@ -150,8 +150,8 @@ impl GetUtxoMapOps for UtxoStandardCoin { #[async_trait] #[cfg_attr(test, mockable)] impl UtxoCommonOps for UtxoStandardCoin { - async fn get_htlc_spend_fee(&self, tx_size: u64) -> UtxoRpcResult { - utxo_common::get_htlc_spend_fee(self, tx_size).await + async fn get_htlc_spend_fee(&self, tx_size: u64, stage: &FeeApproxStage) -> UtxoRpcResult { + utxo_common::get_htlc_spend_fee(self, tx_size, stage).await } fn addresses_from_script(&self, script: &Script) -> Result, String> { @@ -271,10 +271,12 @@ impl UtxoStandardOps for UtxoStandardCoin { #[async_trait] impl SwapOps for UtxoStandardCoin { + #[inline] fn send_taker_fee(&self, fee_addr: &[u8], amount: BigDecimal, _uuid: &[u8]) -> TransactionFut { utxo_common::send_taker_fee(self.clone(), fee_addr, amount) } + #[inline] fn send_maker_payment( &self, _time_lock_duration: u64, @@ -295,6 +297,7 @@ impl SwapOps for UtxoStandardCoin { ) } + #[inline] fn send_taker_payment( &self, _time_lock_duration: u64, @@ -315,6 +318,7 @@ impl SwapOps for UtxoStandardCoin { ) } + #[inline] fn send_maker_spends_taker_payment( &self, taker_payment_tx: &[u8], @@ -336,24 +340,7 @@ impl SwapOps for UtxoStandardCoin { ) } - fn create_taker_spends_maker_payment_preimage( - &self, - maker_payment_tx: &[u8], - time_lock: u32, - maker_pub: &[u8], - secret_hash: &[u8], - swap_unique_data: &[u8], - ) -> TransactionFut { - utxo_common::create_taker_spends_maker_payment_preimage( - self.clone(), - maker_payment_tx, - time_lock, - maker_pub, - secret_hash, - swap_unique_data, - ) - } - + #[inline] fn send_taker_spends_maker_payment( &self, maker_tx: &[u8], @@ -375,10 +362,7 @@ impl SwapOps for UtxoStandardCoin { ) } - fn send_taker_spends_maker_payment_preimage(&self, preimage: &[u8], secret: &[u8]) -> TransactionFut { - utxo_common::send_taker_spends_maker_payment_preimage(self.clone(), preimage, secret) - } - + #[inline] fn send_taker_refunds_payment( &self, taker_tx: &[u8], @@ -398,6 +382,7 @@ impl SwapOps for UtxoStandardCoin { ) } + #[inline] fn send_maker_refunds_payment( &self, maker_tx: &[u8], @@ -441,21 +426,17 @@ impl SwapOps for UtxoStandardCoin { ) } + #[inline] fn validate_maker_payment(&self, input: ValidatePaymentInput) -> ValidatePaymentFut<()> { utxo_common::validate_maker_payment(self, input) } + #[inline] fn validate_taker_payment(&self, input: ValidatePaymentInput) -> ValidatePaymentFut<()> { utxo_common::validate_taker_payment(self, input) } - fn watcher_validate_taker_payment( - &self, - input: WatcherValidatePaymentInput, - ) -> Box> + Send> { - utxo_common::watcher_validate_taker_payment(self, input) - } - + #[inline] fn check_if_my_payment_sent( &self, time_lock: u32, @@ -469,6 +450,7 @@ impl SwapOps for UtxoStandardCoin { utxo_common::check_if_my_payment_sent(self.clone(), time_lock, other_pub, secret_hash, swap_unique_data) } + #[inline] async fn search_for_swap_tx_spend_my( &self, input: SearchForSwapTxSpendInput<'_>, @@ -476,6 +458,7 @@ impl SwapOps for UtxoStandardCoin { utxo_common::search_for_swap_tx_spend_my(self, input, utxo_common::DEFAULT_SWAP_VOUT).await } + #[inline] async fn search_for_swap_tx_spend_other( &self, input: SearchForSwapTxSpendInput<'_>, @@ -483,10 +466,17 @@ impl SwapOps for UtxoStandardCoin { utxo_common::search_for_swap_tx_spend_other(self, input, utxo_common::DEFAULT_SWAP_VOUT).await } + #[inline] + fn check_tx_signed_by_pub(&self, tx: &[u8], expected_pub: &[u8]) -> Result { + utxo_common::check_all_inputs_signed_by_pub(tx, expected_pub) + } + + #[inline] fn extract_secret(&self, secret_hash: &[u8], spend_tx: &[u8]) -> Result, String> { utxo_common::extract_secret(secret_hash, spend_tx) } + #[inline] fn can_refund_htlc(&self, locktime: u64) -> Box + Send + '_> { Box::new( utxo_common::can_refund_htlc(self, locktime) @@ -496,6 +486,7 @@ impl SwapOps for UtxoStandardCoin { ) } + #[inline] fn negotiate_swap_contract_addr( &self, _other_side_address: Option<&[u8]>, @@ -503,15 +494,87 @@ impl SwapOps for UtxoStandardCoin { Ok(None) } + #[inline] fn derive_htlc_key_pair(&self, swap_unique_data: &[u8]) -> KeyPair { utxo_common::derive_htlc_key_pair(self.as_ref(), swap_unique_data) } + #[inline] fn validate_other_pubkey(&self, raw_pubkey: &[u8]) -> MmResult<(), ValidateOtherPubKeyErr> { utxo_common::validate_other_pubkey(raw_pubkey) } } +#[async_trait] +impl WatcherOps for UtxoStandardCoin { + #[inline] + fn create_taker_refunds_payment_preimage( + &self, + taker_tx: &[u8], + time_lock: u32, + maker_pub: &[u8], + secret_hash: &[u8], + _swap_contract_address: &Option, + swap_unique_data: &[u8], + ) -> TransactionFut { + utxo_common::create_taker_refunds_payment_preimage( + self.clone(), + taker_tx, + time_lock, + maker_pub, + secret_hash, + swap_unique_data, + ) + } + + #[inline] + fn create_taker_spends_maker_payment_preimage( + &self, + maker_payment_tx: &[u8], + time_lock: u32, + maker_pub: &[u8], + secret_hash: &[u8], + swap_unique_data: &[u8], + ) -> TransactionFut { + utxo_common::create_taker_spends_maker_payment_preimage( + self.clone(), + maker_payment_tx, + time_lock, + maker_pub, + secret_hash, + swap_unique_data, + ) + } + + #[inline] + fn send_watcher_refunds_taker_payment_preimage(&self, preimage: &[u8]) -> TransactionFut { + utxo_common::send_watcher_refunds_taker_payment_preimage(self.clone(), preimage) + } + + #[inline] + fn send_taker_spends_maker_payment_preimage(&self, preimage: &[u8], secret: &[u8]) -> TransactionFut { + utxo_common::send_taker_spends_maker_payment_preimage(self.clone(), preimage, secret) + } + + #[inline] + fn watcher_validate_taker_fee(&self, taker_fee_hash: Vec, verified_pub: Vec) -> ValidatePaymentFut<()> { + utxo_common::watcher_validate_taker_fee(self.clone(), taker_fee_hash, verified_pub) + } + + #[inline] + fn watcher_validate_taker_payment(&self, input: WatcherValidatePaymentInput) -> ValidatePaymentFut<()> { + utxo_common::watcher_validate_taker_payment(self, input) + } + + #[inline] + async fn watcher_search_for_swap_tx_spend( + &self, + input: WatcherSearchForSwapTxSpendInput<'_>, + ) -> Result, String> { + utxo_common::watcher_search_for_swap_tx_spend(self, input, utxo_common::DEFAULT_SWAP_VOUT).await + } +} + impl MarketCoinOps for UtxoStandardCoin { fn ticker(&self) -> &str { &self.utxo_arc.conf.ticker } diff --git a/mm2src/coins/z_coin.rs b/mm2src/coins/z_coin.rs index e2514e1bf9..d7fb492a2a 100644 --- a/mm2src/coins/z_coin.rs +++ b/mm2src/coins/z_coin.rs @@ -1,4 +1,4 @@ -use crate::coin_errors::{MyAddressError, ValidatePaymentError}; +use crate::coin_errors::MyAddressError; use crate::my_tx_history_v2::{MyTxHistoryErrorV2, MyTxHistoryRequestV2, MyTxHistoryResponseV2}; use crate::rpc_command::init_withdraw::{InitWithdrawCoin, WithdrawInProgressStatus, WithdrawTaskHandle}; use crate::utxo::rpc_clients::{ElectrumRpcRequest, UnspentInfo, UtxoRpcClientEnum, UtxoRpcError, UtxoRpcFut, @@ -18,7 +18,8 @@ use crate::{BalanceError, BalanceFut, CoinBalance, CoinFutSpawner, FeeApproxStag SwapOps, TradeFee, TradePreimageFut, TradePreimageResult, TradePreimageValue, TransactionDetails, TransactionEnum, TransactionFut, TxFeeDetails, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateOtherPubKeyErr, ValidatePaymentFut, ValidatePaymentInput, - VerificationError, VerificationResult, WatcherValidatePaymentInput, WithdrawFut, WithdrawRequest}; + VerificationError, VerificationResult, WatcherOps, WatcherSearchForSwapTxSpendInput, + WatcherValidatePaymentInput, WithdrawFut, WithdrawRequest}; use crate::{Transaction, WithdrawError}; use async_trait::async_trait; use bitcrypto::dhash256; @@ -1131,17 +1132,6 @@ impl SwapOps for ZCoin { Box::new(fut.boxed().compat()) } - fn create_taker_spends_maker_payment_preimage( - &self, - _maker_payment_tx: &[u8], - _time_lock: u32, - _maker_pub: &[u8], - _secret_hash: &[u8], - _swap_unique_data: &[u8], - ) -> TransactionFut { - unimplemented!(); - } - fn send_taker_spends_maker_payment( &self, maker_payment_tx: &[u8], @@ -1181,10 +1171,6 @@ impl SwapOps for ZCoin { Box::new(fut.boxed().compat()) } - fn send_taker_spends_maker_payment_preimage(&self, _preimage: &[u8], _secret: &[u8]) -> TransactionFut { - unimplemented!(); - } - fn send_taker_refunds_payment( &self, taker_payment_tx: &[u8], @@ -1340,21 +1326,17 @@ impl SwapOps for ZCoin { Box::new(fut.boxed().compat()) } + #[inline] fn validate_maker_payment(&self, input: ValidatePaymentInput) -> ValidatePaymentFut<()> { utxo_common::validate_maker_payment(self, input) } + #[inline] fn validate_taker_payment(&self, input: ValidatePaymentInput) -> ValidatePaymentFut<()> { utxo_common::validate_taker_payment(self, input) } - fn watcher_validate_taker_payment( - &self, - _input: WatcherValidatePaymentInput, - ) -> Box> + Send> { - unimplemented!(); - } - + #[inline] fn check_if_my_payment_sent( &self, time_lock: u32, @@ -1368,6 +1350,7 @@ impl SwapOps for ZCoin { utxo_common::check_if_my_payment_sent(self.clone(), time_lock, other_pub, secret_hash, swap_unique_data) } + #[inline] async fn search_for_swap_tx_spend_my( &self, input: SearchForSwapTxSpendInput<'_>, @@ -1375,6 +1358,7 @@ impl SwapOps for ZCoin { utxo_common::search_for_swap_tx_spend_my(self, input, utxo_common::DEFAULT_SWAP_VOUT).await } + #[inline] async fn search_for_swap_tx_spend_other( &self, input: SearchForSwapTxSpendInput<'_>, @@ -1382,10 +1366,16 @@ impl SwapOps for ZCoin { utxo_common::search_for_swap_tx_spend_other(self, input, utxo_common::DEFAULT_SWAP_VOUT).await } + fn check_tx_signed_by_pub(&self, _tx: &[u8], _expected_pub: &[u8]) -> Result { + unimplemented!(); + } + + #[inline] fn extract_secret(&self, secret_hash: &[u8], spend_tx: &[u8]) -> Result, String> { utxo_common::extract_secret(secret_hash, spend_tx) } + #[inline] fn negotiate_swap_contract_addr( &self, _other_side_address: Option<&[u8]>, @@ -1401,11 +1391,61 @@ impl SwapOps for ZCoin { key_pair_from_secret(key.as_slice()).expect("valid privkey") } + #[inline] fn validate_other_pubkey(&self, raw_pubkey: &[u8]) -> MmResult<(), ValidateOtherPubKeyErr> { utxo_common::validate_other_pubkey(raw_pubkey) } } +#[async_trait] +impl WatcherOps for ZCoin { + fn create_taker_spends_maker_payment_preimage( + &self, + _maker_payment_tx: &[u8], + _time_lock: u32, + _maker_pub: &[u8], + _secret_hash: &[u8], + _swap_unique_data: &[u8], + ) -> TransactionFut { + unimplemented!(); + } + + fn send_taker_spends_maker_payment_preimage(&self, _preimage: &[u8], _secret: &[u8]) -> TransactionFut { + unimplemented!(); + } + + fn create_taker_refunds_payment_preimage( + &self, + _taker_payment_tx: &[u8], + _time_lock: u32, + _maker_pub: &[u8], + _secret_hash: &[u8], + _swap_contract_address: &Option, + _swap_unique_data: &[u8], + ) -> TransactionFut { + unimplemented!(); + } + + fn send_watcher_refunds_taker_payment_preimage(&self, _taker_refunds_payment: &[u8]) -> TransactionFut { + unimplemented!(); + } + + fn watcher_validate_taker_fee(&self, _taker_fee_hash: Vec, _verified_pub: Vec) -> ValidatePaymentFut<()> { + unimplemented!(); + } + + fn watcher_validate_taker_payment(&self, _input: WatcherValidatePaymentInput) -> ValidatePaymentFut<()> { + unimplemented!(); + } + + async fn watcher_search_for_swap_tx_spend( + &self, + _input: WatcherSearchForSwapTxSpendInput<'_>, + ) -> Result, String> { + unimplemented!(); + } +} + #[async_trait] impl MmCoin for ZCoin { fn is_asset_chain(&self) -> bool { self.utxo_arc.conf.asset_chain } @@ -1558,8 +1598,8 @@ impl GetUtxoListOps for ZCoin { #[async_trait] impl UtxoCommonOps for ZCoin { - async fn get_htlc_spend_fee(&self, tx_size: u64) -> UtxoRpcResult { - utxo_common::get_htlc_spend_fee(self, tx_size).await + async fn get_htlc_spend_fee(&self, tx_size: u64, stage: &FeeApproxStage) -> UtxoRpcResult { + utxo_common::get_htlc_spend_fee(self, tx_size, stage).await } fn addresses_from_script(&self, script: &Script) -> Result, String> { diff --git a/mm2src/common/time_cache.rs b/mm2src/common/time_cache.rs index 6667a20137..8b8ddb3bbf 100644 --- a/mm2src/common/time_cache.rs +++ b/mm2src/common/time_cache.rs @@ -271,6 +271,10 @@ where } pub fn contains(&mut self, key: &Key) -> bool { self.0.contains_key(key) } + + // Removes a certain key even if it didn't expire plus removing other expired keys + #[inline] + pub fn remove(&mut self, key: Key) { self.0.remove(key); } } #[cfg(test)] diff --git a/mm2src/mm2_main/src/docker_tests.rs b/mm2src/mm2_main/src/docker_tests.rs index 5e5e8c285a..1157b21d21 100644 --- a/mm2src/mm2_main/src/docker_tests.rs +++ b/mm2src/mm2_main/src/docker_tests.rs @@ -110,7 +110,7 @@ mod docker_tests { use coins::utxo::utxo_standard::{utxo_standard_coin_with_priv_key, UtxoStandardCoin}; use coins::utxo::{dhash160, GetUtxoListOps, UtxoActivationParams, UtxoCommonOps}; use coins::{CoinProtocol, FoundSwapTxSpend, MarketCoinOps, MmCoin, SearchForSwapTxSpendInput, SwapOps, - Transaction, TransactionEnum, WithdrawRequest}; + Transaction, TransactionEnum, WatcherOps, WithdrawRequest}; use common::{block_on, now_ms}; use crypto::privkey::{key_pair_from_secret, key_pair_from_seed}; use futures01::Future; @@ -1158,7 +1158,7 @@ mod docker_tests { } #[test] - fn test_watcher_node() { + fn test_watcher_spends_taker_spends_maker_payment() { let (_ctx, _, bob_priv_key) = generate_utxo_coin_with_random_privkey("MYCOIN", 100.into()); generate_utxo_coin_with_privkey("MYCOIN1", 100.into(), &bob_priv_key); let (_ctx, _, alice_priv_key) = generate_utxo_coin_with_random_privkey("MYCOIN1", 100.into()); @@ -1219,9 +1219,9 @@ mod docker_tests { .unwrap(); assert!(rc.0.is_success(), "!buy: {}", rc.1); - block_on(mm_alice.wait_for_log(60., |log| log.contains("Taker payment tx hash"))).unwrap(); + block_on(mm_alice.wait_for_log(60., |log| log.contains("Watcher message sent..."))).unwrap(); block_on(mm_alice.stop()).unwrap(); - block_on(mm_watcher.wait_for_log(60., |log| log.contains("Maker payment spend tx"))).unwrap(); + block_on(mm_watcher.wait_for_log(60., |log| log.contains("Sent maker payment spend tx"))).unwrap(); thread::sleep(Duration::from_secs(5)); let mm_alice = MarketMakerIt::start(alice_conf, "pass".to_string(), None).unwrap(); @@ -1279,6 +1279,51 @@ mod docker_tests { assert_eq!(bob_mycoin_balance, "97.99999"); } + #[test] + fn test_watcher_refunds_taker_payment() { + let timeout = (now_ms() / 1000) + 120; // timeout if test takes more than 120 seconds to run + let (_ctx, coin, _) = generate_utxo_coin_with_random_privkey("MYCOIN", 1000u64.into()); + let my_public_key = coin.my_public_key().unwrap(); + + let time_lock = (now_ms() / 1000) as u32 - 3600; + let tx = coin + .send_taker_payment(0, time_lock, my_public_key, &[0; 20], 1u64.into(), &None, &[]) + .wait() + .unwrap(); + + coin.wait_for_confirmations(&tx.tx_hex(), 1, false, timeout, 1) + .wait() + .unwrap(); + + let refund_tx = coin + .create_taker_refunds_payment_preimage(&tx.tx_hex(), time_lock, my_public_key, &[0; 20], &None, &[]) + .wait() + .unwrap(); + + let refund_tx = coin + .send_watcher_refunds_taker_payment_preimage(&refund_tx.tx_hex()) + .wait() + .unwrap(); + + coin.wait_for_confirmations(&refund_tx.tx_hex(), 1, false, timeout, 1) + .wait() + .unwrap(); + + let search_input = SearchForSwapTxSpendInput { + time_lock, + other_pub: &*coin.my_public_key().unwrap(), + secret_hash: &[0; 20], + tx: &tx.tx_hex(), + search_from_block: 0, + swap_contract_address: &None, + swap_unique_data: &[], + }; + let found = block_on(coin.search_for_swap_tx_spend_my(search_input)) + .unwrap() + .unwrap(); + assert_eq!(FoundSwapTxSpend::Refunded(refund_tx), found); + } + // https://github.com/KomodoPlatform/atomicDEX-API/issues/471 #[test] fn test_match_and_trade_setprice_max() { diff --git a/mm2src/mm2_main/src/lp_swap.rs b/mm2src/mm2_main/src/lp_swap.rs index 937380d387..64364a5636 100644 --- a/mm2src/mm2_main/src/lp_swap.rs +++ b/mm2src/mm2_main/src/lp_swap.rs @@ -60,6 +60,7 @@ use crate::mm2::lp_network::{broadcast_p2p_msg, Libp2pPeerId}; use coins::{lp_coinfind, MmCoinEnum, TradeFee, TransactionEnum}; use common::log::{debug, warn}; +use common::time_cache::DuplicateCache; use common::{bits256, calc_total_pages, executor::{spawn_abortable, AbortOnDropHandle, SpawnFuture, Timer}, log::{error, info}, @@ -80,6 +81,7 @@ use std::num::NonZeroUsize; use std::path::PathBuf; use std::str::FromStr; use std::sync::{Arc, Mutex, Weak}; +use std::time::Duration; use uuid::Uuid; use bitcrypto::{dhash160, sha256}; @@ -112,7 +114,8 @@ use pubkey_banning::BanReason; pub use pubkey_banning::{ban_pubkey_rpc, is_pubkey_banned, list_banned_pubkeys_rpc, unban_pubkeys_rpc}; pub use recreate_swap_data::recreate_swap_data; pub use saved_swap::{SavedSwap, SavedSwapError, SavedSwapIo, SavedSwapResult}; -pub use swap_watcher::{process_watcher_msg, watcher_topic, TakerSwapWatcherData, WATCHER_PREFIX}; +pub use swap_watcher::{process_watcher_msg, watcher_topic, TakerSwapWatcherData, TAKER_SWAP_ENTRY_TIMEOUT, + WATCHER_PREFIX}; use taker_swap::TakerSwapEvent; pub use taker_swap::{calc_max_taker_vol, check_balance_for_taker_swap, max_taker_vol, max_taker_vol_from_available, run_taker_swap, taker_swap_trade_preimage, RunTakerSwapInput, TakerSavedSwap, TakerSwap, @@ -308,6 +311,16 @@ pub fn get_payment_locktime() -> u64 { PAYMENT_LOCKTIME.load(Ordering::Relaxed) } +#[inline] +pub fn wait_for_taker_payment_conf_until(swap_started_at: u64, locktime: u64) -> u64 { + swap_started_at + (locktime * 4) / 5 +} + +#[inline] +pub fn wait_for_maker_payment_conf_until(swap_started_at: u64, locktime: u64) -> u64 { + swap_started_at + (locktime * 2) / 5 +} + const _SWAP_DEFAULT_NUM_CONFIRMS: u32 = 1; const _SWAP_DEFAULT_MAX_CONFIRMS: u32 = 6; /// MM2 checks that swap payment is confirmed every WAIT_CONFIRM_INTERVAL seconds @@ -365,7 +378,7 @@ struct SwapsContext { running_swaps: Mutex>>, banned_pubkeys: Mutex>, swap_msgs: Mutex>, - taker_swap_watchers: PaMutex>, + taker_swap_watchers: PaMutex>>, #[cfg(target_arch = "wasm32")] swap_db: ConstructibleDb, } @@ -378,7 +391,7 @@ impl SwapsContext { running_swaps: Mutex::new(vec![]), banned_pubkeys: Mutex::new(HashMap::new()), swap_msgs: Mutex::new(HashMap::new()), - taker_swap_watchers: PaMutex::new(HashSet::new()), + taker_swap_watchers: PaMutex::new(DuplicateCache::new(Duration::from_secs(TAKER_SWAP_ENTRY_TIMEOUT))), #[cfg(target_arch = "wasm32")] swap_db: ConstructibleDb::new(ctx), }) diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap.rs b/mm2src/mm2_main/src/lp_swap/maker_swap.rs index 87aa6aaf06..2afbe34b25 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap.rs @@ -4,7 +4,8 @@ use super::pubkey_banning::ban_pubkey_on_failed_swap; use super::swap_lock::{SwapLock, SwapLockOps}; use super::trade_preimage::{TradePreimageRequest, TradePreimageRpcError, TradePreimageRpcResult}; use super::{broadcast_my_swap_status, broadcast_swap_message_every, check_other_coin_balance_for_swap, - dex_fee_amount_from_taker_coin, get_locked_amount, recv_swap_msg, swap_topic, AtomicSwap, LockedAmount, + dex_fee_amount_from_taker_coin, get_locked_amount, recv_swap_msg, swap_topic, + wait_for_maker_payment_conf_until, wait_for_taker_payment_conf_until, AtomicSwap, LockedAmount, MySwapInfo, NegotiationDataMsg, NegotiationDataV2, NegotiationDataV3, RecoveredSwap, RecoveredSwapAction, SavedSwap, SavedSwapIo, SavedTradeFee, SwapConfirmationsSettings, SwapError, SwapMsg, SwapsContext, TransactionIdentifier, WAIT_CONFIRM_INTERVAL}; @@ -765,7 +766,8 @@ impl MakerSwap { let abort_send_handle = broadcast_swap_message_every(self.ctx.clone(), swap_topic(&self.uuid), msg, 600., self.p2p_privkey); - let maker_payment_wait_confirm = self.r().data.started_at + (self.r().data.lock_duration * 2) / 5; + let maker_payment_wait_confirm = + wait_for_maker_payment_conf_until(self.r().data.started_at, self.r().data.lock_duration); let f = self.maker_coin.wait_for_confirmations( &self.r().maker_payment.clone().unwrap().tx_hex, self.r().data.maker_payment_confirmations, @@ -833,8 +835,8 @@ impl MakerSwap { } async fn validate_taker_payment(&self) -> Result<(Option, Vec), String> { - let wait_duration = (self.r().data.lock_duration * 4) / 5; - let wait_taker_payment = self.r().data.started_at + wait_duration; + let wait_taker_payment = + wait_for_taker_payment_conf_until(self.r().data.started_at, self.r().data.lock_duration); let confirmations = self.r().data.taker_payment_confirmations; let wait_f = self @@ -894,9 +896,7 @@ impl MakerSwap { ])); } - let duration = (self.r().data.lock_duration * 4) / 5; - let timeout = self.r().data.started_at + duration; - + let timeout = wait_for_taker_payment_conf_until(self.r().data.started_at, self.r().data.lock_duration); let now = now_ms() / 1000; if now > timeout { return Ok((Some(MakerSwapCommand::RefundMakerPayment), vec![ diff --git a/mm2src/mm2_main/src/lp_swap/recreate_swap_data.rs b/mm2src/mm2_main/src/lp_swap/recreate_swap_data.rs index eb71dccebe..1374155cfa 100644 --- a/mm2src/mm2_main/src/lp_swap/recreate_swap_data.rs +++ b/mm2src/mm2_main/src/lp_swap/recreate_swap_data.rs @@ -1,9 +1,9 @@ use crate::mm2::lp_swap::maker_swap::{MakerSwapData, MakerSwapEvent, TakerNegotiationData, MAKER_ERROR_EVENTS, MAKER_SUCCESS_EVENTS}; -use crate::mm2::lp_swap::taker_swap::{maker_payment_wait, MakerNegotiationData, TakerPaymentSpentData, - TakerSavedEvent, TakerSwapData, TakerSwapEvent, TAKER_ERROR_EVENTS, - TAKER_SUCCESS_EVENTS}; -use crate::mm2::lp_swap::{MakerSavedEvent, MakerSavedSwap, SavedSwap, SwapError, TakerSavedSwap}; +use crate::mm2::lp_swap::taker_swap::{MakerNegotiationData, TakerPaymentSpentData, TakerSavedEvent, TakerSwapData, + TakerSwapEvent, TAKER_ERROR_EVENTS, TAKER_SUCCESS_EVENTS}; +use crate::mm2::lp_swap::{wait_for_maker_payment_conf_until, MakerSavedEvent, MakerSavedSwap, SavedSwap, SwapError, + TakerSavedSwap}; use coins::{lp_coinfind, MmCoinEnum}; use common::{HttpStatusCode, StatusCode}; use derive_more::Display; @@ -265,7 +265,7 @@ fn convert_taker_to_maker_events( | TakerSwapEvent::MakerPaymentSpent(_) | TakerSwapEvent::MakerPaymentSpendFailed(_) // We don't know the reason at the moment, so we rely on the errors handling above. - | TakerSwapEvent::WatcherMessageSent(_) + | TakerSwapEvent::WatcherMessageSent(_,_) | TakerSwapEvent::TakerPaymentWaitRefundStarted { .. } | TakerSwapEvent::TakerPaymentRefunded(_) | TakerSwapEvent::TakerPaymentRefundFailed(_) @@ -328,7 +328,7 @@ async fn recreate_taker_swap(ctx: MmArc, maker_swap: MakerSavedSwap) -> Recreate taker_payment_lock: negotiated_event.taker_payment_locktime, uuid: started_event.uuid, started_at: started_event.started_at, - maker_payment_wait: maker_payment_wait(started_event.started_at, started_event.lock_duration), + maker_payment_wait: wait_for_maker_payment_conf_until(started_event.started_at, started_event.lock_duration), maker_coin_start_block: started_event.maker_coin_start_block, taker_coin_start_block: started_event.taker_coin_start_block, // Don't set the fee since the value is used when we calculate locked by other swaps amount only. diff --git a/mm2src/mm2_main/src/lp_swap/swap_watcher.rs b/mm2src/mm2_main/src/lp_swap/swap_watcher.rs index c045d808d8..44de441776 100644 --- a/mm2src/mm2_main/src/lp_swap/swap_watcher.rs +++ b/mm2src/mm2_main/src/lp_swap/swap_watcher.rs @@ -1,41 +1,37 @@ -use super::{broadcast_p2p_tx_msg, lp_coinfind, tx_helper_topic, H256Json, SwapsContext, TransactionIdentifier, - WAIT_CONFIRM_INTERVAL}; -use coins::{MmCoinEnum, WatcherValidatePaymentInput}; -use common::executor::{AbortSettings, SpawnAbortable}; +use super::{broadcast_p2p_tx_msg, lp_coinfind, tx_helper_topic, wait_for_taker_payment_conf_until, H256Json, + SwapsContext, TransactionIdentifier, WAIT_CONFIRM_INTERVAL}; +use crate::mm2::MmError; +use async_trait::async_trait; +use coins::{CanRefundHtlc, FoundSwapTxSpend, MmCoinEnum, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput}; +use common::executor::{AbortSettings, SpawnAbortable, Timer}; use common::log::{error, info}; +use common::state_machine::prelude::*; use futures::compat::Future01CompatExt; -use futures::{select, FutureExt}; use mm2_core::mm_ctx::MmArc; use mm2_libp2p::{decode_signed, pub_sub_topic, TopicPrefix}; use mm2_number::BigDecimal; -use parking_lot::Mutex as PaMutex; use std::cmp::min; use std::sync::Arc; -use std::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; use uuid::Uuid; +#[cfg(not(test))] use common::now_ms; + pub const WATCHER_PREFIX: TopicPrefix = "swpwtchr"; const TAKER_SWAP_CONFIRMATIONS: u64 = 1; +pub const TAKER_SWAP_ENTRY_TIMEOUT: u64 = 21600; +const WAIT_FOR_TAKER_REFUND: u64 = 1200; // How long? -#[derive(Clone, Debug, Deserialize, Serialize)] -pub enum SwapWatcherMsg { - TakerSwapWatcherMsg(Box), -} - -pub struct Watcher { - uuid: Uuid, +struct WatcherContext { ctx: MmArc, taker_coin: MmCoinEnum, maker_coin: MmCoinEnum, - mutable: RwLock, - errors: PaMutex>, data: TakerSwapWatcherData, + verified_pub: Vec, } -pub struct WatcherMut { - taker_payment_spend: Option, - maker_payment_spend: Option, - secret: H256Json, +#[derive(Clone, Debug, Deserialize, Serialize)] +pub enum SwapWatcherMsg { + TakerSwapWatcherMsg(TakerSwapWatcherData), } #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] @@ -43,9 +39,11 @@ pub struct TakerSwapWatcherData { pub uuid: Uuid, pub secret_hash: Vec, pub taker_spends_maker_payment_preimage: Vec, + pub taker_refunds_payment: Vec, pub swap_started_at: u64, pub lock_duration: u64, pub taker_coin: String, + pub taker_fee_hash: Vec, pub taker_payment_hex: Vec, pub taker_payment_lock: u64, pub taker_pub: Vec, @@ -57,205 +55,240 @@ pub struct TakerSwapWatcherData { pub maker_pub: Vec, } -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] -pub struct TakerPaymentSpentData { - pub transaction: TransactionIdentifier, - pub secret: H256Json, -} +struct ValidatePublicKeys {} +struct ValidateTakerFee {} +struct ValidateTakerPayment {} +struct WaitForTakerPaymentSpend {} -#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)] -pub struct WatcherError { - error: String, -} +struct RefundTakerPayment {} -impl From for WatcherError { - fn from(error: String) -> Self { WatcherError { error } } +struct SpendMakerPayment { + secret: H256Json, } -impl From<&str> for WatcherError { - fn from(e: &str) -> Self { WatcherError { error: e.to_owned() } } +impl SpendMakerPayment { + fn new(secret: H256Json) -> Self { SpendMakerPayment { secret } } } -#[allow(clippy::large_enum_variant)] -pub enum RunWatcherInput { - StartNew(Watcher), +struct Stopped { + _stop_reason: StopReason, } -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] -#[serde(tag = "type", content = "data")] -#[allow(clippy::large_enum_variant)] -pub enum WatcherEvent { - Started, - StartFailed(WatcherError), - TakerPaymentWaitConfirmFailed(WatcherError), - TakerPaymentValidatedAndConfirmed, - TakerPaymentValidateFailed(WatcherError), - TakerPaymentSpent(TakerPaymentSpentData), - TakerPaymentWaitForSpendFailed(WatcherError), - MakerPaymentSpendFailed(WatcherError), - MakerPaymentSpent(TransactionIdentifier), - Finished, +#[derive(Debug)] +enum StopReason { + Finished(WatcherSuccess), + Error(MmError), } -impl WatcherEvent { - pub fn status_str(&self) -> String { - match self { - WatcherEvent::Started => "Started...".to_owned(), - WatcherEvent::StartFailed(_) => "Start failed...".to_owned(), - WatcherEvent::TakerPaymentWaitConfirmFailed(_) => { - "Taker payment wait for confirmation failed...".to_owned() - }, - WatcherEvent::TakerPaymentValidatedAndConfirmed => "Taker payment validated and confirmed...".to_owned(), - WatcherEvent::TakerPaymentValidateFailed(_) => "Taker payment validate failed...".to_owned(), - WatcherEvent::TakerPaymentSpent(_) => "Taker payment spent...".to_owned(), - WatcherEvent::TakerPaymentWaitForSpendFailed(_) => "Taker payment wait for spend failed...".to_owned(), - WatcherEvent::MakerPaymentSpendFailed(_) => "Maker payment spend failed...".to_owned(), - WatcherEvent::MakerPaymentSpent(_) => "Maker payment spent...".to_owned(), - WatcherEvent::Finished => "Finished".to_owned(), - } - } +#[derive(Debug)] +enum WatcherSuccess { + MakerPaymentSpent, + TakerPaymentRefunded, + TakerPaymentAlreadySpent, + TakerPaymentAlreadyRefunded, } #[derive(Debug)] -pub enum WatcherCommand { - Start, - ValidateTakerPayment, - WaitForTakerPaymentSpend, - SpendMakerPayment, - Finish, +enum WatcherError { + InvalidValidatePublicKey(String), + InvalidTakerFee(String), + TakerPaymentNotConfirmed(String), + TakerPaymentSearchForSwapFailed(String), + InvalidTakerPayment(String), + UnableToExtractSecret(String), + MakerPaymentSpendFailed(String), + TakerPaymentRefundFailed(String), } -impl Watcher { - #[inline] - fn w(&self) -> RwLockWriteGuard { self.mutable.write().unwrap() } - - #[inline] - fn r(&self) -> RwLockReadGuard { self.mutable.read().unwrap() } - - #[inline] - fn apply_event(&self, event: WatcherEvent) { - match event { - WatcherEvent::Started => (), - WatcherEvent::StartFailed(err) => self.errors.lock().push(err), - WatcherEvent::TakerPaymentWaitConfirmFailed(err) => self.errors.lock().push(err), - WatcherEvent::TakerPaymentValidatedAndConfirmed => (), - WatcherEvent::TakerPaymentValidateFailed(err) => self.errors.lock().push(err), - WatcherEvent::TakerPaymentSpent(data) => { - self.w().taker_payment_spend = Some(data.transaction); - self.w().secret = data.secret; - }, - WatcherEvent::TakerPaymentWaitForSpendFailed(err) => self.errors.lock().push(err), - WatcherEvent::MakerPaymentSpendFailed(err) => self.errors.lock().push(err), - WatcherEvent::MakerPaymentSpent(tx) => self.w().maker_payment_spend = Some(tx), - WatcherEvent::Finished => (), +impl Stopped { + fn from_reason(stop_reason: StopReason) -> Stopped { + Stopped { + _stop_reason: stop_reason, } } +} + +impl TransitionFrom for ValidateTakerFee {} +impl TransitionFrom for ValidateTakerPayment {} +impl TransitionFrom for WaitForTakerPaymentSpend {} +impl TransitionFrom for SpendMakerPayment {} +impl TransitionFrom for RefundTakerPayment {} +impl TransitionFrom for Stopped {} +impl TransitionFrom for Stopped {} +impl TransitionFrom for Stopped {} +impl TransitionFrom for Stopped {} +impl TransitionFrom for Stopped {} +impl TransitionFrom for Stopped {} + +#[async_trait] +impl State for ValidatePublicKeys { + type Ctx = WatcherContext; + type Result = (); + + async fn on_changed(self: Box, watcher_ctx: &mut WatcherContext) -> StateResult { + let redeem_pub_valid = match watcher_ctx + .taker_coin + .check_tx_signed_by_pub(&watcher_ctx.data.taker_payment_hex, &watcher_ctx.verified_pub) + { + Ok(is_valid) => is_valid, + Err(err) => { + return Self::change_state(Stopped::from_reason(StopReason::Error( + WatcherError::InvalidValidatePublicKey(err).into(), + ))) + }, + }; - async fn handle_command( - &self, - command: WatcherCommand, - ) -> Result<(Option, Vec), String> { - match command { - WatcherCommand::Start => self.start().await, - WatcherCommand::ValidateTakerPayment => self.validate_taker_payment().await, - WatcherCommand::WaitForTakerPaymentSpend => self.wait_for_taker_payment_spend().await, - WatcherCommand::SpendMakerPayment => self.spend_maker_payment().await, - WatcherCommand::Finish => Ok((None, vec![WatcherEvent::Finished])), + if !redeem_pub_valid || watcher_ctx.verified_pub != watcher_ctx.data.taker_pub { + return Self::change_state(Stopped::from_reason(StopReason::Error( + WatcherError::InvalidValidatePublicKey("Public key does not belong to taker payment".to_string()) + .into(), + ))); } + + Self::change_state(ValidateTakerFee {}) } +} - pub fn new( - uuid: Uuid, - ctx: MmArc, - maker_coin: MmCoinEnum, - taker_coin: MmCoinEnum, - data: TakerSwapWatcherData, - ) -> Self { - Watcher { - uuid, - ctx, - maker_coin, - taker_coin, - errors: PaMutex::new(Vec::new()), - mutable: RwLock::new(WatcherMut { - taker_payment_spend: None, - maker_payment_spend: None, - secret: H256Json::default(), - }), - data, +#[async_trait] +impl State for ValidateTakerFee { + type Ctx = WatcherContext; + type Result = (); + + async fn on_changed(self: Box, watcher_ctx: &mut WatcherContext) -> StateResult { + let validated_f = watcher_ctx + .taker_coin + .watcher_validate_taker_fee( + watcher_ctx.data.taker_fee_hash.clone(), + watcher_ctx.verified_pub.clone(), + ) + .compat(); + if let Err(err) = validated_f.await { + Self::change_state(Stopped::from_reason(StopReason::Error( + WatcherError::InvalidTakerFee(err.to_string()).into(), + ))); } + Self::change_state(ValidateTakerPayment {}) } +} - async fn start(&self) -> Result<(Option, Vec), String> { - Ok((Some(WatcherCommand::ValidateTakerPayment), vec![WatcherEvent::Started])) - } +// TODO: Do this check periodically while waiting for taker payment spend +#[async_trait] +impl State for ValidateTakerPayment { + type Ctx = WatcherContext; + type Result = (); + + async fn on_changed(self: Box, watcher_ctx: &mut WatcherContext) -> StateResult { + let search_input = WatcherSearchForSwapTxSpendInput { + time_lock: watcher_ctx.data.taker_payment_lock as u32, + taker_pub: &watcher_ctx.data.taker_pub, + maker_pub: &watcher_ctx.data.maker_pub, + secret_hash: &watcher_ctx.data.secret_hash, + tx: &watcher_ctx.data.taker_payment_hex, + search_from_block: watcher_ctx.data.taker_coin_start_block, + swap_contract_address: &None, + }; - // Do we need the exact same validation as the maker, or should we use a simpler validation process? - async fn validate_taker_payment(&self) -> Result<(Option, Vec), String> { - let wait_duration = (self.data.lock_duration * 4) / 5; - let wait_taker_payment = self.data.swap_started_at + wait_duration; - let confirmations = min(self.data.taker_payment_confirmations, TAKER_SWAP_CONFIRMATIONS); + match watcher_ctx + .taker_coin + .watcher_search_for_swap_tx_spend(search_input) + .await + { + Ok(Some(FoundSwapTxSpend::Spent(_))) => { + return Self::change_state(Stopped::from_reason(StopReason::Finished( + WatcherSuccess::TakerPaymentAlreadySpent, + ))) + }, + Ok(Some(FoundSwapTxSpend::Refunded(_))) => { + return Self::change_state(Stopped::from_reason(StopReason::Finished( + WatcherSuccess::TakerPaymentAlreadyRefunded, + ))) + }, + Err(err) => { + return Self::change_state(Stopped::from_reason(StopReason::Error( + WatcherError::TakerPaymentSearchForSwapFailed(err).into(), + ))) + }, + Ok(None) => (), + } + + let wait_taker_payment = + wait_for_taker_payment_conf_until(watcher_ctx.data.swap_started_at, watcher_ctx.data.lock_duration); + let confirmations = min(watcher_ctx.data.taker_payment_confirmations, TAKER_SWAP_CONFIRMATIONS); - // Does the watcher have to wait for the confirmations like the maker does? - let wait_f = self + let wait_f = watcher_ctx .taker_coin .wait_for_confirmations( - &self.data.taker_payment_hex, + &watcher_ctx.data.taker_payment_hex, confirmations, - self.data.taker_payment_requires_nota.unwrap_or(false), + watcher_ctx.data.taker_payment_requires_nota.unwrap_or(false), wait_taker_payment, WAIT_CONFIRM_INTERVAL, ) .compat(); if let Err(err) = wait_f.await { - return Ok((Some(WatcherCommand::Finish), vec![ - WatcherEvent::TakerPaymentWaitConfirmFailed( - ERRL!("!taker_coin.wait_for_confirmations: {}", err).into(), - ), - ])); + Self::change_state(Stopped::from_reason(StopReason::Error( + WatcherError::TakerPaymentNotConfirmed(err).into(), + ))); } let validate_input = WatcherValidatePaymentInput { - payment_tx: self.data.taker_payment_hex.clone(), - time_lock: self.data.taker_payment_lock as u32, - taker_pub: self.data.taker_pub.clone(), - maker_pub: self.data.maker_pub.clone(), - secret_hash: self.data.secret_hash.clone(), - amount: self.data.taker_amount.clone(), + payment_tx: watcher_ctx.data.taker_payment_hex.clone(), + time_lock: watcher_ctx.data.taker_payment_lock as u32, + taker_pub: watcher_ctx.data.taker_pub.clone(), + maker_pub: watcher_ctx.data.maker_pub.clone(), + secret_hash: watcher_ctx.data.secret_hash.clone(), + amount: watcher_ctx.data.taker_amount.clone(), try_spv_proof_until: wait_taker_payment, confirmations, }; - let validated_f = self.taker_coin.watcher_validate_taker_payment(validate_input).compat(); + let validated_f = watcher_ctx + .taker_coin + .watcher_validate_taker_payment(validate_input) + .compat(); - if let Err(e) = validated_f.await { - return Ok((Some(WatcherCommand::Finish), vec![ - WatcherEvent::TakerPaymentValidateFailed( - ERRL!("!taker_coin.watcher_validate_taker_payment: {}", e).into(), - ), - ])); + if let Err(err) = validated_f.await { + Self::change_state(Stopped::from_reason(StopReason::Error( + WatcherError::InvalidTakerPayment(err.to_string()).into(), + ))); } - Ok((Some(WatcherCommand::WaitForTakerPaymentSpend), vec![ - WatcherEvent::TakerPaymentValidatedAndConfirmed, - ])) + Self::change_state(WaitForTakerPaymentSpend {}) } +} + +#[async_trait] +impl State for WaitForTakerPaymentSpend { + type Ctx = WatcherContext; + type Result = (); + + async fn on_changed(self: Box, watcher_ctx: &mut WatcherContext) -> StateResult { + #[cfg(not(test))] + { + // Sleep for half the locktime to allow the taker to spend the maker payment first + let now = now_ms() / 1000; + let wait_for_taker_until = + wait_for_taker_payment_conf_until(watcher_ctx.data.swap_started_at, watcher_ctx.data.lock_duration); + let sleep_duration = (wait_for_taker_until - now + 1) as f64; + + if now < wait_for_taker_until { + Timer::sleep(sleep_duration).await; + } + } - async fn wait_for_taker_payment_spend(&self) -> Result<(Option, Vec), String> { - let f = self.taker_coin.wait_for_htlc_tx_spend( - &self.data.taker_payment_hex[..], + let f = watcher_ctx.taker_coin.wait_for_htlc_tx_spend( + &watcher_ctx.data.taker_payment_hex, &[], - self.data.taker_payment_lock, - self.data.taker_coin_start_block, + watcher_ctx.data.taker_payment_lock, + watcher_ctx.data.taker_coin_start_block, &None, ); let tx = match f.compat().await { Ok(t) => t, Err(err) => { - return Ok((Some(WatcherCommand::Finish), vec![ - WatcherEvent::TakerPaymentWaitForSpendFailed(err.get_plain_text_format().into()), - ])); + error!("{}", err.get_plain_text_format()); + return Self::change_state(RefundTakerPayment {}); }, }; @@ -266,101 +299,143 @@ impl Watcher { tx_hash, }; - let secret = match self + let secret = match watcher_ctx .taker_coin - .extract_secret(&self.data.secret_hash[..], &tx_ident.tx_hex.0) + .extract_secret(&watcher_ctx.data.secret_hash, &tx_ident.tx_hex.0) { Ok(bytes) => H256Json::from(bytes.as_slice()), - Err(e) => { - return Ok((Some(WatcherCommand::Finish), vec![ - WatcherEvent::TakerPaymentWaitForSpendFailed(ERRL!("{}", e).into()), - ])); + Err(err) => { + return Self::change_state(Stopped::from_reason(StopReason::Error( + WatcherError::UnableToExtractSecret(err).into(), + ))) }, }; - Ok((Some(WatcherCommand::SpendMakerPayment), vec![ - WatcherEvent::TakerPaymentSpent(TakerPaymentSpentData { - transaction: tx_ident, - secret, - }), - ])) + Self::change_state(SpendMakerPayment::new(secret)) } +} - async fn spend_maker_payment(&self) -> Result<(Option, Vec), String> { - let spend_fut = self.maker_coin.send_taker_spends_maker_payment_preimage( - &self.data.taker_spends_maker_payment_preimage, - &self.r().secret.0.clone(), +#[async_trait] +impl State for SpendMakerPayment { + type Ctx = WatcherContext; + type Result = (); + + async fn on_changed(self: Box, watcher_ctx: &mut WatcherContext) -> StateResult { + let spend_fut = watcher_ctx.maker_coin.send_taker_spends_maker_payment_preimage( + &watcher_ctx.data.taker_spends_maker_payment_preimage, + &self.secret.0, ); let transaction = match spend_fut.compat().await { Ok(t) => t, Err(err) => { if let Some(tx) = err.get_tx() { - broadcast_p2p_tx_msg(&self.ctx, tx_helper_topic(self.maker_coin.ticker()), &tx, &None); + broadcast_p2p_tx_msg( + &watcher_ctx.ctx, + tx_helper_topic(watcher_ctx.maker_coin.ticker()), + &tx, + &None, + ); }; - - return Ok((Some(WatcherCommand::Finish), vec![ - WatcherEvent::MakerPaymentSpendFailed(ERRL!("{}", err.get_plain_text_format()).into()), - ])); + return Self::change_state(Stopped::from_reason(StopReason::Error( + WatcherError::MakerPaymentSpendFailed(err.get_plain_text_format()).into(), + ))); }, }; broadcast_p2p_tx_msg( - &self.ctx, - tx_helper_topic(self.maker_coin.ticker()), + &watcher_ctx.ctx, + tx_helper_topic(watcher_ctx.maker_coin.ticker()), &transaction, &None, ); let tx_hash = transaction.tx_hash(); - info!("Maker payment spend tx {:02x}", tx_hash); - let tx_ident = TransactionIdentifier { - tx_hex: transaction.tx_hex().into(), - tx_hash, - }; + info!("Sent maker payment spend tx {:02x} as watcher", tx_hash); - Ok((Some(WatcherCommand::Finish), vec![WatcherEvent::MakerPaymentSpent( - tx_ident, - )])) + Self::change_state(Stopped::from_reason(StopReason::Finished( + WatcherSuccess::MakerPaymentSpent, + ))) } } -pub async fn run_watcher(swap: RunWatcherInput) { - let (swap, mut command) = match swap { - RunWatcherInput::StartNew(swap) => (swap, WatcherCommand::Start), - }; +#[async_trait] +impl State for RefundTakerPayment { + type Ctx = WatcherContext; + type Result = (); + + async fn on_changed(self: Box, watcher_ctx: &mut WatcherContext) -> StateResult { + let locktime = watcher_ctx.data.taker_payment_lock; + loop { + match watcher_ctx + .taker_coin + .can_refund_htlc(locktime + WAIT_FOR_TAKER_REFUND) + .compat() + .await + { + Ok(CanRefundHtlc::CanRefundNow) => break, + Ok(CanRefundHtlc::HaveToWait(to_sleep)) => Timer::sleep(to_sleep as f64).await, + Err(e) => { + error!("Error {} on can_refund_htlc, retrying in 30 seconds", e); + Timer::sleep(30.).await; + }, + } + } - let ctx = swap.ctx.clone(); - let mut status = ctx.log.status_handle(); - let uuid_str = swap.uuid.to_string(); - let running_swap = Arc::new(swap); - - let mut swap_fut = Box::pin( - async move { - let mut events; - loop { - let res = running_swap.handle_command(command).await.expect("!handle_command"); - events = res.1; - for event in events { - status.status(&[&"swap", &("uuid", uuid_str.as_str())], &event.status_str()); - running_swap.apply_event(event); - } - match res.0 { - Some(c) => { - command = c; - }, - None => { - break; - }, + let refund_fut = watcher_ctx + .taker_coin + .send_watcher_refunds_taker_payment_preimage(&watcher_ctx.data.taker_refunds_payment); + let transaction = match refund_fut.compat().await { + Ok(t) => t, + Err(err) => { + if let Some(tx) = err.get_tx() { + broadcast_p2p_tx_msg( + &watcher_ctx.ctx, + tx_helper_topic(watcher_ctx.taker_coin.ticker()), + &tx, + &None, + ); } - } + + return Self::change_state(Stopped::from_reason(StopReason::Error( + WatcherError::TakerPaymentRefundFailed(err.get_plain_text_format()).into(), + ))); + }, + }; + + broadcast_p2p_tx_msg( + &watcher_ctx.ctx, + tx_helper_topic(watcher_ctx.taker_coin.ticker()), + &transaction, + &None, + ); + + let wait_fut = watcher_ctx.taker_coin.wait_for_confirmations( + &transaction.tx_hex(), + 1, + false, + watcher_ctx.data.taker_payment_lock + WAIT_FOR_TAKER_REFUND + 3600, + WAIT_CONFIRM_INTERVAL, + ); + if let Err(err) = wait_fut.compat().await { + return Self::change_state(Stopped::from_reason(StopReason::Error( + WatcherError::TakerPaymentRefundFailed(err).into(), + ))); } - .fuse(), - ); - let do_nothing = (); // to fix https://rust-lang.github.io/rust-clippy/master/index.html#unused_unit - select! { - _swap = swap_fut => do_nothing, // swap finished normally - }; + + let tx_hash = transaction.tx_hash(); + info!("Sent taker refund tx {:02x} as watcher", tx_hash); + Self::change_state(Stopped::from_reason(StopReason::Finished( + WatcherSuccess::TakerPaymentRefunded, + ))) + } +} + +#[async_trait] +impl LastState for Stopped { + type Ctx = WatcherContext; + type Result = (); + async fn on_changed(self: Box, _watcher_ctx: &mut Self::Ctx) -> Self::Result {} } pub async fn process_watcher_msg(ctx: MmArc, msg: &[u8]) { @@ -374,8 +449,12 @@ pub async fn process_watcher_msg(ctx: MmArc, msg: &[u8]) { }, }; - match msg.0 { - SwapWatcherMsg::TakerSwapWatcherMsg(watcher_data) => spawn_taker_swap_watcher(ctx, *watcher_data), + let watcher_data = msg.0; + let verified_pubkey = msg.2; + match watcher_data { + SwapWatcherMsg::TakerSwapWatcherMsg(watcher_data) => { + spawn_taker_swap_watcher(ctx, watcher_data, verified_pubkey.to_bytes()) + }, } } @@ -384,32 +463,32 @@ enum WatcherType { Taker, } -/// The `SwapWatcherLock` is used to lock the given `uuid` as the running Swap Watcher, -/// (i.e. insert `uuid` into either [`SwapsContext::taker_swap_watchers`] or [`SwapsContext::maker_swap_watchers`]), -/// and to unlock it (i.e remove `uuid` from corresponding watcher collection) once `SwapWatcherLock` is dropped. +/// The `SwapWatcherLock` is used to lock the given taker fee hash as the running Swap Watcher, +/// (i.e. insert the fee hash into either [`SwapsContext::taker_swap_watchers`] or [`SwapsContext::maker_swap_watchers`]), +/// and to unlock it (i.e remove the hash from corresponding watcher collection) once `SwapWatcherLock` is dropped. struct SwapWatcherLock { swap_ctx: Arc, - uuid: Uuid, + fee_hash: Vec, watcher_type: WatcherType, } impl SwapWatcherLock { - /// Locks the given `uuid` as the running Swap Watcher, - /// so inserts `uuid` into the [`SwapsContext::taker_swap_watchers`] collection. + /// Locks the given taker fee hash as the running Swap Watcher, + /// so inserts the hash into the [`SwapsContext::taker_swap_watchers`] collection. /// /// Returns `None` if there is an ongoing Taker Swap Watcher already. - fn lock_taker(swap_ctx: Arc, uuid: Uuid) -> Option { + fn lock_taker(swap_ctx: Arc, fee_hash: Vec) -> Option { { let mut guard = swap_ctx.taker_swap_watchers.lock(); - if !guard.insert(uuid) { - // There is the same `uuid` already. + if !guard.insert(fee_hash.clone()) { + // There is the same hash already. return None; } } Some(SwapWatcherLock { swap_ctx, - uuid, + fee_hash, watcher_type: WatcherType::Taker, }) } @@ -418,25 +497,24 @@ impl SwapWatcherLock { impl Drop for SwapWatcherLock { fn drop(&mut self) { match self.watcher_type { - WatcherType::Taker => self.swap_ctx.taker_swap_watchers.lock().remove(&self.uuid), + WatcherType::Taker => self.swap_ctx.taker_swap_watchers.lock().remove(self.fee_hash.clone()), }; } } -fn spawn_taker_swap_watcher(ctx: MmArc, watcher_data: TakerSwapWatcherData) { +fn spawn_taker_swap_watcher(ctx: MmArc, watcher_data: TakerSwapWatcherData, verified_pub: Vec) { let swap_ctx = SwapsContext::from_ctx(&ctx).unwrap(); if swap_ctx.swap_msgs.lock().unwrap().contains_key(&watcher_data.uuid) { return; } - - let uuid = watcher_data.uuid; - let taker_watcher_lock = match SwapWatcherLock::lock_taker(swap_ctx, uuid) { + let taker_watcher_lock = match SwapWatcherLock::lock_taker(swap_ctx, watcher_data.taker_fee_hash.clone()) { Some(lock) => lock, // There is an ongoing Taker Swap Watcher already. None => return, }; let spawner = ctx.spawner(); + let fee_hash = H256Json::from(watcher_data.taker_fee_hash.as_slice()); let fut = async move { let taker_coin = match lp_coinfind(&ctx, &watcher_data.taker_coin).await { @@ -466,21 +544,28 @@ fn spawn_taker_swap_watcher(ctx: MmArc, watcher_data: TakerSwapWatcherData) { log_tag!( ctx, ""; - fmt = "Entering the watcher_swap_loop {}/{} with uuid: {}", + fmt = "Entering the taker swap watcher loop {}/{} with taker fee hash: {}", maker_coin.ticker(), taker_coin.ticker(), - uuid + fee_hash ); - let watcher = Watcher::new(watcher_data.uuid, ctx, maker_coin, taker_coin, watcher_data); - run_watcher(RunWatcherInput::StartNew(watcher)).await; + let watcher_ctx = WatcherContext { + ctx, + maker_coin, + taker_coin, + data: watcher_data, + verified_pub, + }; + let state_machine: StateMachine<_, ()> = StateMachine::from_ctx(watcher_ctx); + state_machine.run(ValidatePublicKeys {}).await; // This allows to move the `taker_watcher_lock` value into this async block to keep it alive // until the Swap Watcher finishes. drop(taker_watcher_lock); }; - let settings = AbortSettings::info_on_abort(format!("watcher swap {uuid} stopped!")); + let settings = AbortSettings::info_on_abort(format!("taker swap watcher {fee_hash} stopped!")); // Please note that `taker_watcher_lock` will be dropped once `MmCtx` is stopped // since this `fut` will be aborted. spawner.spawn_with_settings(fut, settings); diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap.rs b/mm2src/mm2_main/src/lp_swap/taker_swap.rs index 4b0651eb27..21cab46e04 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap.rs @@ -6,7 +6,8 @@ use super::swap_watcher::{watcher_topic, SwapWatcherMsg}; use super::trade_preimage::{TradePreimageRequest, TradePreimageRpcError, TradePreimageRpcResult}; use super::{broadcast_my_swap_status, broadcast_swap_message, broadcast_swap_message_every, check_other_coin_balance_for_swap, dex_fee_amount_from_taker_coin, dex_fee_rate, dex_fee_threshold, - get_locked_amount, recv_swap_msg, swap_topic, AtomicSwap, LockedAmount, MySwapInfo, NegotiationDataMsg, + get_locked_amount, recv_swap_msg, swap_topic, wait_for_maker_payment_conf_until, + wait_for_taker_payment_conf_until, AtomicSwap, LockedAmount, MySwapInfo, NegotiationDataMsg, NegotiationDataV2, NegotiationDataV3, RecoveredSwap, RecoveredSwapAction, SavedSwap, SavedSwapIo, SavedTradeFee, SwapConfirmationsSettings, SwapError, SwapMsg, SwapsContext, TransactionIdentifier, WAIT_CONFIRM_INTERVAL}; @@ -21,7 +22,7 @@ use common::executor::Timer; use common::log::{debug, error, info, warn}; use common::{bits256, now_ms, DEX_FEE_ADDR_RAW_PUBKEY}; use crypto::privkey::SerializableSecp256k1Keypair; -use futures::{compat::Future01CompatExt, select, FutureExt}; +use futures::{compat::Future01CompatExt, future::try_join, select, FutureExt}; use http::Response; use keys::KeyPair; use mm2_core::mm_ctx::MmArc; @@ -147,7 +148,7 @@ impl TakerSavedEvent { TakerSwapEvent::MakerPaymentValidateFailed(_) => Some(TakerSwapCommand::Finish), TakerSwapEvent::MakerPaymentWaitConfirmFailed(_) => Some(TakerSwapCommand::Finish), TakerSwapEvent::TakerPaymentSent(_) => Some(TakerSwapCommand::WaitForTakerPaymentSpend), - TakerSwapEvent::WatcherMessageSent(_) => Some(TakerSwapCommand::WaitForTakerPaymentSpend), + TakerSwapEvent::WatcherMessageSent(_, _) => Some(TakerSwapCommand::WaitForTakerPaymentSpend), TakerSwapEvent::TakerPaymentTransactionFailed(_) => Some(TakerSwapCommand::Finish), TakerSwapEvent::TakerPaymentDataSendFailed(_) => Some(TakerSwapCommand::RefundTakerPayment), TakerSwapEvent::TakerPaymentSpent(_) => Some(TakerSwapCommand::SpendMakerPayment), @@ -484,6 +485,7 @@ pub struct TakerSwapMut { maker_payment_spend: Option, taker_payment_spend: Option, taker_spends_maker_payment_preimage: Option>, + taker_refunds_payment: Option>, taker_payment_refund: Option, secret_hash: BytesJson, secret: H256Json, @@ -570,7 +572,7 @@ pub enum TakerSwapEvent { MakerPaymentValidateFailed(SwapError), MakerPaymentWaitConfirmFailed(SwapError), TakerPaymentSent(TransactionIdentifier), - WatcherMessageSent(Option>), + WatcherMessageSent(Option>, Option>), TakerPaymentTransactionFailed(SwapError), TakerPaymentDataSendFailed(SwapError), TakerPaymentWaitConfirmFailed(SwapError), @@ -601,7 +603,7 @@ impl TakerSwapEvent { "Maker payment wait for confirmation failed...".to_owned() }, TakerSwapEvent::TakerPaymentSent(_) => "Taker payment sent...".to_owned(), - TakerSwapEvent::WatcherMessageSent(_) => "Watcher message sent...".to_owned(), + TakerSwapEvent::WatcherMessageSent(_, _) => "Watcher message sent...".to_owned(), TakerSwapEvent::TakerPaymentTransactionFailed(_) => "Taker payment transaction failed...".to_owned(), TakerSwapEvent::TakerPaymentDataSendFailed(_) => "Taker payment data send failed...".to_owned(), TakerSwapEvent::TakerPaymentWaitConfirmFailed(_) => { @@ -635,7 +637,7 @@ impl TakerSwapEvent { | TakerSwapEvent::TakerFeeSent(_) | TakerSwapEvent::MakerPaymentReceived(_) | TakerSwapEvent::MakerPaymentWaitConfirmStarted - | TakerSwapEvent::WatcherMessageSent(_) + | TakerSwapEvent::WatcherMessageSent(_, _) | TakerSwapEvent::MakerPaymentValidatedAndConfirmed | TakerSwapEvent::TakerPaymentSent(_) | TakerSwapEvent::TakerPaymentSpent(_) @@ -719,8 +721,9 @@ impl TakerSwap { TakerSwapEvent::MakerPaymentValidateFailed(err) => self.errors.lock().push(err), TakerSwapEvent::MakerPaymentWaitConfirmFailed(err) => self.errors.lock().push(err), TakerSwapEvent::TakerPaymentSent(tx) => self.w().taker_payment = Some(tx), - TakerSwapEvent::WatcherMessageSent(preimage_hex) => { - self.w().taker_spends_maker_payment_preimage = preimage_hex + TakerSwapEvent::WatcherMessageSent(taker_spends_maker_payment, taker_refunds_payment) => { + self.w().taker_spends_maker_payment_preimage = taker_spends_maker_payment; + self.w().taker_refunds_payment = taker_refunds_payment; }, TakerSwapEvent::TakerPaymentTransactionFailed(err) => self.errors.lock().push(err), TakerSwapEvent::TakerPaymentDataSendFailed(err) => self.errors.lock().push(err), @@ -797,6 +800,7 @@ impl TakerSwap { taker_payment: None, taker_payment_spend: None, taker_spends_maker_payment_preimage: None, + taker_refunds_payment: None, maker_payment_spend: None, taker_payment_refund: None, secret_hash: BytesJson::default(), @@ -936,7 +940,7 @@ impl TakerSwap { taker_payment_lock: started_at + self.payment_locktime, my_persistent_pub: self.my_persistent_pub.into(), uuid: self.uuid, - maker_payment_wait: maker_payment_wait(started_at, self.payment_locktime), + maker_payment_wait: wait_for_maker_payment_conf_until(started_at, self.payment_locktime), maker_coin_start_block, taker_coin_start_block, fee_to_send_taker_fee: Some(SavedTradeFee::from(fee_to_send_dex_fee)), @@ -1230,14 +1234,17 @@ impl TakerSwap { &self, taker_payment_hex: Vec, taker_spends_maker_payment_preimage: Vec, + taker_refunds_payment: Vec, ) -> TakerSwapWatcherData { TakerSwapWatcherData { uuid: self.uuid, secret_hash: self.r().secret_hash.clone().into(), taker_spends_maker_payment_preimage, + taker_refunds_payment, swap_started_at: self.r().data.started_at, lock_duration: self.r().data.lock_duration, taker_coin: self.r().data.taker_coin.clone(), + taker_fee_hash: self.r().taker_fee.as_ref().unwrap().tx_hash.0.clone(), taker_payment_hex, taker_payment_lock: self.r().data.taker_payment_lock, taker_pub: self.r().data.taker_coin_htlc_pubkey.unwrap().into(), @@ -1318,28 +1325,49 @@ impl TakerSwap { let mut swap_events = vec![TakerSwapEvent::TakerPaymentSent(tx_ident)]; if self.ctx.use_watchers() { - let preimage_fut = self.taker_coin.create_taker_spends_maker_payment_preimage( + let taker_spends_maker_payment_fut = self.taker_coin.create_taker_spends_maker_payment_preimage( &self.r().maker_payment.as_ref().unwrap().tx_hex, self.maker_payment_lock.load(Ordering::Relaxed) as u32, self.r().other_maker_coin_htlc_pub.as_slice(), &self.r().secret_hash.0, &self.unique_swap_data()[..], ); + let taker_refunds_payment_fut = self.taker_coin.create_taker_refunds_payment_preimage( + &transaction.tx_hex(), + self.r().data.taker_payment_lock as u32, + &*self.r().other_taker_coin_htlc_pub, + &self.r().secret_hash.0, + &self.r().data.taker_coin_swap_contract_address, + &self.unique_swap_data(), + ); + let payment_fut_pair = try_join( + taker_spends_maker_payment_fut.compat(), + taker_refunds_payment_fut.compat(), + ); + + match payment_fut_pair.await { + Ok((taker_spends_maker_payment, taker_refunds_payment)) => { + let watcher_data = self.create_watcher_data( + transaction.tx_hex(), + taker_spends_maker_payment.tx_hex(), + taker_refunds_payment.tx_hex(), + ); + let swpmsg_watcher = SwapWatcherMsg::TakerSwapWatcherMsg(watcher_data); - match preimage_fut.compat().await { - Ok(preimage) => { - let watcher_data = self.create_watcher_data(transaction.tx_hex(), preimage.tx_hex()); - let swpmsg_watcher = SwapWatcherMsg::TakerSwapWatcherMsg(Box::new(watcher_data)); broadcast_swap_message( &self.ctx, watcher_topic(&self.r().data.taker_coin), swpmsg_watcher, &self.p2p_privkey, ); - swap_events.push(TakerSwapEvent::WatcherMessageSent(Some(preimage.tx_hex()))) + + swap_events.push(TakerSwapEvent::WatcherMessageSent( + Some(taker_spends_maker_payment.tx_hex()), + Some(taker_refunds_payment.tx_hex()), + )); }, Err(e) => error!( - "The watcher message could not be sent, error creating the taker spends maker payment preimage: {}", + "The watcher message could not be sent, error creating the taker refunds payment: {}", e.get_plain_text_format() ), } @@ -1349,28 +1377,37 @@ impl TakerSwap { } async fn wait_for_taker_payment_spend(&self) -> Result<(Option, Vec), String> { + const WAIT_FOR_TAKER_PAYMENT_INTERVAL: f64 = 600.; let tx_hex = self.r().taker_payment.as_ref().unwrap().tx_hex.0.clone(); let mut watcher_broadcast_abort_handle = None; if self.ctx.use_watchers() { - let preimage_hex = self.r().taker_spends_maker_payment_preimage.clone(); - if let Some(preimage_hex) = preimage_hex { - let watcher_data = self.create_watcher_data(tx_hex.clone(), preimage_hex); - let swpmsg_watcher = SwapWatcherMsg::TakerSwapWatcherMsg(Box::new(watcher_data)); + if let (Some(taker_spends_maker_payment), Some(taker_refunds_payment)) = ( + self.r().taker_spends_maker_payment_preimage.clone(), + self.r().taker_refunds_payment.clone(), + ) { + let watcher_data = + self.create_watcher_data(tx_hex.clone(), taker_spends_maker_payment, taker_refunds_payment); + let swpmsg_watcher = SwapWatcherMsg::TakerSwapWatcherMsg(watcher_data); watcher_broadcast_abort_handle = Some(broadcast_swap_message_every( self.ctx.clone(), watcher_topic(&self.r().data.taker_coin), swpmsg_watcher, - 600., + WAIT_FOR_TAKER_PAYMENT_INTERVAL, self.p2p_privkey, )); } } let msg = SwapMsg::TakerPayment(tx_hex); - let send_abort_handle = - broadcast_swap_message_every(self.ctx.clone(), swap_topic(&self.uuid), msg, 600., self.p2p_privkey); + let send_abort_handle = broadcast_swap_message_every( + self.ctx.clone(), + swap_topic(&self.uuid), + msg, + WAIT_FOR_TAKER_PAYMENT_INTERVAL, + self.p2p_privkey, + ); - let wait_duration = (self.r().data.lock_duration * 4) / 5; - let wait_taker_payment = self.r().data.started_at + wait_duration; + let wait_taker_payment = + wait_for_taker_payment_conf_until(self.r().data.started_at, self.r().data.lock_duration); let wait_f = self .taker_coin .wait_for_confirmations( @@ -2230,10 +2267,6 @@ pub fn max_taker_vol_from_available( Ok(max_vol) } -pub fn maker_payment_wait(swap_started_at: u64, payment_locktime: u64) -> u64 { - swap_started_at + (payment_locktime * 2) / 5 -} - #[cfg(all(test, not(target_arch = "wasm32")))] mod taker_swap_tests { use super::*;