diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index b8db8c7c12..0dbae158cb 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -162,7 +162,7 @@ pub mod erc20; use erc20::get_token_decimals; pub(crate) mod eth_swap_v2; -use eth_swap_v2::{EthPaymentType, PaymentMethod}; +use eth_swap_v2::{extract_id_from_tx_data, EthPaymentType, PaymentMethod, SpendTxSearchParams}; /// https://github.com/artemii235/etomic-swap/blob/master/contracts/EtomicSwap.sol /// Dev chain (195.201.137.5:8565) contract address: 0x83965C539899cC0F918552e5A26915de40ee8852 @@ -2587,73 +2587,27 @@ impl MarketCoinOps for EthCoin { }, }; - let payment_func = try_tx_s!(SWAP_CONTRACT.function(&func_name)); - let decoded = try_tx_s!(decode_contract_call(payment_func, tx.unsigned().data())); - let id = match decoded.first() { - Some(Token::FixedBytes(bytes)) => bytes.clone(), - invalid_token => { - return Err(TransactionErr::Plain(ERRL!( - "Expected Token::FixedBytes, got {:?}", - invalid_token - ))) - }, - }; - - loop { - if now_sec() > args.wait_until { - return TX_PLAIN_ERR!( - "Waited too long until {} for transaction {:?} to be spent ", - args.wait_until, - tx, - ); - } + let id = try_tx_s!(extract_id_from_tx_data(tx.unsigned().data(), &SWAP_CONTRACT, &func_name).await); - let current_block = match self.current_block().compat().await { - Ok(b) => b, - Err(e) => { - error!("Error getting block number: {}", e); - Timer::sleep(5.).await; - continue; - }, - }; - - let events = match self - .spend_events(swap_contract_address, args.from_block, current_block) - .compat() - .await - { - Ok(ev) => ev, - Err(e) => { - error!("Error getting spend events: {}", e); - Timer::sleep(5.).await; - continue; - }, - }; - - let found = events.iter().find(|event| &event.data.0[..32] == id.as_slice()); - - if let Some(event) = found { - if let Some(tx_hash) = event.transaction_hash { - let transaction = match self.transaction(TransactionId::Hash(tx_hash)).await { - Ok(Some(t)) => t, - Ok(None) => { - info!("Tx {} not found yet", tx_hash); - Timer::sleep(args.check_every).await; - continue; - }, - Err(e) => { - error!("Get tx {} error: {}", tx_hash, e); - Timer::sleep(args.check_every).await; - continue; - }, - }; - - return Ok(TransactionEnum::from(try_tx_s!(signed_tx_from_web3_tx(transaction)))); - } - } + let find_params = SpendTxSearchParams { + swap_contract_address, + event_name: "ReceiverSpent", + abi_contract: &SWAP_CONTRACT, + swap_id: &try_tx_s!(id.as_slice().try_into()), + from_block: args.from_block, + wait_until: args.wait_until, + check_every: args.check_every, + }; + let tx_hash = self + .find_transaction_hash_by_event(find_params) + .await + .map_err(|e| TransactionErr::Plain(e.get_inner().to_string()))?; - Timer::sleep(5.).await; - } + let spend_tx = self + .wait_for_spend_transaction(tx_hash, args.wait_until, args.check_every) + .await + .map_err(|e| TransactionErr::Plain(e.get_inner().to_string()))?; + Ok(TransactionEnum::from(spend_tx)) } fn tx_enum_from_bytes(&self, bytes: &[u8]) -> Result> { @@ -4537,7 +4491,9 @@ impl EthCoin { let function = ERC20_CONTRACT.function("balanceOf")?; let data = function.encode_input(&[Token::Address(address)])?; - let res = coin.call_request(address, *token_addr, None, Some(data.into())).await?; + let res = coin + .call_request(address, *token_addr, None, Some(data.into()), BlockNumber::Latest) + .await?; let decoded = function.decode_output(&res.0)?; match decoded[0] { Token::Uint(number) => Ok(number), @@ -4601,7 +4557,7 @@ impl EthCoin { let function = ERC20_CONTRACT.function("balanceOf")?; let data = function.encode_input(&[Token::Address(address)])?; let res = self - .call_request(address, token_address, None, Some(data.into())) + .call_request(address, token_address, None, Some(data.into()), BlockNumber::Latest) .await?; let decoded = function.decode_output(&res.0)?; @@ -4628,7 +4584,7 @@ impl EthCoin { let my_address = self.derivation_method.single_addr_or_err().await?; let data = function.encode_input(&[Token::Address(my_address), Token::Uint(token_id_u256)])?; let result = self - .call_request(my_address, token_addr, None, Some(data.into())) + .call_request(my_address, token_addr, None, Some(data.into()), BlockNumber::Latest) .await?; let decoded = function.decode_output(&result.0)?; match decoded[0] { @@ -4659,7 +4615,7 @@ impl EthCoin { let data = function.encode_input(&[Token::Uint(token_id_u256)])?; let my_address = self.derivation_method.single_addr_or_err().await?; let result = self - .call_request(my_address, token_addr, None, Some(data.into())) + .call_request(my_address, token_addr, None, Some(data.into()), BlockNumber::Latest) .await?; let decoded = function.decode_output(&result.0)?; match decoded[0] { @@ -4737,6 +4693,7 @@ impl EthCoin { to: Address, value: Option, data: Option, + block_number: BlockNumber, ) -> Result { let request = CallRequest { from: Some(from), @@ -4748,7 +4705,7 @@ impl EthCoin { ..CallRequest::default() }; - self.call(request, Some(BlockId::Number(BlockNumber::Latest))).await + self.call(request, Some(BlockId::Number(block_number))).await } pub fn allowance(&self, spender: Address) -> Web3RpcFut { @@ -4764,7 +4721,7 @@ impl EthCoin { let data = function.encode_input(&[Token::Address(my_address), Token::Address(spender)])?; let res = coin - .call_request(my_address, *token_addr, None, Some(data.into())) + .call_request(my_address, *token_addr, None, Some(data.into()), BlockNumber::Latest) .await?; let decoded = function.decode_output(&res.0)?; @@ -4864,25 +4821,30 @@ impl EthCoin { Box::new(fut.boxed().compat()) } - /// Gets `ReceiverSpent` events from etomic swap smart contract since `from_block` - fn spend_events( + /// Returns events from `from_block` to `to_block` or current `latest` block. + /// According to ["eth_getLogs" doc](https://docs.infura.io/api/networks/ethereum/json-rpc-methods/eth_getlogs) `toBlock` is optional, default is "latest". + async fn events_from_block( &self, swap_contract_address: Address, + event_name: &str, from_block: u64, - to_block: u64, - ) -> Box, Error = String> + Send> { - let contract_event = try_fus!(SWAP_CONTRACT.event("ReceiverSpent")); - let filter = FilterBuilder::default() + to_block: Option, + swap_contract: &Contract, + ) -> MmResult, FindPaymentSpendError> { + let contract_event = swap_contract.event(event_name)?; + let mut filter_builder = FilterBuilder::default() .topics(Some(vec![contract_event.signature()]), None, None, None) .from_block(BlockNumber::Number(from_block.into())) - .to_block(BlockNumber::Number(to_block.into())) - .address(vec![swap_contract_address]) - .build(); - - let coin = self.clone(); - - let fut = async move { coin.logs(filter).await.map_err(|e| ERRL!("{}", e)) }; - Box::new(fut.boxed().compat()) + .address(vec![swap_contract_address]); + if let Some(block) = to_block { + filter_builder = filter_builder.to_block(BlockNumber::Number(block.into())); + } + let filter = filter_builder.build(); + let events_logs = self + .logs(filter) + .await + .map_err(|e| FindPaymentSpendError::Transport(e.to_string()))?; + Ok(events_logs) } fn validate_payment(&self, input: ValidatePaymentInput) -> ValidatePaymentFut<()> { @@ -5192,9 +5154,16 @@ impl EthCoin { .single_addr_or_err() .await .map_err(|e| ERRL!("{}", e))?; - coin.call_request(my_address, swap_contract_address, None, Some(data.into())) - .await - .map_err(|e| ERRL!("{}", e)) + coin.call_request( + my_address, + swap_contract_address, + None, + Some(data.into()), + // TODO worth reviewing places where we could use BlockNumber::Pending + BlockNumber::Latest, + ) + .await + .map_err(|e| ERRL!("{}", e)) }; Box::new(fut.boxed().compat().and_then(move |bytes| { @@ -5244,10 +5213,16 @@ impl EthCoin { let to_block = current_block.min(from_block + self.logs_block_range); let spend_events = try_s!( - self.spend_events(swap_contract_address, from_block, to_block) - .compat() - .await + self.events_from_block( + swap_contract_address, + "ReceiverSpent", + from_block, + Some(to_block), + &SWAP_CONTRACT + ) + .await ); + let found = spend_events.iter().find(|event| &event.data.0[..32] == id.as_slice()); if let Some(event) = found { @@ -7006,6 +6981,7 @@ impl ParseCoinAssocTypes for EthCoin { } fn parse_address(&self, address: &str) -> Result { + // crate `Address::from_str` supports both address variants with and without `0x` prefix Address::from_str(address).map_to_mm(|e| EthAssocTypesError::InvalidHexString(e.to_string())) } @@ -7038,6 +7014,10 @@ impl ToBytes for Address { fn to_bytes(&self) -> Vec { self.0.to_vec() } } +impl AddrToString for Address { + fn addr_to_string(&self) -> String { eth_addr_to_hex(self) } +} + impl ToBytes for BigUint { fn to_bytes(&self) -> Vec { self.to_bytes_be() } } @@ -7347,14 +7327,20 @@ impl TakerCoinSwapOpsV2 for EthCoin { self.sign_and_broadcast_taker_payment_spend_impl(gen_args, secret).await } - /// Wrapper for [EthCoin::wait_for_taker_payment_spend_impl] - async fn wait_for_taker_payment_spend( + /// Wrapper for [EthCoin::find_taker_payment_spend_tx_impl] + async fn find_taker_payment_spend_tx( &self, taker_payment: &Self::Tx, - _from_block: u64, + from_block: u64, wait_until: u64, - ) -> MmResult { - self.wait_for_taker_payment_spend_impl(taker_payment, wait_until).await + ) -> MmResult { + const CHECK_EVERY: f64 = 10.; + self.find_taker_payment_spend_tx_impl(taker_payment, from_block, wait_until, CHECK_EVERY) + .await + } + + async fn extract_secret_v2(&self, _secret_hash: &[u8], spend_tx: &Self::Tx) -> Result<[u8; 32], String> { + self.extract_secret_v2_impl(spend_tx).await } } diff --git a/mm2src/coins/eth/eth_swap_v2/eth_maker_swap_v2.rs b/mm2src/coins/eth/eth_swap_v2/eth_maker_swap_v2.rs index 3089604ede..c5e97359e7 100644 --- a/mm2src/coins/eth/eth_swap_v2/eth_maker_swap_v2.rs +++ b/mm2src/coins/eth/eth_swap_v2/eth_maker_swap_v2.rs @@ -13,7 +13,7 @@ use futures::compat::Future01CompatExt; use mm2_err_handle::mm_error::MmError; use mm2_err_handle::prelude::MapToMmResult; use std::convert::TryInto; -use web3::types::TransactionId; +use web3::types::{BlockNumber, TransactionId}; const ETH_MAKER_PAYMENT: &str = "ethMakerPayment"; const ERC20_MAKER_PAYMENT: &str = "erc20MakerPayment"; @@ -27,10 +27,10 @@ const ERC20_MAKER_PAYMENT: &str = "erc20MakerPayment"; /// } const MAKER_PAYMENT_STATE_INDEX: usize = 2; -struct MakerPaymentArgs { +struct MakerPaymentArgs<'a> { taker_address: Address, - taker_secret_hash: [u8; 32], - maker_secret_hash: [u8; 32], + taker_secret_hash: &'a [u8; 32], + maker_secret_hash: &'a [u8; 32], payment_time_lock: u64, } @@ -43,20 +43,20 @@ struct MakerValidationArgs<'a> { payment_time_lock: u64, } -struct MakerRefundTimelockArgs { +struct MakerRefundTimelockArgs<'a> { payment_amount: U256, taker_address: Address, - taker_secret_hash: [u8; 32], - maker_secret_hash: [u8; 32], + taker_secret_hash: &'a [u8; 32], + maker_secret_hash: &'a [u8; 32], payment_time_lock: u64, token_address: Address, } -struct MakerRefundSecretArgs { +struct MakerRefundSecretArgs<'a> { payment_amount: U256, taker_address: Address, - taker_secret: [u8; 32], - maker_secret_hash: [u8; 32], + taker_secret: &'a [u8; 32], + maker_secret_hash: &'a [u8; 32], payment_time_lock: u64, token_address: Address, } @@ -143,6 +143,7 @@ impl EthCoin { &MAKER_SWAP_V2, EthPaymentType::MakerPayments, MAKER_PAYMENT_STATE_INDEX, + BlockNumber::Latest, ) .await?; @@ -271,7 +272,6 @@ impl EthCoin { ) .map_err(|e| TransactionErr::Plain(ERRL!("{}", e)))?; - let taker_secret = try_tx_s!(args.taker_secret.try_into()); let maker_secret_hash = try_tx_s!(args.maker_secret_hash.try_into()); let payment_amount = try_tx_s!(wei_from_big_decimal(&args.amount, self.decimals)); let args = { @@ -279,7 +279,7 @@ impl EthCoin { MakerRefundSecretArgs { payment_amount, taker_address, - taker_secret, + taker_secret: args.taker_secret, maker_secret_hash, payment_time_lock: args.time_lock, token_address, @@ -326,9 +326,9 @@ impl EthCoin { } /// Prepares data for EtomicSwapMakerV2 contract [ethMakerPayment](https://github.com/KomodoPlatform/etomic-swap/blob/5e15641cbf41766cd5b37b4d71842c270773f788/contracts/EtomicSwapMakerV2.sol#L30) method - async fn prepare_maker_eth_payment_data(&self, args: &MakerPaymentArgs) -> Result, PrepareTxDataError> { + async fn prepare_maker_eth_payment_data(&self, args: &MakerPaymentArgs<'_>) -> Result, PrepareTxDataError> { let function = MAKER_SWAP_V2.function(ETH_MAKER_PAYMENT)?; - let id = self.etomic_swap_id_v2(args.payment_time_lock, &args.maker_secret_hash); + let id = self.etomic_swap_id_v2(args.payment_time_lock, args.maker_secret_hash); let data = function.encode_input(&[ Token::FixedBytes(id), Token::Address(args.taker_address), @@ -342,12 +342,12 @@ impl EthCoin { /// Prepares data for EtomicSwapMakerV2 contract [erc20MakerPayment](https://github.com/KomodoPlatform/etomic-swap/blob/5e15641cbf41766cd5b37b4d71842c270773f788/contracts/EtomicSwapMakerV2.sol#L64) method async fn prepare_maker_erc20_payment_data( &self, - args: &MakerPaymentArgs, + args: &MakerPaymentArgs<'_>, payment_amount: U256, token_address: Address, ) -> Result, PrepareTxDataError> { let function = MAKER_SWAP_V2.function(ERC20_MAKER_PAYMENT)?; - let id = self.etomic_swap_id_v2(args.payment_time_lock, &args.maker_secret_hash); + let id = self.etomic_swap_id_v2(args.payment_time_lock, args.maker_secret_hash); let data = function.encode_input(&[ Token::FixedBytes(id), Token::Uint(payment_amount), @@ -363,10 +363,10 @@ impl EthCoin { /// Prepares data for EtomicSwapMakerV2 contract [refundMakerPaymentTimelock](https://github.com/KomodoPlatform/etomic-swap/blob/5e15641cbf41766cd5b37b4d71842c270773f788/contracts/EtomicSwapMakerV2.sol#L144) method async fn prepare_refund_maker_payment_timelock_data( &self, - args: MakerRefundTimelockArgs, + args: MakerRefundTimelockArgs<'_>, ) -> Result, PrepareTxDataError> { let function = MAKER_SWAP_V2.function("refundMakerPaymentTimelock")?; - let id = self.etomic_swap_id_v2(args.payment_time_lock, &args.maker_secret_hash); + let id = self.etomic_swap_id_v2(args.payment_time_lock, args.maker_secret_hash); let data = function.encode_input(&[ Token::FixedBytes(id), Token::Uint(args.payment_amount), @@ -381,10 +381,10 @@ impl EthCoin { /// Prepares data for EtomicSwapMakerV2 contract [refundMakerPaymentSecret](https://github.com/KomodoPlatform/etomic-swap/blob/5e15641cbf41766cd5b37b4d71842c270773f788/contracts/EtomicSwapMakerV2.sol#L190) method async fn prepare_refund_maker_payment_secret_data( &self, - args: MakerRefundSecretArgs, + args: MakerRefundSecretArgs<'_>, ) -> Result, PrepareTxDataError> { let function = MAKER_SWAP_V2.function("refundMakerPaymentSecret")?; - let id = self.etomic_swap_id_v2(args.payment_time_lock, &args.maker_secret_hash); + let id = self.etomic_swap_id_v2(args.payment_time_lock, args.maker_secret_hash); let data = function.encode_input(&[ Token::FixedBytes(id), Token::Uint(args.payment_amount), @@ -422,7 +422,7 @@ impl EthCoin { /// Validation function for ETH maker payment data fn validate_eth_maker_payment_data( decoded: &[Token], - args: &MakerValidationArgs, + args: &MakerValidationArgs<'_>, func: &Function, tx_value: U256, ) -> Result<(), MmError> { diff --git a/mm2src/coins/eth/eth_swap_v2/eth_taker_swap_v2.rs b/mm2src/coins/eth/eth_swap_v2/eth_taker_swap_v2.rs index fea91b0408..4bb757e636 100644 --- a/mm2src/coins/eth/eth_swap_v2/eth_taker_swap_v2.rs +++ b/mm2src/coins/eth/eth_swap_v2/eth_taker_swap_v2.rs @@ -1,13 +1,11 @@ -use super::{check_decoded_length, validate_amount, validate_from_to_and_status, validate_payment_state, - EthPaymentType, PaymentMethod, PrepareTxDataError, ZERO_VALUE}; +use super::{check_decoded_length, extract_id_from_tx_data, validate_amount, validate_from_to_and_status, + validate_payment_state, EthPaymentType, PaymentMethod, PrepareTxDataError, SpendTxSearchParams, ZERO_VALUE}; use crate::eth::{decode_contract_call, get_function_input_data, wei_from_big_decimal, EthCoin, EthCoinType, ParseCoinAssocTypes, RefundFundingSecretArgs, RefundTakerPaymentArgs, SendTakerFundingArgs, SignedEthTx, SwapTxTypeWithSecretHash, TakerPaymentStateV2, TransactionErr, ValidateSwapV2TxError, ValidateSwapV2TxResult, ValidateTakerFundingArgs, TAKER_SWAP_V2}; -use crate::{FundingTxSpend, GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, SearchForFundingSpendErr, - WaitForPaymentSpendError}; -use common::executor::Timer; -use common::now_sec; +use crate::{FindPaymentSpendError, FundingTxSpend, GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, + SearchForFundingSpendErr}; use ethabi::{Function, Token}; use ethcore_transaction::Action; use ethereum_types::{Address, Public, U256}; @@ -15,7 +13,7 @@ use ethkey::public_to_address; use futures::compat::Future01CompatExt; use mm2_err_handle::prelude::{MapToMmResult, MmError, MmResult}; use std::convert::TryInto; -use web3::types::TransactionId; +use web3::types::{BlockNumber, TransactionId}; const ETH_TAKER_PAYMENT: &str = "ethTakerPayment"; const ERC20_TAKER_PAYMENT: &str = "erc20TakerPayment"; @@ -31,32 +29,32 @@ const TAKER_PAYMENT_APPROVE: &str = "takerPaymentApprove"; /// } const TAKER_PAYMENT_STATE_INDEX: usize = 3; -struct TakerFundingArgs { +struct TakerFundingArgs<'a> { dex_fee: U256, payment_amount: U256, maker_address: Address, - taker_secret_hash: [u8; 32], - maker_secret_hash: [u8; 32], + taker_secret_hash: &'a [u8; 32], + maker_secret_hash: &'a [u8; 32], funding_time_lock: u64, payment_time_lock: u64, } -struct TakerRefundTimelockArgs { +struct TakerRefundTimelockArgs<'a> { dex_fee: U256, payment_amount: U256, maker_address: Address, - taker_secret_hash: [u8; 32], - maker_secret_hash: [u8; 32], + taker_secret_hash: &'a [u8; 32], + maker_secret_hash: &'a [u8; 32], payment_time_lock: u64, token_address: Address, } -struct TakerRefundSecretArgs { +struct TakerRefundSecretArgs<'a> { dex_fee: U256, payment_amount: U256, maker_address: Address, - taker_secret: [u8; 32], - maker_secret_hash: [u8; 32], + taker_secret: &'a [u8; 32], + maker_secret_hash: &'a [u8; 32], payment_time_lock: u64, token_address: Address, } @@ -165,6 +163,7 @@ impl EthCoin { &TAKER_SWAP_V2, EthPaymentType::TakerPayments, TAKER_PAYMENT_STATE_INDEX, + BlockNumber::Latest, ) .await?; @@ -243,6 +242,7 @@ impl EthCoin { &TAKER_SWAP_V2, EthPaymentType::TakerPayments, TAKER_PAYMENT_STATE_INDEX, + BlockNumber::Latest, ) .await ); @@ -348,7 +348,6 @@ impl EthCoin { ) .map_err(|e| TransactionErr::Plain(ERRL!("{}", e)))?; - let taker_secret = try_tx_s!(args.taker_secret.try_into()); let maker_secret_hash = try_tx_s!(args.maker_secret_hash.try_into()); let dex_fee = try_tx_s!(wei_from_big_decimal( &args.dex_fee.fee_amount().to_decimal(), @@ -365,7 +364,7 @@ impl EthCoin { dex_fee, payment_amount, maker_address, - taker_secret, + taker_secret: args.taker_secret, maker_secret_hash, payment_time_lock: args.payment_time_lock, token_address, @@ -383,23 +382,24 @@ impl EthCoin { .await } - /// Checks that taker payment state is `TakerApproved`. - /// Accepts a taker-approved payment transaction and returns it if the state is correct. + /// Checks that taker payment state is `TakerApproved`. Called by maker. + /// Accepts a taker payment transaction and returns it if the state is correct. pub(crate) async fn search_for_taker_funding_spend_impl( &self, tx: &SignedEthTx, ) -> Result>, SearchForFundingSpendErr> { let (decoded, taker_swap_v2_contract) = self - .get_decoded_and_swap_contract(tx, TAKER_PAYMENT_APPROVE) + .get_funding_decoded_and_swap_contract(tx) .await .map_err(|e| SearchForFundingSpendErr::Internal(ERRL!("{}", e)))?; let taker_status = self .payment_status_v2( taker_swap_v2_contract, - decoded[0].clone(), // id from takerPaymentApprove + decoded[0].clone(), // id from ethTakerPayment or erc20TakerPayment &TAKER_SWAP_V2, EthPaymentType::TakerPayments, TAKER_PAYMENT_STATE_INDEX, + BlockNumber::Latest, ) .await .map_err(|e| SearchForFundingSpendErr::Internal(ERRL!("{}", e)))?; @@ -409,8 +409,8 @@ impl EthCoin { Ok(None) } - /// Taker swap contract `spendTakerPayment` method is called for EVM based chains. - /// Returns maker spent payment transaction. + /// Returns maker spent taker payment transaction. Called by maker. + /// Taker swap contract's `spendTakerPayment` method is called for EVM-based chains. pub(crate) async fn sign_and_broadcast_taker_payment_spend_impl( &self, gen_args: &GenTakerPaymentSpendArgs<'_, Self>, @@ -421,10 +421,10 @@ impl EthCoin { .gas_limit(&self.coin_type, EthPaymentType::TakerPayments, PaymentMethod::Spend) .map_err(|e| TransactionErr::Plain(ERRL!("{}", e)))?; - let (taker_swap_v2_contract, approve_func, token_address) = self - .taker_swap_v2_details(TAKER_PAYMENT_APPROVE, TAKER_PAYMENT_APPROVE) + let (taker_swap_v2_contract, taker_payment, token_address) = self + .taker_swap_v2_details(ETH_TAKER_PAYMENT, ERC20_TAKER_PAYMENT) .await?; - let decoded = try_tx_s!(decode_contract_call(approve_func, gen_args.taker_tx.unsigned().data())); + let decoded = try_tx_s!(decode_contract_call(taker_payment, gen_args.taker_tx.unsigned().data())); let taker_status = try_tx_s!( self.payment_status_v2( taker_swap_v2_contract, @@ -432,6 +432,7 @@ impl EthCoin { &TAKER_SWAP_V2, EthPaymentType::TakerPayments, TAKER_PAYMENT_STATE_INDEX, + BlockNumber::Latest ) .await ); @@ -457,41 +458,45 @@ impl EthCoin { Ok(spend_payment_tx) } - /// Checks that taker payment state is `MakerSpent`. - /// Accepts maker spent payment transaction and returns it if payment status is correct. - pub(crate) async fn wait_for_taker_payment_spend_impl( + pub(crate) async fn find_taker_payment_spend_tx_impl( &self, - taker_payment: &SignedEthTx, + taker_payment: &SignedEthTx, // it's approve_tx in Eth case, as in sign_and_send_taker_funding_spend we return approve_tx tx for it + from_block: u64, wait_until: u64, - ) -> MmResult { - let (decoded, taker_swap_v2_contract) = self - .get_decoded_and_swap_contract(taker_payment, "spendTakerPayment") + check_every: f64, + ) -> MmResult { + let taker_swap_v2_contract = self + .swap_v2_contracts + .ok_or_else(|| { + FindPaymentSpendError::Internal("Expected swap_v2_contracts to be Some, but found None".to_string()) + })? + .taker_swap_v2_contract; + let id_array = extract_id_from_tx_data(taker_payment.unsigned().data(), &TAKER_SWAP_V2, TAKER_PAYMENT_APPROVE) + .await? + .as_slice() + .try_into()?; + + let params = SpendTxSearchParams { + swap_contract_address: taker_swap_v2_contract, + event_name: "TakerPaymentSpent", + abi_contract: &TAKER_SWAP_V2, + swap_id: &id_array, + from_block, + wait_until, + check_every, + }; + let tx_hash = self.find_transaction_hash_by_event(params).await?; + + let spend_tx = self + .wait_for_spend_transaction(tx_hash, wait_until, check_every) .await?; - loop { - let taker_status = self - .payment_status_v2( - taker_swap_v2_contract, - decoded[0].clone(), // id from spendTakerPayment - &TAKER_SWAP_V2, - EthPaymentType::TakerPayments, - TAKER_PAYMENT_STATE_INDEX, - ) - .await?; - if taker_status == U256::from(TakerPaymentStateV2::MakerSpent as u8) { - return Ok(taker_payment.clone()); - } - let now = now_sec(); - if now > wait_until { - return MmError::err(WaitForPaymentSpendError::Timeout { wait_until, now }); - } - Timer::sleep(10.).await; - } + Ok(spend_tx) } /// Prepares data for EtomicSwapTakerV2 contract [ethTakerPayment](https://github.com/KomodoPlatform/etomic-swap/blob/5e15641cbf41766cd5b37b4d71842c270773f788/contracts/EtomicSwapTakerV2.sol#L44) method - async fn prepare_taker_eth_funding_data(&self, args: &TakerFundingArgs) -> Result, PrepareTxDataError> { + async fn prepare_taker_eth_funding_data(&self, args: &TakerFundingArgs<'_>) -> Result, PrepareTxDataError> { let function = TAKER_SWAP_V2.function(ETH_TAKER_PAYMENT)?; - let id = self.etomic_swap_id_v2(args.payment_time_lock, &args.maker_secret_hash); + let id = self.etomic_swap_id_v2(args.payment_time_lock, args.maker_secret_hash); let data = function.encode_input(&[ Token::FixedBytes(id), Token::Uint(args.dex_fee), @@ -507,11 +512,11 @@ impl EthCoin { /// Prepares data for EtomicSwapTakerV2 contract [erc20TakerPayment](https://github.com/KomodoPlatform/etomic-swap/blob/5e15641cbf41766cd5b37b4d71842c270773f788/contracts/EtomicSwapTakerV2.sol#L83) method async fn prepare_taker_erc20_funding_data( &self, - args: &TakerFundingArgs, + args: &TakerFundingArgs<'_>, token_address: Address, ) -> Result, PrepareTxDataError> { let function = TAKER_SWAP_V2.function(ERC20_TAKER_PAYMENT)?; - let id = self.etomic_swap_id_v2(args.payment_time_lock, &args.maker_secret_hash); + let id = self.etomic_swap_id_v2(args.payment_time_lock, args.maker_secret_hash); let data = function.encode_input(&[ Token::FixedBytes(id), Token::Uint(args.payment_amount), @@ -529,10 +534,10 @@ impl EthCoin { /// Prepares data for EtomicSwapTakerV2 contract [refundTakerPaymentTimelock](https://github.com/KomodoPlatform/etomic-swap/blob/5e15641cbf41766cd5b37b4d71842c270773f788/contracts/EtomicSwapTakerV2.sol#L208) method async fn prepare_taker_refund_payment_timelock_data( &self, - args: TakerRefundTimelockArgs, + args: TakerRefundTimelockArgs<'_>, ) -> Result, PrepareTxDataError> { let function = TAKER_SWAP_V2.function("refundTakerPaymentTimelock")?; - let id = self.etomic_swap_id_v2(args.payment_time_lock, &args.maker_secret_hash); + let id = self.etomic_swap_id_v2(args.payment_time_lock, args.maker_secret_hash); let data = function.encode_input(&[ Token::FixedBytes(id), Token::Uint(args.payment_amount), @@ -548,10 +553,10 @@ impl EthCoin { /// Prepares data for EtomicSwapTakerV2 contract [refundTakerPaymentSecret](https://github.com/KomodoPlatform/etomic-swap/blob/5e15641cbf41766cd5b37b4d71842c270773f788/contracts/EtomicSwapTakerV2.sol#L267) method async fn prepare_taker_refund_payment_secret_data( &self, - args: &TakerRefundSecretArgs, + args: &TakerRefundSecretArgs<'_>, ) -> Result, PrepareTxDataError> { let function = TAKER_SWAP_V2.function("refundTakerPaymentSecret")?; - let id = self.etomic_swap_id_v2(args.payment_time_lock, &args.maker_secret_hash); + let id = self.etomic_swap_id_v2(args.payment_time_lock, args.maker_secret_hash); let data = function.encode_input(&[ Token::FixedBytes(id), Token::Uint(args.payment_amount), @@ -575,24 +580,13 @@ impl EthCoin { let function = TAKER_SWAP_V2.function(TAKER_PAYMENT_APPROVE)?; let data = match self.coin_type { EthCoinType::Eth => { - check_decoded_length(&decoded, 7)?; - let dex_fee = match &decoded[1] { - Token::Uint(value) => value, - _ => return Err(PrepareTxDataError::Internal("Invalid token type for dex fee".into())), - }; - let amount = args - .funding_tx - .unsigned() - .value() - .checked_sub(*dex_fee) - .ok_or_else(|| { - PrepareTxDataError::Internal("Underflow occurred while calculating amount".into()) - })?; + let (dex_fee, amount) = + get_dex_fee_and_amount_from_eth_payment_data(&decoded, args.funding_tx.unsigned().value())?; function.encode_input(&[ - decoded[0].clone(), // id from ethTakerPayment - Token::Uint(amount), // calculated payment amount (tx value - dexFee) - decoded[1].clone(), // dexFee from ethTakerPayment - decoded[2].clone(), // receiver from ethTakerPayment + decoded[0].clone(), // id from ethTakerPayment + Token::Uint(amount), // calculated payment amount (tx value - dexFee) + Token::Uint(dex_fee), // dexFee from ethTakerPayment + decoded[2].clone(), // receiver from ethTakerPayment Token::FixedBytes(args.taker_secret_hash.to_vec()), Token::FixedBytes(args.maker_secret_hash.to_vec()), Token::Address(token_address), // should be zero address Address::default() @@ -611,7 +605,9 @@ impl EthCoin { ])? }, EthCoinType::Nft { .. } => { - return Err(PrepareTxDataError::Internal("EthCoinType must be ETH or ERC20".into())) + return Err(PrepareTxDataError::Internal( + "NFT protocol is not supported for ETH and ERC20 Swaps".into(), + )) }, }; Ok(data) @@ -625,19 +621,40 @@ impl EthCoin { decoded: Vec, token_address: Address, ) -> Result, PrepareTxDataError> { - check_decoded_length(&decoded, 7)?; let function = TAKER_SWAP_V2.function("spendTakerPayment")?; let taker_address = public_to_address(args.taker_pub); - let data = function.encode_input(&[ - decoded[0].clone(), // id from takerPaymentApprove - decoded[1].clone(), // amount from takerPaymentApprove - decoded[2].clone(), // dexFee from takerPaymentApprove - Token::Address(taker_address), // taker address - decoded[4].clone(), // takerSecretHash from ethTakerPayment - Token::FixedBytes(secret.to_vec()), // makerSecret - Token::Address(token_address), // tokenAddress - ])?; - Ok(data) + match self.coin_type { + EthCoinType::Eth => { + let (dex_fee, amount) = + get_dex_fee_and_amount_from_eth_payment_data(&decoded, args.taker_tx.unsigned().value())?; + let data = function.encode_input(&[ + decoded[0].clone(), // id from ethTakerPayment + Token::Uint(amount), // calculated payment amount (tx value - dexFee) + Token::Uint(dex_fee), // dexFee from ethTakerPayment + Token::Address(taker_address), // taker address + decoded[3].clone(), // takerSecretHash from ethTakerPayment + Token::FixedBytes(secret.to_vec()), // makerSecret + Token::Address(token_address), // tokenAddress + ])?; + Ok(data) + }, + EthCoinType::Erc20 { .. } => { + check_decoded_length(&decoded, 9)?; + let data = function.encode_input(&[ + decoded[0].clone(), // id from erc20TakerPayment + decoded[1].clone(), // amount from erc20TakerPayment + decoded[2].clone(), // dexFee from erc20TakerPayment + Token::Address(taker_address), // taker address + decoded[5].clone(), // takerSecretHash from erc20TakerPayment + Token::FixedBytes(secret.to_vec()), // makerSecret + Token::Address(token_address), // tokenAddress + ])?; + Ok(data) + }, + EthCoinType::Nft { .. } => Err(PrepareTxDataError::Internal( + "NFT protocol is not supported for ETH and ERC20 Swaps".to_string(), + )), + } } /// Retrieves the taker smart contract address, the corresponding function, and the token address. @@ -666,14 +683,14 @@ impl EthCoin { Ok((taker_swap_v2_contract, func, token_address)) } - async fn get_decoded_and_swap_contract( + async fn get_funding_decoded_and_swap_contract( &self, tx: &SignedEthTx, - function_name: &str, ) -> Result<(Vec, Address), PrepareTxDataError> { let decoded = { let func = match self.coin_type { - EthCoinType::Eth | EthCoinType::Erc20 { .. } => TAKER_SWAP_V2.function(function_name)?, + EthCoinType::Eth => TAKER_SWAP_V2.function(ETH_TAKER_PAYMENT)?, + EthCoinType::Erc20 { .. } => TAKER_SWAP_V2.function(ERC20_TAKER_PAYMENT)?, EthCoinType::Nft { .. } => { return Err(PrepareTxDataError::Internal( "NFT protocol is not supported for ETH and ERC20 Swaps".to_string(), @@ -692,6 +709,42 @@ impl EthCoin { Ok((decoded, taker_swap_v2_contract)) } + + /// Extracts the maker's secret from the input of transaction that calls the `spendTakerPayment` smart contract method. + /// + /// function spendTakerPayment( + /// bytes32 id, + /// uint256 amount, + /// uint256 dexFee, + /// address taker, + /// bytes32 takerSecretHash, + /// bytes32 makerSecret, + /// address tokenAddress + /// ) + pub(crate) async fn extract_secret_v2_impl(&self, spend_tx: &SignedEthTx) -> Result<[u8; 32], String> { + let function = try_s!(TAKER_SWAP_V2.function("spendTakerPayment")); + // should be 0xcc90c199 + let expected_signature = function.short_signature(); + let signature = &spend_tx.unsigned().data()[0..4]; + if signature != expected_signature { + return ERR!( + "Expected 'spendTakerPayment' contract call signature: {:?}, found {:?}", + expected_signature, + signature + ); + }; + let decoded = try_s!(decode_contract_call(function, spend_tx.unsigned().data())); + if decoded.len() < 7 { + return ERR!("Invalid arguments in 'spendTakerPayment' call: {:?}", decoded); + } + match &decoded[5] { + Token::FixedBytes(secret) => Ok(try_s!(secret.as_slice().try_into())), + _ => ERR!( + "Expected secret to be fixed bytes, but decoded function data is {:?}", + decoded + ), + } + } } /// Validation function for ETH taker payment data @@ -766,3 +819,23 @@ fn validate_erc20_taker_payment_data( } Ok(()) } + +fn get_dex_fee_and_amount_from_eth_payment_data( + decoded: &Vec, + tx_value: U256, +) -> Result<(U256, U256), PrepareTxDataError> { + check_decoded_length(decoded, 7)?; + let dex_fee = match decoded.get(1) { + Some(Token::Uint(dex_fee)) => *dex_fee, + _ => { + return Err(PrepareTxDataError::Internal(format!( + "Invalid token type for dex fee, got decoded function data: {:?}", + decoded + ))) + }, + }; + let amount = tx_value + .checked_sub(dex_fee) + .ok_or_else(|| PrepareTxDataError::Internal("Underflow occurred while calculating amount".into()))?; + Ok((dex_fee, amount)) +} diff --git a/mm2src/coins/eth/eth_swap_v2/mod.rs b/mm2src/coins/eth/eth_swap_v2/mod.rs index 798a232d56..dc199fa5f3 100644 --- a/mm2src/coins/eth/eth_swap_v2/mod.rs +++ b/mm2src/coins/eth/eth_swap_v2/mod.rs @@ -1,13 +1,18 @@ -use crate::eth::{EthCoin, EthCoinType, ParseCoinAssocTypes, Transaction, TransactionErr}; +use crate::eth::{decode_contract_call, signed_tx_from_web3_tx, EthCoin, EthCoinType, ParseCoinAssocTypes, Transaction, + TransactionErr}; +use crate::{FindPaymentSpendError, MarketCoinOps}; +use common::executor::Timer; +use common::log::{error, info}; +use common::now_sec; use enum_derives::EnumFromStringify; use ethabi::{Contract, Token}; use ethcore_transaction::SignedTransaction as SignedEthTx; -use ethereum_types::{Address, U256}; +use ethereum_types::{Address, H256, U256}; use futures::compat::Future01CompatExt; -use mm2_err_handle::mm_error::MmError; +use mm2_err_handle::prelude::{MmError, MmResult}; use mm2_number::BigDecimal; use num_traits::Signed; -use web3::types::Transaction as Web3Tx; +use web3::types::{BlockNumber, Transaction as Web3Tx, TransactionId}; pub(crate) mod eth_maker_swap_v2; pub(crate) mod eth_taker_swap_v2; @@ -68,6 +73,16 @@ pub(crate) enum PrepareTxDataError { Internal(String), } +pub(crate) struct SpendTxSearchParams<'a> { + pub(crate) swap_contract_address: Address, + pub(crate) event_name: &'a str, + pub(crate) abi_contract: &'a Contract, + pub(crate) swap_id: &'a [u8; 32], + pub(crate) from_block: u64, + pub(crate) wait_until: u64, + pub(crate) check_every: f64, +} + impl EthCoin { /// Retrieves the payment status from a given smart contract address based on the swap ID and state type. pub(crate) async fn payment_status_v2( @@ -77,12 +92,19 @@ impl EthCoin { contract_abi: &Contract, payment_type: EthPaymentType, state_index: usize, + block_number: BlockNumber, ) -> Result { let function_name = payment_type.as_str(); let function = contract_abi.function(function_name)?; let data = function.encode_input(&[swap_id])?; let bytes = self - .call_request(self.my_addr().await, swap_address, None, Some(data.into())) + .call_request( + self.my_addr().await, + swap_address, + None, + Some(data.into()), + block_number, + ) .await?; let decoded_tokens = function.decode_output(&bytes.0)?; @@ -108,6 +130,100 @@ impl EthCoin { EthCoinType::Nft { .. } => Err("NFT protocol is not supported for ETH and ERC20 Swaps".to_string()), } } + + /// A helper function that scans blocks for a specific event containing the given `swap_id`, + /// returning transaction hash of spend transaction once found. + /// **NOTE:** The current function implementation assumes that `swap_id` is the first 32 bytes of the transaction input data. + pub(crate) async fn find_transaction_hash_by_event( + &self, + params: SpendTxSearchParams<'_>, + ) -> MmResult { + loop { + let now = now_sec(); + if now > params.wait_until { + return MmError::err(FindPaymentSpendError::Timeout { + wait_until: params.wait_until, + now, + }); + } + + let current_block = match self.current_block().compat().await { + Ok(b) => b, + Err(e) => { + error!("Error getting block number: {}", e); + Timer::sleep(params.check_every).await; + continue; + }, + }; + + let mut next_from_block = params.from_block; + while next_from_block <= current_block { + let to_block = std::cmp::min(next_from_block + self.logs_block_range - 1, current_block); + + // Fetch events for the current block range + let events = match self + .events_from_block( + params.swap_contract_address, + params.event_name, + next_from_block, + Some(to_block), + params.abi_contract, + ) + .await + { + Ok(events) => events, + Err(e) => { + error!( + "Error getting {} events from {} to {} block: {}", + params.event_name, next_from_block, to_block, e + ); + Timer::sleep(params.check_every).await; + continue; + }, + }; + + // Check if any event matches the SWAP ID + if let Some(found_event) = events + .into_iter() + .find(|event| event.data.0.len() >= 32 && &event.data.0[..32] == params.swap_id) + { + if let Some(hash) = found_event.transaction_hash { + return Ok(hash); + } + } + + next_from_block += self.logs_block_range; + } + + Timer::sleep(params.check_every).await; + } + } + + /// Waits until the specified transaction is found by its hash or the given timeout is reached + pub(crate) async fn wait_for_spend_transaction( + &self, + tx_hash: H256, + wait_until: u64, + check_every: f64, + ) -> MmResult { + loop { + let now = now_sec(); + if now > wait_until { + return MmError::err(FindPaymentSpendError::Timeout { wait_until, now }); + } + + match self.transaction(TransactionId::Hash(tx_hash)).await { + Ok(Some(t)) => { + let transaction = signed_tx_from_web3_tx(t).map_err(FindPaymentSpendError::Internal)?; + return Ok(transaction); + }, + Ok(None) => info!("Transaction {} not found yet", tx_hash), + Err(e) => error!("Get transaction {} error: {}", tx_hash, e), + }; + + Timer::sleep(check_every).await; + } + } } pub(crate) fn validate_payment_state( @@ -133,8 +249,8 @@ pub(crate) fn validate_from_to_and_status( ) -> Result<(), MmError> { if status != U256::from(expected_status) { return MmError::err(ValidatePaymentV2Err::UnexpectedPaymentState(format!( - "Payment state is not `PaymentSent`, got {}", - status + "tx {:?} Payment state is not `PaymentSent`, got {}", + tx_from_rpc.hash, status ))); } if tx_from_rpc.from != Some(expected_from) { @@ -201,3 +317,19 @@ impl EthCoin { Ok(()) } } + +pub(crate) async fn extract_id_from_tx_data( + tx_data: &[u8], + abi_contract: &Contract, + func_name: &str, +) -> Result, FindPaymentSpendError> { + let func = abi_contract.function(func_name)?; + let decoded = decode_contract_call(func, tx_data)?; + match decoded.first() { + Some(Token::FixedBytes(bytes)) => Ok(bytes.clone()), + invalid_token => Err(FindPaymentSpendError::InvalidData(format!( + "Expected Token::FixedBytes, got {:?}", + invalid_token + ))), + } +} diff --git a/mm2src/coins/eth/eth_tests.rs b/mm2src/coins/eth/eth_tests.rs index a02aa9eaf8..8f726b65b1 100644 --- a/mm2src/coins/eth/eth_tests.rs +++ b/mm2src/coins/eth/eth_tests.rs @@ -1,6 +1,7 @@ use super::*; use crate::IguanaPrivKey; use common::block_on; +use futures_util::future; use mm2_core::mm_ctx::MmCtxBuilder; cfg_native!( @@ -163,7 +164,7 @@ fn test_wei_from_big_decimal() { fn test_wait_for_payment_spend_timeout() { const TAKER_PAYMENT_SPEND_SEARCH_INTERVAL: f64 = 1.; - EthCoin::spend_events.mock_safe(|_, _, _, _| MockResult::Return(Box::new(futures01::future::ok(vec![])))); + EthCoin::events_from_block.mock_safe(|_, _, _, _, _, _| MockResult::Return(Box::pin(future::ok(vec![])))); EthCoin::current_block.mock_safe(|_| MockResult::Return(Box::new(futures01::future::ok(900)))); let key_pair = Random.generate().unwrap(); diff --git a/mm2src/coins/eth/nft_swap_v2/mod.rs b/mm2src/coins/eth/nft_swap_v2/mod.rs index f4c909bd32..1305668f83 100644 --- a/mm2src/coins/eth/nft_swap_v2/mod.rs +++ b/mm2src/coins/eth/nft_swap_v2/mod.rs @@ -6,7 +6,7 @@ use futures::compat::Future01CompatExt; use mm2_err_handle::prelude::{MapToMmResult, MmError, MmResult}; use mm2_number::BigDecimal; use num_traits::Signed; -use web3::types::TransactionId; +use web3::types::{BlockNumber, TransactionId}; use super::ContractType; use crate::coin_errors::{ValidatePaymentError, ValidatePaymentResult}; @@ -88,6 +88,7 @@ impl EthCoin { &NFT_MAKER_SWAP_V2, EthPaymentType::MakerPayments, 2, + BlockNumber::Latest, ) .await?; let tx_from_rpc = self @@ -467,6 +468,7 @@ impl EthCoin { contract_abi, payment_type, state_index, + BlockNumber::Latest, ) .await?; diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 16ade17a6b..7ad6a58d4d 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -212,6 +212,7 @@ pub mod watcher_common; pub mod coin_errors; use coin_errors::{MyAddressError, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentResult}; +use crypto::secret_hash_algo::SecretHashAlgo; #[doc(hidden)] #[cfg(test)] @@ -1288,7 +1289,7 @@ pub struct RefundFundingSecretArgs<'a, Coin: ParseCoinAssocTypes + ?Sized> { pub funding_time_lock: u64, pub payment_time_lock: u64, pub maker_pubkey: &'a Coin::Pubkey, - pub taker_secret: &'a [u8], + pub taker_secret: &'a [u8; 32], pub taker_secret_hash: &'a [u8], pub maker_secret_hash: &'a [u8], pub dex_fee: &'a DexFee, @@ -1533,10 +1534,20 @@ pub trait ToBytes { fn to_bytes(&self) -> Vec; } +/// Should convert coin `Self::Address` type into a properly formatted string representation. +/// +/// Don't use `to_string` directly on `Self::Address` types in generic TPU code! +/// It may produce abbreviated or non-standard formats (e.g. `ethereum_types::Address` will be like this `0x7cc9…3874`), +/// which are not guaranteed to be parsable back into the original `Address` type. +/// This function should ensure the resulting string is consistently formatted and fully reversible. +pub trait AddrToString { + fn addr_to_string(&self) -> String; +} + /// Defines associated types specific to each coin (Pubkey, Address, etc.) #[async_trait] pub trait ParseCoinAssocTypes { - type Address: Send + Sync + fmt::Display; + type Address: Send + Sync + fmt::Display + AddrToString; type AddressParseError: fmt::Debug + Send + fmt::Display; type Pubkey: ToBytes + Send + Sync; type PubkeyParseError: fmt::Debug + Send + fmt::Display; @@ -1667,7 +1678,7 @@ pub struct RefundMakerPaymentSecretArgs<'a, Coin: ParseCoinAssocTypes + ?Sized> /// The hash of the secret generated by maker, taker needs it to spend the payment pub maker_secret_hash: &'a [u8], /// Taker's secret - pub taker_secret: &'a [u8], + pub taker_secret: &'a [u8; 32], /// Taker's HTLC pubkey pub taker_pub: &'a Coin::Pubkey, /// Unique data of specific swap @@ -1702,7 +1713,7 @@ pub struct SpendMakerPaymentArgs<'a, Coin: ParseCoinAssocTypes + ?Sized> { /// The hash of the secret generated by maker, taker needs it to spend the payment pub maker_secret_hash: &'a [u8], /// The secret generated by maker, revealed when maker spends taker's payment - pub maker_secret: &'a [u8], + pub maker_secret: [u8; 32], /// Maker's HTLC pubkey pub maker_pub: &'a Coin::Pubkey, /// Unique data of specific swap @@ -1786,7 +1797,7 @@ pub trait MakerNftSwapOpsV2: ParseCoinAssocTypes + ParseNftAssocTypes + Send + S /// Enum representing errors that can occur while waiting for taker payment spend. #[derive(Display, Debug, EnumFromStringify)] -pub enum WaitForPaymentSpendError { +pub enum FindPaymentSpendError { /// Timeout error variant, indicating that the wait for taker payment spend has timed out. #[display( fmt = "Timed out waiting for taker payment spend, wait_until {}, now {}", @@ -1801,6 +1812,7 @@ pub enum WaitForPaymentSpendError { }, /// Invalid input transaction error variant, containing additional information about the error. InvalidInputTx(String), + #[from_stringify("TryFromSliceError")] Internal(String), #[from_stringify("ethabi::Error")] #[display(fmt = "ABI error: {}", _0)] @@ -1809,18 +1821,18 @@ pub enum WaitForPaymentSpendError { Transport(String), } -impl From for WaitForPaymentSpendError { +impl From for FindPaymentSpendError { fn from(err: WaitForOutputSpendErr) -> Self { match err { - WaitForOutputSpendErr::Timeout { wait_until, now } => WaitForPaymentSpendError::Timeout { wait_until, now }, + WaitForOutputSpendErr::Timeout { wait_until, now } => FindPaymentSpendError::Timeout { wait_until, now }, WaitForOutputSpendErr::NoOutputWithIndex(index) => { - WaitForPaymentSpendError::InvalidInputTx(format!("Tx doesn't have output with index {}", index)) + FindPaymentSpendError::InvalidInputTx(format!("Tx doesn't have output with index {}", index)) }, } } } -impl From for WaitForPaymentSpendError { +impl From for FindPaymentSpendError { fn from(e: PaymentStatusErr) -> Self { match e { PaymentStatusErr::ABIError(e) => Self::ABIError(e), @@ -1831,7 +1843,7 @@ impl From for WaitForPaymentSpendError { } } -impl From for WaitForPaymentSpendError { +impl From for FindPaymentSpendError { fn from(e: PrepareTxDataError) -> Self { match e { PrepareTxDataError::ABIError(e) => Self::ABIError(e), @@ -1966,13 +1978,15 @@ pub trait TakerCoinSwapOpsV2: ParseCoinAssocTypes + CommonSwapOpsV2 + Send + Syn swap_unique_data: &[u8], ) -> Result; - /// Wait until taker payment spend is found on-chain - async fn wait_for_taker_payment_spend( + /// Wait until taker payment spend transaction is found on-chain + async fn find_taker_payment_spend_tx( &self, taker_payment: &Self::Tx, from_block: u64, wait_until: u64, - ) -> MmResult; + ) -> MmResult; + + async fn extract_secret_v2(&self, secret_hash: &[u8], spend_tx: &Self::Tx) -> Result<[u8; 32], String>; } #[async_trait] @@ -3564,6 +3578,21 @@ impl MmCoinEnum { pub fn is_eth(&self) -> bool { matches!(self, MmCoinEnum::EthCoin(_)) } fn is_platform_coin(&self) -> bool { self.ticker() == self.platform_ticker() } + + /// Determines the secret hash algorithm for a coin, prioritizing specific algorithms for certain protocols. + /// # Attention + /// When adding new coins, update this function to specify their appropriate secret hash algorithm. + /// Otherwise, the function will default to `SecretHashAlgo::DHASH160`, which may not be correct for the new coin. + pub fn secret_hash_algo_v2(&self) -> SecretHashAlgo { + match self { + MmCoinEnum::Tendermint(_) | MmCoinEnum::TendermintToken(_) | MmCoinEnum::EthCoin(_) => { + SecretHashAlgo::SHA256 + }, + #[cfg(not(target_arch = "wasm32"))] + MmCoinEnum::LightningCoin(_) => SecretHashAlgo::SHA256, + _ => SecretHashAlgo::DHASH160, + } + } } #[async_trait] diff --git a/mm2src/coins/test_coin.rs b/mm2src/coins/test_coin.rs index 43765ab0ba..0d8ec2a7ee 100644 --- a/mm2src/coins/test_coin.rs +++ b/mm2src/coins/test_coin.rs @@ -1,8 +1,8 @@ #![allow(clippy::all)] -use super::{CoinBalance, CommonSwapOpsV2, FundingTxSpend, HistorySyncState, MarketCoinOps, MmCoin, RawTransactionFut, - RawTransactionRequest, RefundTakerPaymentArgs, SearchForFundingSpendErr, SwapOps, TradeFee, - TransactionEnum, TransactionFut, WaitForPaymentSpendError}; +use super::{AddrToString, CoinBalance, CommonSwapOpsV2, FindPaymentSpendError, FundingTxSpend, HistorySyncState, + MarketCoinOps, MmCoin, RawTransactionFut, RawTransactionRequest, RefundTakerPaymentArgs, + SearchForFundingSpendErr, SwapOps, TradeFee, TransactionEnum, TransactionFut}; use crate::coin_errors::ValidatePaymentResult; use crate::{coin_errors::MyAddressError, BalanceFut, CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinFutSpawner, ConfirmPaymentInput, FeeApproxStage, FoundSwapTxSpend, GenPreimageResult, GenTakerFundingSpendArgs, @@ -29,6 +29,7 @@ use mm2_number::{BigDecimal, MmNumber}; use mocktopus::macros::*; use rpc::v1::types::Bytes as BytesJson; use serde_json::Value as Json; +use std::fmt::{Display, Formatter}; use std::ops::Deref; use std::sync::Arc; @@ -441,9 +442,19 @@ impl ToBytes for TestSig { fn to_bytes(&self) -> Vec { vec![] } } +pub struct TestAddress {} + +impl AddrToString for TestAddress { + fn addr_to_string(&self) -> String { unimplemented!() } +} + +impl Display for TestAddress { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { unimplemented!() } +} + #[async_trait] impl ParseCoinAssocTypes for TestCoin { - type Address = String; + type Address = TestAddress; type AddressParseError = String; type Pubkey = TestPubkey; type PubkeyParseError = String; @@ -557,12 +568,16 @@ impl TakerCoinSwapOpsV2 for TestCoin { unimplemented!() } - async fn wait_for_taker_payment_spend( + async fn find_taker_payment_spend_tx( &self, taker_payment: &Self::Tx, from_block: u64, wait_until: u64, - ) -> MmResult { + ) -> MmResult { + unimplemented!() + } + + async fn extract_secret_v2(&self, secret_hash: &[u8], spend_tx: &Self::Tx) -> Result<[u8; 32], String> { unimplemented!() } } diff --git a/mm2src/coins/utxo.rs b/mm2src/coins/utxo.rs index 6d98451c7f..626836864e 100644 --- a/mm2src/coins/utxo.rs +++ b/mm2src/coins/utxo.rs @@ -102,10 +102,10 @@ use utxo_signer::{TxProvider, TxProviderError, UtxoSignTxError, UtxoSignTxResult use self::rpc_clients::{electrum_script_hash, ElectrumClient, ElectrumConnectionSettings, EstimateFeeMethod, EstimateFeeMode, NativeClient, UnspentInfo, UnspentMap, UtxoRpcClientEnum, UtxoRpcError, UtxoRpcFut, UtxoRpcResult}; -use super::{big_decimal_from_sat_unsigned, BalanceError, BalanceFut, BalanceResult, CoinBalance, CoinFutSpawner, - CoinsContext, DerivationMethod, FeeApproxStage, FoundSwapTxSpend, HistorySyncState, KmdRewardsDetails, - MarketCoinOps, MmCoin, NumConversError, NumConversResult, PrivKeyActivationPolicy, PrivKeyPolicy, - PrivKeyPolicyNotAllowed, RawTransactionFut, TradeFee, TradePreimageError, TradePreimageFut, +use super::{big_decimal_from_sat_unsigned, AddrToString, BalanceError, BalanceFut, BalanceResult, CoinBalance, + CoinFutSpawner, CoinsContext, DerivationMethod, FeeApproxStage, FoundSwapTxSpend, HistorySyncState, + KmdRewardsDetails, MarketCoinOps, MmCoin, NumConversError, NumConversResult, PrivKeyActivationPolicy, + PrivKeyPolicy, PrivKeyPolicyNotAllowed, RawTransactionFut, TradeFee, TradePreimageError, TradePreimageFut, TradePreimageResult, Transaction, TransactionDetails, TransactionEnum, TransactionErr, UnexpectedDerivationMethod, VerificationError, WithdrawError, WithdrawRequest}; use crate::coin_balance::{EnableCoinScanPolicy, EnabledCoinBalanceParams, HDAddressBalanceScanner}; @@ -1028,6 +1028,10 @@ impl ToBytes for Signature { fn to_bytes(&self) -> Vec { self.to_vec() } } +impl AddrToString for Address { + fn addr_to_string(&self) -> String { self.to_string() } +} + #[async_trait] impl ParseCoinAssocTypes for T { type Address = Address; diff --git a/mm2src/coins/utxo/utxo_common.rs b/mm2src/coins/utxo/utxo_common.rs index 70c8522b58..23d3201fa7 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -2584,19 +2584,25 @@ pub async fn get_taker_watcher_reward Result, String> { let spend_tx: UtxoTx = try_s!(deserialize(spend_tx).map_err(|e| ERRL!("{:?}", e))); + extract_secret_v2(secret_hash, &spend_tx).map(|secret_array| secret_array.to_vec()) +} + +/// Extract a secret from the `spend_tx`. +/// Note spender could generate the spend with several inputs where the only one input is the p2sh script. +pub fn extract_secret_v2(secret_hash: &[u8], spend_tx: &UtxoTx) -> Result<[u8; 32], String> { let expected_secret_hash = if secret_hash.len() == 32 { ripemd160(secret_hash) } else { H160::from(secret_hash) }; - for input in spend_tx.inputs.into_iter() { + for input in spend_tx.inputs.iter() { let script: Script = input.script_sig.clone().into(); for instruction in script.iter().flatten() { if instruction.opcode == Opcode::OP_PUSHBYTES_32 { if let Some(secret) = instruction.data { let actual_secret_hash = dhash160(secret); if actual_secret_hash == expected_secret_hash { - return Ok(secret.to_vec()); + return Ok(try_s!(secret.try_into())); } } } @@ -5009,7 +5015,7 @@ pub async fn spend_maker_payment_v2( let key_pair = coin.derive_htlc_key_pair(args.swap_unique_data); let script_data = Builder::default() - .push_data(args.maker_secret) + .push_data(&args.maker_secret) .push_opcode(Opcode::OP_1) .push_opcode(Opcode::OP_0) .into_script(); diff --git a/mm2src/coins/utxo/utxo_standard.rs b/mm2src/coins/utxo/utxo_standard.rs index f5a02f5095..e513a492bb 100644 --- a/mm2src/coins/utxo/utxo_standard.rs +++ b/mm2src/coins/utxo/utxo_standard.rs @@ -23,22 +23,22 @@ use crate::utxo::utxo_hd_wallet::{UtxoHDAccount, UtxoHDAddress}; use crate::utxo::utxo_tx_history_v2::{UtxoMyAddressesHistoryError, UtxoTxDetailsError, UtxoTxDetailsParams, UtxoTxHistoryOps}; use crate::{CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinBalance, CoinBalanceMap, CoinWithDerivationMethod, - CoinWithPrivKeyPolicy, CommonSwapOpsV2, ConfirmPaymentInput, DexFee, FundingTxSpend, GenPreimageResult, - GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, GetWithdrawSenderAddress, IguanaBalanceOps, - IguanaPrivKey, MakerCoinSwapOpsV2, MakerSwapTakerCoin, MmCoinEnum, NegotiateSwapContractAddrErr, - PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, - RawTransactionRequest, RawTransactionResult, RefundError, RefundFundingSecretArgs, - RefundMakerPaymentSecretArgs, RefundMakerPaymentTimelockArgs, RefundPaymentArgs, RefundResult, - RefundTakerPaymentArgs, SearchForFundingSpendErr, SearchForSwapTxSpendInput, SendMakerPaymentArgs, - SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SendTakerFundingArgs, SignRawTransactionRequest, - SignatureResult, SpendMakerPaymentArgs, SpendPaymentArgs, SwapOps, SwapTxTypeWithSecretHash, - TakerCoinSwapOpsV2, TakerSwapMakerCoin, ToBytes, TradePreimageValue, TransactionFut, TransactionResult, - TxMarshalingErr, TxPreimageWithSig, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, - ValidateMakerPaymentArgs, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, - ValidatePaymentInput, ValidateSwapV2TxResult, ValidateTakerFundingArgs, - ValidateTakerFundingSpendPreimageResult, ValidateTakerPaymentSpendPreimageResult, - ValidateWatcherSpendInput, VerificationResult, WaitForHTLCTxSpendArgs, WaitForPaymentSpendError, - WatcherOps, WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, + CoinWithPrivKeyPolicy, CommonSwapOpsV2, ConfirmPaymentInput, DexFee, FindPaymentSpendError, + FundingTxSpend, GenPreimageResult, GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, + GetWithdrawSenderAddress, IguanaBalanceOps, IguanaPrivKey, MakerCoinSwapOpsV2, MakerSwapTakerCoin, + MmCoinEnum, NegotiateSwapContractAddrErr, PaymentInstructionArgs, PaymentInstructions, + PaymentInstructionsErr, PrivKeyBuildPolicy, RawTransactionRequest, RawTransactionResult, RefundError, + RefundFundingSecretArgs, RefundMakerPaymentSecretArgs, RefundMakerPaymentTimelockArgs, RefundPaymentArgs, + RefundResult, RefundTakerPaymentArgs, SearchForFundingSpendErr, SearchForSwapTxSpendInput, + SendMakerPaymentArgs, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SendTakerFundingArgs, + SignRawTransactionRequest, SignatureResult, SpendMakerPaymentArgs, SpendPaymentArgs, SwapOps, + SwapTxTypeWithSecretHash, TakerCoinSwapOpsV2, TakerSwapMakerCoin, ToBytes, TradePreimageValue, + TransactionFut, TransactionResult, TxMarshalingErr, TxPreimageWithSig, ValidateAddressResult, + ValidateFeeArgs, ValidateInstructionsErr, ValidateMakerPaymentArgs, ValidateOtherPubKeyErr, + ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, ValidateSwapV2TxResult, + ValidateTakerFundingArgs, ValidateTakerFundingSpendPreimageResult, + ValidateTakerPaymentSpendPreimageResult, ValidateWatcherSpendInput, VerificationResult, + WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut}; use common::executor::{AbortableSystem, AbortedError}; use futures::{FutureExt, TryFutureExt}; @@ -866,12 +866,12 @@ impl TakerCoinSwapOpsV2 for UtxoStandardCoin { utxo_common::sign_and_broadcast_taker_payment_spend(self, preimage, gen_args, secret, &htlc_keypair).await } - async fn wait_for_taker_payment_spend( + async fn find_taker_payment_spend_tx( &self, taker_payment: &Self::Tx, from_block: u64, wait_until: u64, - ) -> MmResult { + ) -> MmResult { let res = utxo_common::wait_for_output_spend_impl( self.as_ref(), taker_payment, @@ -883,6 +883,10 @@ impl TakerCoinSwapOpsV2 for UtxoStandardCoin { .await?; Ok(res) } + + async fn extract_secret_v2(&self, secret_hash: &[u8], spend_tx: &Self::Tx) -> Result<[u8; 32], String> { + utxo_common::extract_secret_v2(secret_hash, spend_tx) + } } impl CommonSwapOpsV2 for UtxoStandardCoin { diff --git a/mm2src/crypto/src/lib.rs b/mm2src/crypto/src/lib.rs index e2651a54ea..f735203232 100644 --- a/mm2src/crypto/src/lib.rs +++ b/mm2src/crypto/src/lib.rs @@ -12,6 +12,7 @@ pub mod hw_rpc_task; mod key_derivation; pub mod mnemonic; pub mod privkey; +pub mod secret_hash_algo; mod shared_db_id; mod slip21; mod standard_hd_path; diff --git a/mm2src/crypto/src/secret_hash_algo.rs b/mm2src/crypto/src/secret_hash_algo.rs new file mode 100644 index 0000000000..c5add0492f --- /dev/null +++ b/mm2src/crypto/src/secret_hash_algo.rs @@ -0,0 +1,39 @@ +use bitcrypto::{dhash160, sha256}; +use derive_more::Display; +use std::convert::TryFrom; + +/// Algorithm used to hash swap secret. +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize)] +pub enum SecretHashAlgo { + /// ripemd160(sha256(secret)) + #[default] + DHASH160 = 1, + /// sha256(secret) + SHA256 = 2, +} + +#[derive(Debug, Display)] +pub struct UnsupportedSecretHashAlgo(u8); + +impl std::error::Error for UnsupportedSecretHashAlgo {} + +impl TryFrom for SecretHashAlgo { + type Error = UnsupportedSecretHashAlgo; + + fn try_from(value: u8) -> Result { + match value { + 1 => Ok(SecretHashAlgo::DHASH160), + 2 => Ok(SecretHashAlgo::SHA256), + unsupported => Err(UnsupportedSecretHashAlgo(unsupported)), + } + } +} + +impl SecretHashAlgo { + pub fn hash_secret(&self, secret: &[u8]) -> Vec { + match self { + SecretHashAlgo::DHASH160 => dhash160(secret).take().into(), + SecretHashAlgo::SHA256 => sha256(secret).take().into(), + } + } +} diff --git a/mm2src/mm2_main/src/lp_ordermatch.rs b/mm2src/mm2_main/src/lp_ordermatch.rs index dba2139998..c408afa0bc 100644 --- a/mm2src/mm2_main/src/lp_ordermatch.rs +++ b/mm2src/mm2_main/src/lp_ordermatch.rs @@ -25,7 +25,7 @@ use blake2::digest::{Update, VariableOutput}; use blake2::Blake2bVar; use coins::utxo::{compressed_pub_key_from_priv_raw, ChecksumType, UtxoAddressFormat}; use coins::{coin_conf, find_pair, lp_coinfind, BalanceTradeFeeUpdatedHandler, CoinProtocol, CoinsContext, - FeeApproxStage, MarketCoinOps, MmCoinEnum}; + FeeApproxStage, MakerCoinSwapOpsV2, MmCoin, MmCoinEnum, TakerCoinSwapOpsV2}; use common::executor::{simple_map::AbortableSimpleMap, AbortSettings, AbortableSystem, AbortedError, SpawnAbortable, SpawnFuture, Timer}; use common::log::{error, warn, LogOnError}; @@ -57,6 +57,7 @@ use my_orders_storage::{delete_my_maker_order, delete_my_taker_order, save_maker use num_traits::identities::Zero; use parking_lot::Mutex as PaMutex; use rpc::v1::types::H256 as H256Json; +use secp256k1::PublicKey as Secp256k1Pubkey; use serde_json::{self as json, Value as Json}; use sp_trie::{delta_trie_root, MemoryDB, Trie, TrieConfiguration, TrieDB, TrieDBMut, TrieHash, TrieMut}; use std::collections::hash_map::{Entry, HashMap, RawEntryMut}; @@ -74,7 +75,7 @@ use crate::lp_network::{broadcast_p2p_msg, request_any_relay, request_one_peer, use crate::lp_swap::maker_swap_v2::{self, MakerSwapStateMachine, MakerSwapStorage}; use crate::lp_swap::taker_swap_v2::{self, TakerSwapStateMachine, TakerSwapStorage}; use crate::lp_swap::{calc_max_maker_vol, check_balance_for_maker_swap, check_balance_for_taker_swap, - check_other_coin_balance_for_swap, detect_secret_hash_algo, dex_fee_amount_from_taker_coin, + check_other_coin_balance_for_swap, detect_secret_hash_algo_v2, dex_fee_amount_from_taker_coin, generate_secret, get_max_maker_vol, insert_new_swap_to_db, is_pubkey_banned, lp_atomic_locktime, p2p_keypair_and_peer_id_to_broadcast, p2p_private_and_peer_id_to_broadcast, run_maker_swap, run_taker_swap, swap_v2_topic, AtomicLocktimeVersion, CheckBalanceError, CheckBalanceResult, @@ -85,6 +86,7 @@ use crate::lp_swap::{calc_max_maker_vol, check_balance_for_maker_swap, check_bal use crate::lp_swap::taker_swap::FailAt; pub use best_orders::{best_orders_rpc, best_orders_rpc_v2}; +use crypto::secret_hash_algo::SecretHashAlgo; pub use orderbook_depth::orderbook_depth_rpc; pub use orderbook_rpc::{orderbook_rpc, orderbook_rpc_v2}; @@ -99,6 +101,7 @@ mod best_orders; mod lp_bot; pub use lp_bot::{start_simple_market_maker_bot, stop_simple_market_maker_bot, StartSimpleMakerBotRequest, TradingBotEvent}; +use primitives::hash::{H256, H264}; mod my_orders_storage; mod new_protocol; @@ -2891,6 +2894,25 @@ impl MakerOrdersContext { fn balance_loop_exists(&mut self, ticker: &str) -> bool { self.balance_loops.lock().contains(ticker).unwrap() } } +struct LegacySwapParams<'a> { + maker_coin: &'a MmCoinEnum, + taker_coin: &'a MmCoinEnum, + uuid: &'a Uuid, + my_conf_settings: &'a SwapConfirmationsSettings, + my_persistent_pub: &'a H264, + maker_amount: &'a MmNumber, + taker_amount: &'a MmNumber, + locktime: &'a u64, +} +struct StateMachineParams<'a> { + secret_hash_algo: &'a SecretHashAlgo, + uuid: &'a Uuid, + my_conf_settings: &'a SwapConfirmationsSettings, + locktime: &'a u64, + maker_amount: &'a MmNumber, + taker_amount: &'a MmNumber, +} + #[cfg_attr(test, mockable)] fn lp_connect_start_bob(ctx: MmArc, maker_match: MakerMatch, maker_order: MakerOrder, taker_p2p_pubkey: PublicKey) { let spawner = ctx.spawner(); @@ -2921,7 +2943,7 @@ fn lp_connect_start_bob(ctx: MmArc, maker_match: MakerMatch, maker_order: MakerO return; }, }; - let alice = bits256::from(maker_match.request.sender_pubkey.0); + let taker_pubkey = bits256::from(maker_match.request.sender_pubkey.0); let maker_amount = maker_match.reserved.get_base_amount().clone(); let taker_amount = maker_match.reserved.get_rel_amount().clone(); @@ -2966,8 +2988,6 @@ fn lp_connect_start_bob(ctx: MmArc, maker_match: MakerMatch, maker_order: MakerO uuid ); - let now = now_sec(); - let secret = match generate_secret() { Ok(s) => s.into(), Err(e) => { @@ -2976,72 +2996,62 @@ fn lp_connect_start_bob(ctx: MmArc, maker_match: MakerMatch, maker_order: MakerO }, }; - if ctx.use_trading_proto_v2() { - let secret_hash_algo = detect_secret_hash_algo(&maker_coin, &taker_coin); - match (maker_coin, taker_coin) { - (MmCoinEnum::UtxoCoin(m), MmCoinEnum::UtxoCoin(t)) => { - let mut maker_swap_state_machine = MakerSwapStateMachine { - storage: MakerSwapStorage::new(ctx.clone()), - abortable_system: ctx - .abortable_system - .create_subsystem() - .expect("create_subsystem should not fail"), - ctx, - started_at: now_sec(), - maker_coin: m.clone(), - maker_volume: maker_amount, - secret, - taker_coin: t.clone(), - dex_fee: dex_fee_amount_from_taker_coin(&t, m.ticker(), &taker_amount), - taker_volume: taker_amount, - taker_premium: Default::default(), - conf_settings: my_conf_settings, - p2p_topic: swap_v2_topic(&uuid), - uuid, - p2p_keypair: maker_order.p2p_privkey.map(SerializableSecp256k1Keypair::into_inner), - secret_hash_algo, - lock_duration: lock_time, - taker_p2p_pubkey: match taker_p2p_pubkey { - PublicKey::Secp256k1(pubkey) => pubkey.into(), - }, - }; - #[allow(clippy::box_default)] - maker_swap_state_machine - .run(Box::new(maker_swap_v2::Initialize::default())) - .await - .error_log(); - }, - _ => todo!("implement fallback to the old protocol here"), - } - } else { - if let Err(e) = insert_new_swap_to_db( - ctx.clone(), - maker_coin.ticker(), - taker_coin.ticker(), - uuid, - now, - LEGACY_SWAP_TYPE, - ) - .await - { - error!("Error {} on new swap insertion", e); - } - let maker_swap = MakerSwap::new( - ctx.clone(), - alice, - maker_amount.to_decimal(), - taker_amount.to_decimal(), - my_persistent_pub, - uuid, - Some(maker_order.uuid), - my_conf_settings, - maker_coin, - taker_coin, - lock_time, - maker_order.p2p_privkey.map(SerializableSecp256k1Keypair::into_inner), - secret, - ); - run_maker_swap(RunMakerSwapInput::StartNew(maker_swap), ctx).await; + // TODO add alice swap protocol version check once protocol versioning is implemented + if !ctx.use_trading_proto_v2() { + let params = LegacySwapParams { + maker_coin: &maker_coin, + taker_coin: &taker_coin, + uuid: &uuid, + my_conf_settings: &my_conf_settings, + my_persistent_pub: &my_persistent_pub, + maker_amount: &maker_amount, + taker_amount: &taker_amount, + locktime: &lock_time, + }; + start_maker_legacy_swap(&ctx, maker_order, taker_pubkey, secret, params).await; + return; + } + + // Ensure detect_secret_hash_algo_v2 returns the correct secret hash algorithm when adding new coin support in TPU. + let params = StateMachineParams { + secret_hash_algo: &detect_secret_hash_algo_v2(&maker_coin, &taker_coin), + uuid: &uuid, + my_conf_settings: &my_conf_settings, + locktime: &lock_time, + maker_amount: &maker_amount, + taker_amount: &taker_amount, + }; + let taker_p2p_pubkey = match taker_p2p_pubkey { + PublicKey::Secp256k1(pubkey) => pubkey.into(), + }; + + // TODO try to handle it more gracefully during project redesign + match (&maker_coin, &taker_coin) { + (MmCoinEnum::UtxoCoin(m), MmCoinEnum::UtxoCoin(t)) => { + start_maker_swap_state_machine(&ctx, &maker_order, &taker_p2p_pubkey, &secret, m, t, ¶ms).await; + }, + (MmCoinEnum::EthCoin(m), MmCoinEnum::EthCoin(t)) => { + start_maker_swap_state_machine(&ctx, &maker_order, &taker_p2p_pubkey, &secret, m, t, ¶ms).await; + }, + (MmCoinEnum::UtxoCoin(m), MmCoinEnum::EthCoin(t)) => { + start_maker_swap_state_machine(&ctx, &maker_order, &taker_p2p_pubkey, &secret, m, t, ¶ms).await; + }, + (MmCoinEnum::EthCoin(m), MmCoinEnum::UtxoCoin(t)) => { + start_maker_swap_state_machine(&ctx, &maker_order, &taker_p2p_pubkey, &secret, m, t, ¶ms).await; + }, + _ => { + let params = LegacySwapParams { + maker_coin: &maker_coin, + taker_coin: &taker_coin, + uuid: &uuid, + my_conf_settings: &my_conf_settings, + my_persistent_pub: &my_persistent_pub, + maker_amount: &maker_amount, + taker_amount: &taker_amount, + locktime: &lock_time, + }; + start_maker_legacy_swap(&ctx, maker_order, taker_pubkey, secret, params).await + }, } }; @@ -3049,13 +3059,94 @@ fn lp_connect_start_bob(ctx: MmArc, maker_match: MakerMatch, maker_order: MakerO spawner.spawn_with_settings(fut, settings); } +async fn start_maker_legacy_swap( + ctx: &MmArc, + maker_order: MakerOrder, + taker_pubkey: bits256, + secret: H256, + params: LegacySwapParams<'_>, +) { + if let Err(e) = insert_new_swap_to_db( + ctx.clone(), + params.maker_coin.ticker(), + params.taker_coin.ticker(), + *params.uuid, + now_sec(), + LEGACY_SWAP_TYPE, + ) + .await + { + error!("Error {} on new swap insertion", e); + } + + let maker_swap = MakerSwap::new( + ctx.clone(), + taker_pubkey, + params.maker_amount.to_decimal(), + params.taker_amount.to_decimal(), + *params.my_persistent_pub, + *params.uuid, + Some(maker_order.uuid), + *params.my_conf_settings, + params.maker_coin.clone(), + params.taker_coin.clone(), + *params.locktime, + maker_order.p2p_privkey.map(SerializableSecp256k1Keypair::into_inner), + secret, + ); + run_maker_swap(RunMakerSwapInput::StartNew(maker_swap), ctx.clone()).await; +} + +async fn start_maker_swap_state_machine< + MakerCoin: MmCoin + MakerCoinSwapOpsV2 + Clone, + TakerCoin: MmCoin + TakerCoinSwapOpsV2 + Clone, +>( + ctx: &MmArc, + maker_order: &MakerOrder, + taker_p2p_pubkey: &Secp256k1Pubkey, + secret: &H256, + maker_coin: &MakerCoin, + taker_coin: &TakerCoin, + params: &StateMachineParams<'_>, +) { + let mut maker_swap_state_machine = MakerSwapStateMachine { + storage: MakerSwapStorage::new(ctx.clone()), + abortable_system: ctx + .abortable_system + .create_subsystem() + .expect("create_subsystem should not fail"), + ctx: ctx.clone(), + started_at: now_sec(), + maker_coin: maker_coin.clone(), + maker_volume: params.maker_amount.clone(), + secret: *secret, + taker_coin: taker_coin.clone(), + dex_fee: dex_fee_amount_from_taker_coin(taker_coin, maker_coin.ticker(), params.taker_amount), + taker_volume: params.taker_amount.clone(), + taker_premium: Default::default(), + conf_settings: *params.my_conf_settings, + p2p_topic: swap_v2_topic(params.uuid), + uuid: *params.uuid, + p2p_keypair: maker_order.p2p_privkey.map(SerializableSecp256k1Keypair::into_inner), + secret_hash_algo: *params.secret_hash_algo, + lock_duration: *params.locktime, + taker_p2p_pubkey: *taker_p2p_pubkey, + require_taker_payment_spend_confirm: true, + }; + #[allow(clippy::box_default)] + maker_swap_state_machine + .run(Box::new(maker_swap_v2::Initialize::default())) + .await + .error_log(); +} + fn lp_connected_alice(ctx: MmArc, taker_order: TakerOrder, taker_match: TakerMatch, maker_p2p_pubkey: PublicKey) { let spawner = ctx.spawner(); let uuid = taker_match.reserved.taker_order_uuid; let fut = async move { // aka "taker_loop" - let maker = bits256::from(taker_match.reserved.sender_pubkey.0); + let maker_pubkey = bits256::from(taker_match.reserved.sender_pubkey.0); let taker_coin_ticker = taker_order.taker_coin_ticker(); let taker_coin = match lp_coinfind(&ctx, taker_coin_ticker).await { Ok(Some(c)) => c, @@ -3126,86 +3217,74 @@ fn lp_connected_alice(ctx: MmArc, taker_order: TakerOrder, taker_match: TakerMat uuid ); - let now = now_sec(); - if ctx.use_trading_proto_v2() { - let taker_secret = match generate_secret() { - Ok(s) => s.into(), - Err(e) => { - error!("Error {} on secret generation", e); - return; - }, + // TODO add bob swap protocol version check once protocol versioning is implemented + if !ctx.use_trading_proto_v2() { + let params = LegacySwapParams { + maker_coin: &maker_coin, + taker_coin: &taker_coin, + uuid: &uuid, + my_conf_settings: &my_conf_settings, + my_persistent_pub: &my_persistent_pub, + maker_amount: &maker_amount, + taker_amount: &taker_amount, + locktime: &locktime, }; - let secret_hash_algo = detect_secret_hash_algo(&maker_coin, &taker_coin); - match (maker_coin, taker_coin) { - (MmCoinEnum::UtxoCoin(m), MmCoinEnum::UtxoCoin(t)) => { - let mut taker_swap_state_machine = TakerSwapStateMachine { - storage: TakerSwapStorage::new(ctx.clone()), - abortable_system: ctx - .abortable_system - .create_subsystem() - .expect("create_subsystem should not fail"), - ctx, - started_at: now, - lock_duration: locktime, - maker_coin: m.clone(), - maker_volume: maker_amount, - taker_coin: t.clone(), - dex_fee: dex_fee_amount_from_taker_coin(&t, maker_coin_ticker, &taker_amount), - taker_volume: taker_amount, - taker_premium: Default::default(), - secret_hash_algo, - conf_settings: my_conf_settings, - p2p_topic: swap_v2_topic(&uuid), - uuid, - p2p_keypair: taker_order.p2p_privkey.map(SerializableSecp256k1Keypair::into_inner), - taker_secret, - maker_p2p_pubkey: match maker_p2p_pubkey { - PublicKey::Secp256k1(pubkey) => pubkey.into(), - }, - require_maker_payment_confirm_before_funding_spend: true, - }; - #[allow(clippy::box_default)] - taker_swap_state_machine - .run(Box::new(taker_swap_v2::Initialize::default())) - .await - .error_log(); - }, - _ => todo!("implement fallback to the old protocol here"), - } - } else { - #[cfg(any(test, feature = "run-docker-tests"))] - let fail_at = std::env::var("TAKER_FAIL_AT").map(FailAt::from).ok(); + start_taker_legacy_swap(&ctx, taker_order, maker_pubkey, params).await; + return; + } - if let Err(e) = insert_new_swap_to_db( - ctx.clone(), - taker_coin.ticker(), - maker_coin.ticker(), - uuid, - now, - LEGACY_SWAP_TYPE, - ) - .await - { - error!("Error {} on new swap insertion", e); - } + let taker_secret = match generate_secret() { + Ok(s) => s.into(), + Err(e) => { + error!("Error {} on secret generation", e); + return; + }, + }; - let taker_swap = TakerSwap::new( - ctx.clone(), - maker, - maker_amount, - taker_amount, - my_persistent_pub, - uuid, - Some(uuid), - my_conf_settings, - maker_coin, - taker_coin, - locktime, - taker_order.p2p_privkey.map(SerializableSecp256k1Keypair::into_inner), - #[cfg(any(test, feature = "run-docker-tests"))] - fail_at, - ); - run_taker_swap(RunTakerSwapInput::StartNew(taker_swap), ctx).await + // Ensure detect_secret_hash_algo_v2 returns the correct secret hash algorithm when adding new coin support in TPU. + let params = StateMachineParams { + secret_hash_algo: &detect_secret_hash_algo_v2(&maker_coin, &taker_coin), + uuid: &uuid, + my_conf_settings: &my_conf_settings, + locktime: &locktime, + maker_amount: &maker_amount, + taker_amount: &taker_amount, + }; + let maker_p2p_pubkey = match maker_p2p_pubkey { + PublicKey::Secp256k1(pubkey) => pubkey.into(), + }; + + // TODO try to handle it more gracefully during project redesign + match (&maker_coin, &taker_coin) { + (MmCoinEnum::UtxoCoin(m), MmCoinEnum::UtxoCoin(t)) => { + start_taker_swap_state_machine(&ctx, &taker_order, &maker_p2p_pubkey, &taker_secret, m, t, ¶ms) + .await; + }, + (MmCoinEnum::EthCoin(m), MmCoinEnum::EthCoin(t)) => { + start_taker_swap_state_machine(&ctx, &taker_order, &maker_p2p_pubkey, &taker_secret, m, t, ¶ms) + .await; + }, + (MmCoinEnum::UtxoCoin(m), MmCoinEnum::EthCoin(t)) => { + start_taker_swap_state_machine(&ctx, &taker_order, &maker_p2p_pubkey, &taker_secret, m, t, ¶ms) + .await; + }, + (MmCoinEnum::EthCoin(m), MmCoinEnum::UtxoCoin(t)) => { + start_taker_swap_state_machine(&ctx, &taker_order, &maker_p2p_pubkey, &taker_secret, m, t, ¶ms) + .await; + }, + _ => { + let params = LegacySwapParams { + maker_coin: &maker_coin, + taker_coin: &taker_coin, + uuid: &uuid, + my_conf_settings: &my_conf_settings, + my_persistent_pub: &my_persistent_pub, + maker_amount: &maker_amount, + taker_amount: &taker_amount, + locktime: &locktime, + }; + start_taker_legacy_swap(&ctx, taker_order, maker_pubkey, params).await; + }, } }; @@ -3213,6 +3292,91 @@ fn lp_connected_alice(ctx: MmArc, taker_order: TakerOrder, taker_match: TakerMat spawner.spawn_with_settings(fut, settings) } +async fn start_taker_legacy_swap( + ctx: &MmArc, + taker_order: TakerOrder, + maker_pubkey: bits256, + params: LegacySwapParams<'_>, +) { + #[cfg(any(test, feature = "run-docker-tests"))] + let fail_at = std::env::var("TAKER_FAIL_AT").map(FailAt::from).ok(); + + if let Err(e) = insert_new_swap_to_db( + ctx.clone(), + params.taker_coin.ticker(), + params.maker_coin.ticker(), + *params.uuid, + now_sec(), + LEGACY_SWAP_TYPE, + ) + .await + { + error!("Error {} on new swap insertion", e); + } + + let taker_swap = TakerSwap::new( + ctx.clone(), + maker_pubkey, + params.maker_amount.clone(), + params.taker_amount.clone(), + *params.my_persistent_pub, + *params.uuid, + Some(*params.uuid), + *params.my_conf_settings, + params.maker_coin.clone(), + params.taker_coin.clone(), + *params.locktime, + taker_order.p2p_privkey.map(SerializableSecp256k1Keypair::into_inner), + #[cfg(any(test, feature = "run-docker-tests"))] + fail_at, + ); + run_taker_swap(RunTakerSwapInput::StartNew(taker_swap), ctx.clone()).await +} + +async fn start_taker_swap_state_machine< + MakerCoin: MmCoin + MakerCoinSwapOpsV2 + Clone, + TakerCoin: MmCoin + TakerCoinSwapOpsV2 + Clone, +>( + ctx: &MmArc, + taker_order: &TakerOrder, + maker_p2p_pubkey: &Secp256k1Pubkey, + taker_secret: &H256, + maker_coin: &MakerCoin, + taker_coin: &TakerCoin, + params: &StateMachineParams<'_>, +) { + let mut taker_swap_state_machine = TakerSwapStateMachine { + storage: TakerSwapStorage::new(ctx.clone()), + abortable_system: ctx + .abortable_system + .create_subsystem() + .expect("create_subsystem should not fail"), + ctx: ctx.clone(), + started_at: now_sec(), + lock_duration: *params.locktime, + maker_coin: maker_coin.clone(), + maker_volume: params.maker_amount.clone(), + taker_coin: taker_coin.clone(), + dex_fee: dex_fee_amount_from_taker_coin(taker_coin, taker_order.maker_coin_ticker(), params.taker_amount), + taker_volume: params.taker_amount.clone(), + taker_premium: Default::default(), + secret_hash_algo: *params.secret_hash_algo, + conf_settings: *params.my_conf_settings, + p2p_topic: swap_v2_topic(params.uuid), + uuid: *params.uuid, + p2p_keypair: taker_order.p2p_privkey.map(SerializableSecp256k1Keypair::into_inner), + taker_secret: *taker_secret, + maker_p2p_pubkey: *maker_p2p_pubkey, + require_maker_payment_confirm_before_funding_spend: true, + require_maker_payment_spend_confirm: true, + }; + #[allow(clippy::box_default)] + taker_swap_state_machine + .run(Box::new(taker_swap_v2::Initialize::default())) + .await + .error_log(); +} + pub async fn lp_ordermatch_loop(ctx: MmArc) { // lp_ordermatch_loop is spawned only if CryptoCtx is initialized let my_pubsecp = CryptoCtx::from_ctx(&ctx) diff --git a/mm2src/mm2_main/src/lp_swap.rs b/mm2src/mm2_main/src/lp_swap.rs index 507b9a6f51..6275123b44 100644 --- a/mm2src/mm2_main/src/lp_swap.rs +++ b/mm2src/mm2_main/src/lp_swap.rs @@ -61,7 +61,7 @@ use super::lp_network::P2PRequestResult; use crate::lp_network::{broadcast_p2p_msg, Libp2pPeerId, P2PProcessError, P2PProcessResult, P2PRequestError}; use crate::lp_swap::maker_swap_v2::{MakerSwapStateMachine, MakerSwapStorage}; use crate::lp_swap::taker_swap_v2::{TakerSwapStateMachine, TakerSwapStorage}; -use bitcrypto::{dhash160, sha256}; +use bitcrypto::sha256; use coins::{lp_coinfind, lp_coinfind_or_err, CoinFindError, DexFee, MmCoin, MmCoinEnum, TradeFee, TransactionEnum}; use common::log::{debug, warn}; use common::now_sec; @@ -84,7 +84,6 @@ use secp256k1::{PublicKey, SecretKey, Signature}; use serde::Serialize; use serde_json::{self as json, Value as Json}; use std::collections::{HashMap, HashSet}; -use std::convert::TryFrom; use std::num::NonZeroUsize; use std::path::PathBuf; use std::str::FromStr; @@ -119,6 +118,7 @@ mod trade_preimage; pub use check_balance::{check_other_coin_balance_for_swap, CheckBalanceError, CheckBalanceResult}; use coins::utxo::utxo_standard::UtxoStandardCoin; +use crypto::secret_hash_algo::SecretHashAlgo; use crypto::CryptoCtx; use keys::{KeyPair, SECP_SIGN, SECP_VERIFY}; use maker_swap::MakerSwapEvent; @@ -1626,42 +1626,6 @@ pub async fn active_swaps_rpc(ctx: MmArc, req: Json) -> Result> Ok(try_s!(Response::builder().body(res))) } -/// Algorithm used to hash swap secret. -#[derive(Clone, Copy, Debug, Deserialize, Serialize, Default)] -pub enum SecretHashAlgo { - /// ripemd160(sha256(secret)) - #[default] - DHASH160 = 1, - /// sha256(secret) - SHA256 = 2, -} - -#[derive(Debug, Display)] -pub struct UnsupportedSecretHashAlgo(u8); - -impl std::error::Error for UnsupportedSecretHashAlgo {} - -impl TryFrom for SecretHashAlgo { - type Error = UnsupportedSecretHashAlgo; - - fn try_from(value: u8) -> Result { - match value { - 1 => Ok(SecretHashAlgo::DHASH160), - 2 => Ok(SecretHashAlgo::SHA256), - unsupported => Err(UnsupportedSecretHashAlgo(unsupported)), - } - } -} - -impl SecretHashAlgo { - fn hash_secret(&self, secret: &[u8]) -> Vec { - match self { - SecretHashAlgo::DHASH160 => dhash160(secret).take().into(), - SecretHashAlgo::SHA256 => sha256(secret).take().into(), - } - } -} - // Todo: Maybe add a secret_hash_algo method to the SwapOps trait instead /// Selects secret hash algorithm depending on types of coins being swapped #[cfg(not(target_arch = "wasm32"))] @@ -1686,6 +1650,19 @@ pub fn detect_secret_hash_algo(maker_coin: &MmCoinEnum, taker_coin: &MmCoinEnum) } } +/// Determines the secret hash algorithm for TPU, prioritizing SHA256 if either coin supports it. +/// # Attention +/// When adding new coins support, ensure their `secret_hash_algo_v2` implementation returns correct secret hash algorithm. +pub fn detect_secret_hash_algo_v2(maker_coin: &MmCoinEnum, taker_coin: &MmCoinEnum) -> SecretHashAlgo { + let maker_algo = maker_coin.secret_hash_algo_v2(); + let taker_algo = taker_coin.secret_hash_algo_v2(); + if maker_algo == SecretHashAlgo::SHA256 || taker_algo == SecretHashAlgo::SHA256 { + SecretHashAlgo::SHA256 + } else { + SecretHashAlgo::DHASH160 + } +} + pub struct SwapPubkeys { pub maker: String, pub taker: String, diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap.rs b/mm2src/mm2_main/src/lp_swap/maker_swap.rs index 0eb72b8a71..511248fcd8 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap.rs @@ -8,8 +8,8 @@ use super::{broadcast_my_swap_status, broadcast_p2p_tx_msg, broadcast_swap_msg_e get_locked_amount, recv_swap_msg, swap_topic, taker_payment_spend_deadline, tx_helper_topic, wait_for_maker_payment_conf_until, AtomicSwap, LockedAmount, MySwapInfo, NegotiationDataMsg, NegotiationDataV2, NegotiationDataV3, RecoveredSwap, RecoveredSwapAction, SavedSwap, SavedSwapIo, - SavedTradeFee, SecretHashAlgo, SwapConfirmationsSettings, SwapError, SwapMsg, SwapPubkeys, SwapTxDataMsg, - SwapsContext, TransactionIdentifier, INCLUDE_REFUND_FEE, NO_REFUND_FEE, TAKER_FEE_VALIDATION_ATTEMPTS, + SavedTradeFee, SwapConfirmationsSettings, SwapError, SwapMsg, SwapPubkeys, SwapTxDataMsg, SwapsContext, + TransactionIdentifier, INCLUDE_REFUND_FEE, NO_REFUND_FEE, TAKER_FEE_VALIDATION_ATTEMPTS, TAKER_FEE_VALIDATION_RETRY_DELAY_SECS, WAIT_CONFIRM_INTERVAL_SEC}; use crate::lp_dispatcher::{DispatcherContext, LpEvents}; use crate::lp_network::subscribe_to_topic; @@ -25,6 +25,7 @@ use common::log::{debug, error, info, warn}; use common::{bits256, executor::Timer, now_ms, DEX_FEE_ADDR_RAW_PUBKEY}; use common::{now_sec, wait_until_sec}; use crypto::privkey::SerializableSecp256k1Keypair; +use crypto::secret_hash_algo::SecretHashAlgo; use crypto::CryptoCtx; use futures::{compat::Future01CompatExt, select, FutureExt}; use keys::KeyPair; @@ -141,7 +142,8 @@ impl TakerNegotiationData { pub struct MakerSwapData { pub taker_coin: String, pub maker_coin: String, - pub taker: H256Json, + #[serde(alias = "taker")] + pub taker_pubkey: H256Json, pub secret: H256Json, pub secret_hash: Option, pub my_persistent_pub: H264Json, @@ -216,7 +218,7 @@ pub struct MakerSwap { maker_amount: BigDecimal, taker_amount: BigDecimal, my_persistent_pub: H264, - taker: bits256, + taker_pubkey: bits256, uuid: Uuid, my_order_uuid: Option, taker_payment_lock: AtomicU64, @@ -356,7 +358,7 @@ impl MakerSwap { #[allow(clippy::too_many_arguments)] pub fn new( ctx: MmArc, - taker: bits256, + taker_pubkey: bits256, maker_amount: BigDecimal, taker_amount: BigDecimal, my_persistent_pub: H264, @@ -376,7 +378,7 @@ impl MakerSwap { maker_amount, taker_amount, my_persistent_pub, - taker, + taker_pubkey, uuid, my_order_uuid, taker_payment_lock: AtomicU64::new(0), @@ -548,7 +550,7 @@ impl MakerSwap { let data = MakerSwapData { taker_coin: self.taker_coin.ticker().to_owned(), maker_coin: self.maker_coin.ticker().to_owned(), - taker: self.taker.bytes.into(), + taker_pubkey: self.taker_pubkey.bytes.into(), secret: self.secret.into(), secret_hash: Some(self.secret_hash().into()), started_at, @@ -1338,7 +1340,7 @@ impl MakerSwap { } let mut taker = bits256::from([0; 32]); - taker.bytes = data.taker.0; + taker.bytes = data.taker_pubkey.0; let crypto_ctx = try_s!(CryptoCtx::from_ctx(&ctx)); let my_persistent_pub = H264::from(&**crypto_ctx.mm2_internal_key_pair().public()); @@ -1818,7 +1820,7 @@ impl MakerSavedSwap { event: MakerSwapEvent::Started(MakerSwapData { taker_coin: "".to_string(), maker_coin: "".to_string(), - taker: Default::default(), + taker_pubkey: Default::default(), secret: Default::default(), secret_hash: None, my_persistent_pub: Default::default(), @@ -2097,7 +2099,7 @@ pub async fn run_maker_swap(swap: RunMakerSwapInput, ctx: MmArc) { let running_swap = Arc::new(swap); let weak_ref = Arc::downgrade(&running_swap); let swap_ctx = SwapsContext::from_ctx(&ctx).unwrap(); - swap_ctx.init_msg_store(running_swap.uuid, running_swap.taker); + swap_ctx.init_msg_store(running_swap.uuid, running_swap.taker_pubkey); swap_ctx.running_swaps.lock().unwrap().push(weak_ref); let mut swap_fut = Box::pin( async move { @@ -2124,7 +2126,7 @@ pub async fn run_maker_swap(swap: RunMakerSwapInput, ctx: MmArc) { if event.should_ban_taker() { ban_pubkey_on_failed_swap( &ctx, - running_swap.taker.bytes.into(), + running_swap.taker_pubkey.bytes.into(), &running_swap.uuid, event.clone().into(), ) diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs b/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs index d0e667a752..bf79c402a0 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs @@ -3,20 +3,22 @@ use super::{swap_v2_topic, LockedAmount, LockedAmountInfo, SavedTradeFee, SwapsC NEGOTIATION_TIMEOUT_SEC}; use crate::lp_swap::maker_swap::MakerSwapPreparedParams; use crate::lp_swap::swap_lock::SwapLock; -use crate::lp_swap::{broadcast_swap_v2_msg_every, check_balance_for_maker_swap, recv_swap_v2_msg, SecretHashAlgo, +use crate::lp_swap::{broadcast_swap_v2_msg_every, check_balance_for_maker_swap, recv_swap_v2_msg, SwapConfirmationsSettings, TransactionIdentifier, MAKER_SWAP_V2_TYPE, MAX_STARTED_AT_DIFF}; use crate::lp_swap::{swap_v2_pb::*, NO_REFUND_FEE}; use async_trait::async_trait; use bitcrypto::{dhash160, sha256}; -use coins::{CanRefundHtlc, ConfirmPaymentInput, DexFee, FeeApproxStage, FundingTxSpend, GenTakerFundingSpendArgs, - GenTakerPaymentSpendArgs, MakerCoinSwapOpsV2, MmCoin, ParseCoinAssocTypes, RefundMakerPaymentSecretArgs, - RefundMakerPaymentTimelockArgs, SearchForFundingSpendErr, SendMakerPaymentArgs, SwapTxTypeWithSecretHash, - TakerCoinSwapOpsV2, ToBytes, TradePreimageValue, Transaction, TxPreimageWithSig, ValidateTakerFundingArgs}; +use coins::{AddrToString, CanRefundHtlc, ConfirmPaymentInput, DexFee, FeeApproxStage, FundingTxSpend, + GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, MakerCoinSwapOpsV2, MmCoin, ParseCoinAssocTypes, + RefundMakerPaymentSecretArgs, RefundMakerPaymentTimelockArgs, SearchForFundingSpendErr, + SendMakerPaymentArgs, SwapTxTypeWithSecretHash, TakerCoinSwapOpsV2, ToBytes, TradePreimageValue, + Transaction, TxPreimageWithSig, ValidateTakerFundingArgs}; use common::executor::abortable_queue::AbortableQueue; use common::executor::{AbortableSystem, Timer}; use common::log::{debug, error, info, warn}; use common::{now_sec, Future01CompatExt, DEX_FEE_ADDR_RAW_PUBKEY}; use crypto::privkey::SerializableSecp256k1Keypair; +use crypto::secret_hash_algo::SecretHashAlgo; use keys::KeyPair; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; @@ -121,6 +123,7 @@ pub enum MakerSwapEvent { maker_payment: TransactionIdentifier, taker_payment: TransactionIdentifier, taker_payment_spend: TransactionIdentifier, + negotiation_data: StoredNegotiationData, }, /// Swap has been aborted before maker payment was sent. Aborted { reason: AbortReason }, @@ -387,6 +390,8 @@ pub struct MakerSwapStateMachine @@ -400,6 +405,9 @@ impl u64 { self.started_at + 2 * self.lock_duration } + #[inline] + fn taker_payment_spend_conf_timeout(&self) -> u64 { self.started_at + 3 * self.lock_duration } + /// Returns secret hash generated using selected [SecretHashAlgo]. fn secret_hash(&self) -> Vec { match self.secret_hash_algo { @@ -593,6 +601,7 @@ impl Box::new(TakerPaymentSpent { maker_coin_start_block, taker_coin_start_block, @@ -608,6 +617,11 @@ impl return MmError::err(SwapRecreateError::SwapAborted), MakerSwapEvent::Completed => return MmError::err(SwapRecreateError::SwapCompleted), @@ -645,6 +659,7 @@ impl, state_machine: &mut Self::StateMachine) -> StateResult { let unique_data = state_machine.unique_data(); + let taker_coin_address = state_machine.taker_coin.my_addr().await; let maker_negotiation_msg = MakerNegotiation { started_at: state_machine.started_at, @@ -909,7 +925,7 @@ impl TransitionFrom> for MakerPaymentRefundRequired { } +impl + TransitionFrom> for MakerPaymentRefundRequired +{ +} #[async_trait] impl State @@ -1707,6 +1728,7 @@ impl, } impl @@ -1754,6 +1777,27 @@ impl; async fn on_changed(self: Box, state_machine: &mut Self::StateMachine) -> StateResult { + if state_machine.require_taker_payment_spend_confirm { + let input = ConfirmPaymentInput { + payment_tx: self.taker_payment_spend.tx_hex(), + confirmations: state_machine.conf_settings.taker_coin_confs, + requires_nota: state_machine.conf_settings.taker_coin_nota, + wait_until: state_machine.taker_payment_spend_conf_timeout(), + check_every: 10, + }; + + if let Err(e) = state_machine.taker_coin.wait_for_confirmations(input).compat().await { + let next_state = MakerPaymentRefundRequired { + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + negotiation_data: self.negotiation_data, + maker_payment: self.maker_payment, + reason: MakerPaymentRefundReason::TakerPaymentSpendNotConfirmedInTime(e.to_string()), + }; + return Self::change_state(next_state, state_machine).await; + } + } + Self::change_state(Completed::new(), state_machine).await } } @@ -1779,6 +1823,7 @@ impl RecreateSwapRe let maker_started_event = MakerSwapEvent::Started(MakerSwapData { taker_coin: started_event.taker_coin, maker_coin: started_event.maker_coin, - taker: H256Json::from(taker_p2p_pubkey), + taker_pubkey: H256Json::from(taker_p2p_pubkey), // We could parse the `TakerSwapEvent::TakerPaymentSpent` event. // As for now, don't try to find the secret in the events since we can refund without it. secret: H256Json::default(), @@ -323,7 +323,7 @@ async fn recreate_taker_swap(ctx: MmArc, maker_swap: MakerSavedSwap) -> Recreate let taker_started_event = TakerSwapEvent::Started(TakerSwapData { taker_coin: started_event.taker_coin, maker_coin: started_event.maker_coin.clone(), - maker: H256Json::from(maker_p2p_pubkey), + maker_pubkey: H256Json::from(maker_p2p_pubkey), my_persistent_pub: negotiated_event.taker_pubkey, lock_duration: started_event.lock_duration, maker_amount: started_event.maker_amount, diff --git a/mm2src/mm2_main/src/lp_swap/swap_v2_common.rs b/mm2src/mm2_main/src/lp_swap/swap_v2_common.rs index 686d263d5a..52f1690335 100644 --- a/mm2src/mm2_main/src/lp_swap/swap_v2_common.rs +++ b/mm2src/mm2_main/src/lp_swap/swap_v2_common.rs @@ -345,6 +345,7 @@ pub(super) async fn swap_kickstart_handler< }; }; + // TODO add ETH support let (maker_coin, taker_coin) = match (maker_coin, taker_coin) { (MmCoinEnum::UtxoCoin(m), MmCoinEnum::UtxoCoin(t)) => (m, t), _ => { diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap.rs b/mm2src/mm2_main/src/lp_swap/taker_swap.rs index c7b1cf59a9..78a9308f21 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap.rs @@ -463,7 +463,7 @@ pub async fn run_taker_swap(swap: RunTakerSwapInput, ctx: MmArc) { let running_swap = Arc::new(swap); let weak_ref = Arc::downgrade(&running_swap); let swap_ctx = SwapsContext::from_ctx(&ctx).unwrap(); - swap_ctx.init_msg_store(running_swap.uuid, running_swap.maker); + swap_ctx.init_msg_store(running_swap.uuid, running_swap.maker_pubkey); swap_ctx.running_swaps.lock().unwrap().push(weak_ref); let mut swap_fut = Box::pin( @@ -484,7 +484,7 @@ pub async fn run_taker_swap(swap: RunTakerSwapInput, ctx: MmArc) { if event.should_ban_maker() { ban_pubkey_on_failed_swap( &ctx, - running_swap.maker.bytes.into(), + running_swap.maker_pubkey.bytes.into(), &running_swap.uuid, event.clone().into(), ) @@ -528,7 +528,8 @@ pub async fn run_taker_swap(swap: RunTakerSwapInput, ctx: MmArc) { pub struct TakerSwapData { pub taker_coin: String, pub maker_coin: String, - pub maker: H256Json, + #[serde(alias = "maker")] + pub maker_pubkey: H256Json, pub my_persistent_pub: H264Json, pub lock_duration: u64, pub maker_amount: BigDecimal, @@ -618,7 +619,7 @@ pub struct TakerSwap { pub maker_amount: MmNumber, pub taker_amount: MmNumber, my_persistent_pub: H264, - maker: bits256, + maker_pubkey: bits256, uuid: Uuid, my_order_uuid: Option, pub maker_payment_lock: AtomicU64, @@ -901,7 +902,7 @@ impl TakerSwap { #[allow(clippy::too_many_arguments)] pub fn new( ctx: MmArc, - maker: bits256, + maker_pubkey: bits256, maker_amount: MmNumber, taker_amount: MmNumber, my_persistent_pub: H264, @@ -920,7 +921,7 @@ impl TakerSwap { maker_amount, taker_amount, my_persistent_pub, - maker, + maker_pubkey, uuid, my_order_uuid, maker_payment_confirmed: AtomicBool::new(false), @@ -1111,7 +1112,7 @@ impl TakerSwap { let data = TakerSwapData { taker_coin: self.taker_coin.ticker().to_owned(), maker_coin: self.maker_coin.ticker().to_owned(), - maker: self.maker.bytes.into(), + maker_pubkey: self.maker_pubkey.bytes.into(), started_at, lock_duration: self.payment_locktime, maker_amount: self.maker_amount.to_decimal(), @@ -2043,7 +2044,7 @@ impl TakerSwap { let my_persistent_pub = H264::from(&**crypto_ctx.mm2_internal_key_pair().public()); let mut maker = bits256::from([0; 32]); - maker.bytes = data.maker.0; + maker.bytes = data.maker_pubkey.0; let conf_settings = SwapConfirmationsSettings { maker_coin_confs: data.maker_payment_confirmations, maker_coin_nota: data diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs b/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs index 29f3d07277..67dd2b3dbb 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs @@ -3,12 +3,11 @@ use super::{LockedAmount, LockedAmountInfo, SavedTradeFee, SwapsContext, TakerSw NEGOTIATE_SEND_INTERVAL, NEGOTIATION_TIMEOUT_SEC}; use crate::lp_swap::swap_lock::SwapLock; use crate::lp_swap::{broadcast_swap_v2_msg_every, check_balance_for_taker_swap, recv_swap_v2_msg, swap_v2_topic, - SecretHashAlgo, SwapConfirmationsSettings, TransactionIdentifier, MAX_STARTED_AT_DIFF, - TAKER_SWAP_V2_TYPE}; + SwapConfirmationsSettings, TransactionIdentifier, MAX_STARTED_AT_DIFF, TAKER_SWAP_V2_TYPE}; use crate::lp_swap::{swap_v2_pb::*, NO_REFUND_FEE}; use async_trait::async_trait; use bitcrypto::{dhash160, sha256}; -use coins::{CanRefundHtlc, ConfirmPaymentInput, DexFee, FeeApproxStage, GenTakerFundingSpendArgs, +use coins::{AddrToString, CanRefundHtlc, ConfirmPaymentInput, DexFee, FeeApproxStage, GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, MakerCoinSwapOpsV2, MmCoin, ParseCoinAssocTypes, RefundFundingSecretArgs, RefundTakerPaymentArgs, SendTakerFundingArgs, SpendMakerPaymentArgs, SwapTxTypeWithSecretHash, TakerCoinSwapOpsV2, ToBytes, TradeFee, TradePreimageValue, Transaction, TxPreimageWithSig, @@ -18,6 +17,7 @@ use common::executor::{AbortableSystem, Timer}; use common::log::{debug, error, info, warn}; use common::{Future01CompatExt, DEX_FEE_ADDR_RAW_PUBKEY}; use crypto::privkey::SerializableSecp256k1Keypair; +use crypto::secret_hash_algo::SecretHashAlgo; use keys::KeyPair; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; @@ -141,6 +141,7 @@ pub enum TakerSwapEvent { taker_payment: TransactionIdentifier, taker_payment_spend: TransactionIdentifier, maker_payment_spend: TransactionIdentifier, + negotiation_data: StoredNegotiationData, }, /// Swap has been finished with taker funding tx refund TakerFundingRefunded { @@ -421,6 +422,8 @@ pub struct TakerSwapStateMachine @@ -432,6 +435,9 @@ impl u64 { self.started_at + self.lock_duration } + #[inline] + fn maker_payment_spend_conf_timeout(&self) -> u64 { self.started_at + 4 * self.lock_duration } + fn unique_data(&self) -> Vec { self.uuid.as_bytes().to_vec() } /// Returns secret hash generated using selected [SecretHashAlgo]. @@ -702,6 +708,7 @@ impl Box::new(MakerPaymentSpent { maker_coin_start_block, taker_coin_start_block, @@ -721,6 +728,11 @@ impl return MmError::err(SwapRecreateError::SwapAborted), TakerSwapEvent::Completed => return MmError::err(SwapRecreateError::SwapCompleted), @@ -762,6 +774,7 @@ impl Negotiation StoredNegotiationData { maker_payment_locktime: self.maker_payment_locktime, maker_secret_hash: self.maker_secret_hash.clone().into(), - taker_coin_maker_address: self.taker_coin_maker_address.to_string(), + taker_coin_maker_address: self.taker_coin_maker_address.addr_to_string(), maker_coin_htlc_pub_from_maker: self.maker_coin_htlc_pub_from_maker.to_bytes().into(), taker_coin_htlc_pub_from_maker: self.taker_coin_htlc_pub_from_maker.to_bytes().into(), maker_coin_swap_contract: self.maker_coin_swap_contract.clone().map(|b| b.into()), @@ -1573,7 +1586,7 @@ impl TransitionFrom> for TakerPaymentRefundRequired { } +impl + TransitionFrom> for TakerPaymentRefundRequired +{ +} #[async_trait] impl State @@ -2004,7 +2022,7 @@ impl s, @@ -2091,7 +2105,7 @@ impl, } impl @@ -2186,6 +2202,7 @@ impl; async fn on_changed(self: Box, state_machine: &mut Self::StateMachine) -> StateResult { + if state_machine.require_maker_payment_spend_confirm { + let input = ConfirmPaymentInput { + payment_tx: self.maker_payment_spend.tx_hex(), + confirmations: state_machine.conf_settings.maker_coin_confs, + requires_nota: state_machine.conf_settings.maker_coin_nota, + wait_until: state_machine.maker_payment_spend_conf_timeout(), + check_every: 10, + }; + + if let Err(e) = state_machine.maker_coin.wait_for_confirmations(input).compat().await { + let next_state = TakerPaymentRefundRequired { + taker_payment: self.taker_payment, + negotiation_data: self.negotiation_data, + reason: TakerPaymentRefundReason::MakerPaymentSpendNotConfirmedInTime(e.to_string()), + }; + return Self::change_state(next_state, state_machine).await; + } + } + Self::change_state(Completed::new(), state_machine).await } } diff --git a/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs b/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs index abcdc2ce9a..d922ba8f27 100644 --- a/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs @@ -1484,10 +1484,10 @@ fn send_and_refund_taker_funding_by_secret_eth() { let taker_coin = get_or_create_sepolia_coin(&MM_CTX1, SEPOLIA_TAKER_PRIV, ETH, ð_sepolia_conf(), false); let maker_coin = get_or_create_sepolia_coin(&MM_CTX, SEPOLIA_MAKER_PRIV, ETH, ð_sepolia_conf(), false); - let taker_secret = vec![0; 32]; - let taker_secret_hash = sha256(&taker_secret).to_vec(); - let maker_secret = vec![1; 32]; - let maker_secret_hash = sha256(&maker_secret).to_vec(); + let taker_secret = &[0; 32]; + let taker_secret_hash = sha256(taker_secret).to_vec(); + let maker_secret = &[1; 32]; + let maker_secret_hash = sha256(maker_secret).to_vec(); let funding_time_lock = now_sec() + 3000; let payment_time_lock = now_sec() + 1000; @@ -1519,7 +1519,7 @@ fn send_and_refund_taker_funding_by_secret_eth() { funding_time_lock, payment_time_lock, maker_pubkey: maker_pub, - taker_secret: &taker_secret, + taker_secret, taker_secret_hash: &taker_secret_hash, maker_secret_hash: &maker_secret_hash, dex_fee, @@ -1547,9 +1547,9 @@ fn send_and_refund_taker_funding_by_secret_erc20() { let taker_coin = get_or_create_sepolia_coin(&MM_CTX1, SEPOLIA_TAKER_PRIV, ERC20, erc20_conf, true); let maker_coin = get_or_create_sepolia_coin(&MM_CTX1, SEPOLIA_MAKER_PRIV, ERC20, erc20_conf, true); - let taker_secret = vec![0; 32]; - let taker_secret_hash = sha256(&taker_secret).to_vec(); - let maker_secret = vec![1; 32]; + let taker_secret = &[0; 32]; + let taker_secret_hash = sha256(taker_secret).to_vec(); + let maker_secret = [1; 32]; let maker_secret_hash = sha256(&maker_secret).to_vec(); let taker_address = block_on(taker_coin.my_addr()); @@ -1583,7 +1583,7 @@ fn send_and_refund_taker_funding_by_secret_erc20() { funding_time_lock, payment_time_lock, maker_pubkey: &taker_coin.derive_htlc_pubkey_v2(&[]), - taker_secret: &taker_secret, + taker_secret, taker_secret_hash: &taker_secret_hash, maker_secret_hash: &maker_secret_hash, dex_fee, @@ -1609,9 +1609,9 @@ fn send_and_refund_taker_funding_exceed_pre_approve_timelock_eth() { let taker_coin = get_or_create_sepolia_coin(&MM_CTX1, SEPOLIA_TAKER_PRIV, ETH, ð_sepolia_conf(), false); let maker_coin = get_or_create_sepolia_coin(&MM_CTX, SEPOLIA_MAKER_PRIV, ETH, ð_sepolia_conf(), false); - let taker_secret = vec![0; 32]; + let taker_secret = [0; 32]; let taker_secret_hash = sha256(&taker_secret).to_vec(); - let maker_secret = vec![1; 32]; + let maker_secret = [1; 32]; let maker_secret_hash = sha256(&maker_secret).to_vec(); let taker_address = block_on(taker_coin.my_addr()); @@ -1674,10 +1674,10 @@ fn taker_send_approve_and_spend_eth() { let taker_coin = get_or_create_sepolia_coin(&MM_CTX1, SEPOLIA_TAKER_PRIV, ETH, ð_sepolia_conf(), false); let maker_coin = get_or_create_sepolia_coin(&MM_CTX, SEPOLIA_MAKER_PRIV, ETH, ð_sepolia_conf(), false); - let taker_secret = vec![0; 32]; - let taker_secret_hash = sha256(&taker_secret).to_vec(); - let maker_secret = vec![1; 32]; - let maker_secret_hash = sha256(&maker_secret).to_vec(); + let taker_secret = &[0; 32]; + let taker_secret_hash = sha256(taker_secret).to_vec(); + let maker_secret = &[1; 32]; + let maker_secret_hash = sha256(maker_secret).to_vec(); let funding_time_lock = now_sec() + 3000; let payment_time_lock = now_sec() + 600; @@ -1700,6 +1700,7 @@ fn taker_send_approve_and_spend_eth() { swap_unique_data: &[], }; wait_pending_transactions(Address::from_slice(taker_address.as_bytes())); + let taker_coin_start_block = block_on(taker_coin.current_block().compat()).unwrap(); let funding_tx = block_on(taker_coin.send_taker_funding(payment_args)).unwrap(); log!("Taker sent ETH funding, tx hash: {:02x}", funding_tx.tx_hash()); wait_for_confirmations(&taker_coin, &funding_tx, 100); @@ -1742,12 +1743,12 @@ fn taker_send_approve_and_spend_eth() { wait_for_confirmations(&taker_coin, &taker_approve_tx, 100); wait_pending_transactions(Address::from_slice(maker_address.as_bytes())); - let check_taker_approved_tx = block_on(maker_coin.search_for_taker_funding_spend(&taker_approve_tx, 0u64, &[])) + let check_taker_approved_tx = block_on(maker_coin.search_for_taker_funding_spend(&funding_tx, 0u64, &[])) .unwrap() .unwrap(); match check_taker_approved_tx { FundingTxSpend::TransferredToTakerPayment(tx) => { - assert_eq!(tx, taker_approve_tx); + assert_eq!(tx, funding_tx); }, FundingTxSpend::RefundedTimelock(_) | FundingTxSpend::RefundedSecret { .. } => { panic!("Wrong FundingTxSpend variant, expected TransferredToTakerPayment") @@ -1756,7 +1757,7 @@ fn taker_send_approve_and_spend_eth() { let dex_fee_pub = sepolia_taker_swap_v2(); let spend_args = GenTakerPaymentSpendArgs { - taker_tx: &taker_approve_tx, + taker_tx: &funding_tx, time_lock: payment_time_lock, maker_secret_hash: &maker_secret_hash, maker_pub, @@ -1769,11 +1770,14 @@ fn taker_send_approve_and_spend_eth() { }; wait_pending_transactions(Address::from_slice(maker_address.as_bytes())); let spend_tx = - block_on(maker_coin.sign_and_broadcast_taker_payment_spend(&preimage, &spend_args, &maker_secret, &[])) - .unwrap(); + block_on(maker_coin.sign_and_broadcast_taker_payment_spend(&preimage, &spend_args, maker_secret, &[])).unwrap(); log!("Maker spent ETH payment, tx hash: {:02x}", spend_tx.tx_hash()); wait_for_confirmations(&maker_coin, &spend_tx, 100); - block_on(taker_coin.wait_for_taker_payment_spend(&spend_tx, 0u64, payment_time_lock)).unwrap(); + let found_spend_tx = + block_on(taker_coin.find_taker_payment_spend_tx(&taker_approve_tx, taker_coin_start_block, payment_time_lock)) + .unwrap(); + let extracted_maker_secret = block_on(taker_coin.extract_secret_v2(&[], &found_spend_tx)).unwrap(); + assert_eq!(maker_secret, &extracted_maker_secret); } #[cfg(feature = "sepolia-taker-swap-v2-tests")] @@ -1785,9 +1789,9 @@ fn taker_send_approve_and_spend_erc20() { let taker_coin = get_or_create_sepolia_coin(&MM_CTX1, SEPOLIA_TAKER_PRIV, ERC20, erc20_conf, true); let maker_coin = get_or_create_sepolia_coin(&MM_CTX, SEPOLIA_MAKER_PRIV, ERC20, erc20_conf, true); - let taker_secret = vec![0; 32]; + let taker_secret = [0; 32]; let taker_secret_hash = sha256(&taker_secret).to_vec(); - let maker_secret = vec![1; 32]; + let maker_secret = [1; 32]; let maker_secret_hash = sha256(&maker_secret).to_vec(); let funding_time_lock = now_sec() + 3000; let payment_time_lock = now_sec() + 600; @@ -1811,6 +1815,7 @@ fn taker_send_approve_and_spend_erc20() { swap_unique_data: &[], }; wait_pending_transactions(Address::from_slice(taker_address.as_bytes())); + let taker_coin_start_block = block_on(taker_coin.current_block().compat()).unwrap(); let funding_tx = block_on(taker_coin.send_taker_funding(payment_args)).unwrap(); log!("Taker sent ERC20 funding, tx hash: {:02x}", funding_tx.tx_hash()); wait_for_confirmations(&taker_coin, &funding_tx, 100); @@ -1853,12 +1858,12 @@ fn taker_send_approve_and_spend_erc20() { wait_for_confirmations(&taker_coin, &taker_approve_tx, 100); wait_pending_transactions(Address::from_slice(maker_address.as_bytes())); - let check_taker_approved_tx = block_on(maker_coin.search_for_taker_funding_spend(&taker_approve_tx, 0u64, &[])) + let check_taker_approved_tx = block_on(maker_coin.search_for_taker_funding_spend(&funding_tx, 0u64, &[])) .unwrap() .unwrap(); match check_taker_approved_tx { FundingTxSpend::TransferredToTakerPayment(tx) => { - assert_eq!(tx, taker_approve_tx); + assert_eq!(tx, funding_tx); }, FundingTxSpend::RefundedTimelock(_) | FundingTxSpend::RefundedSecret { .. } => { panic!("Wrong FundingTxSpend variant, expected TransferredToTakerPayment") @@ -1867,7 +1872,7 @@ fn taker_send_approve_and_spend_erc20() { let dex_fee_pub = sepolia_taker_swap_v2(); let spend_args = GenTakerPaymentSpendArgs { - taker_tx: &taker_approve_tx, + taker_tx: &funding_tx, time_lock: payment_time_lock, maker_secret_hash: &maker_secret_hash, maker_pub, @@ -1883,7 +1888,11 @@ fn taker_send_approve_and_spend_erc20() { block_on(maker_coin.sign_and_broadcast_taker_payment_spend(&preimage, &spend_args, &maker_secret, &[])) .unwrap(); log!("Maker spent ERC20 payment, tx hash: {:02x}", spend_tx.tx_hash()); - block_on(taker_coin.wait_for_taker_payment_spend(&spend_tx, 0u64, payment_time_lock)).unwrap(); + let found_spend_tx = + block_on(taker_coin.find_taker_payment_spend_tx(&taker_approve_tx, taker_coin_start_block, payment_time_lock)) + .unwrap(); + let extracted_maker_secret = block_on(taker_coin.extract_secret_v2(&[], &found_spend_tx)).unwrap(); + assert_eq!(maker_secret, extracted_maker_secret); } #[cfg(feature = "sepolia-taker-swap-v2-tests")] @@ -1894,9 +1903,9 @@ fn send_and_refund_taker_funding_exceed_payment_timelock_eth() { let taker_coin = get_or_create_sepolia_coin(&MM_CTX1, SEPOLIA_TAKER_PRIV, ETH, ð_sepolia_conf(), false); let maker_coin = get_or_create_sepolia_coin(&MM_CTX, SEPOLIA_MAKER_PRIV, ETH, ð_sepolia_conf(), false); - let taker_secret = vec![0; 32]; + let taker_secret = [0; 32]; let taker_secret_hash = sha256(&taker_secret).to_vec(); - let maker_secret = vec![1; 32]; + let maker_secret = [1; 32]; let maker_secret_hash = sha256(&maker_secret).to_vec(); let funding_time_lock = now_sec() + 3000; let payment_time_lock = now_sec() - 1000; @@ -1979,9 +1988,9 @@ fn send_and_refund_taker_funding_exceed_payment_timelock_erc20() { let taker_coin = get_or_create_sepolia_coin(&MM_CTX1, SEPOLIA_TAKER_PRIV, ERC20, erc20_conf, true); let maker_coin = get_or_create_sepolia_coin(&MM_CTX, SEPOLIA_MAKER_PRIV, ERC20, erc20_conf, true); - let taker_secret = vec![0; 32]; + let taker_secret = [0; 32]; let taker_secret_hash = sha256(&taker_secret).to_vec(); - let maker_secret = vec![1; 32]; + let maker_secret = [1; 32]; let maker_secret_hash = sha256(&maker_secret).to_vec(); let funding_time_lock = now_sec() + 29; let payment_time_lock = now_sec() + 15; @@ -2065,9 +2074,9 @@ fn send_and_refund_taker_funding_exceed_pre_approve_timelock_erc20() { let taker_coin = get_or_create_sepolia_coin(&MM_CTX1, SEPOLIA_TAKER_PRIV, ERC20, erc20_conf, true); let maker_coin = get_or_create_sepolia_coin(&MM_CTX1, SEPOLIA_MAKER_PRIV, ERC20, erc20_conf, true); - let taker_secret = vec![0; 32]; + let taker_secret = [0; 32]; let taker_secret_hash = sha256(&taker_secret).to_vec(); - let maker_secret = vec![1; 32]; + let maker_secret = [1; 32]; let maker_secret_hash = sha256(&maker_secret).to_vec(); let taker_address = block_on(taker_coin.my_addr()); @@ -2130,9 +2139,9 @@ fn send_maker_payment_and_refund_timelock_eth() { let taker_coin = get_or_create_sepolia_coin(&MM_CTX1, SEPOLIA_TAKER_PRIV, ETH, ð_sepolia_conf(), false); let maker_coin = get_or_create_sepolia_coin(&MM_CTX, SEPOLIA_MAKER_PRIV, ETH, ð_sepolia_conf(), false); - let taker_secret = vec![0; 32]; + let taker_secret = [0; 32]; let taker_secret_hash = sha256(&taker_secret).to_vec(); - let maker_secret = vec![1; 32]; + let maker_secret = [1; 32]; let maker_secret_hash = sha256(&maker_secret).to_vec(); let payment_time_lock = now_sec() - 1000; @@ -2185,9 +2194,9 @@ fn send_maker_payment_and_refund_timelock_erc20() { let taker_coin = get_or_create_sepolia_coin(&MM_CTX1, SEPOLIA_TAKER_PRIV, ERC20, erc20_conf, true); let maker_coin = get_or_create_sepolia_coin(&MM_CTX, SEPOLIA_MAKER_PRIV, ERC20, erc20_conf, true); - let taker_secret = vec![0; 32]; + let taker_secret = [0; 32]; let taker_secret_hash = sha256(&taker_secret).to_vec(); - let maker_secret = vec![1; 32]; + let maker_secret = [1; 32]; let maker_secret_hash = sha256(&maker_secret).to_vec(); let payment_time_lock = now_sec() - 1000; @@ -2239,10 +2248,10 @@ fn send_maker_payment_and_refund_secret_eth() { let taker_coin = get_or_create_sepolia_coin(&MM_CTX1, SEPOLIA_TAKER_PRIV, ETH, ð_sepolia_conf(), false); let maker_coin = get_or_create_sepolia_coin(&MM_CTX, SEPOLIA_MAKER_PRIV, ETH, ð_sepolia_conf(), false); - let taker_secret = vec![0; 32]; - let taker_secret_hash = sha256(&taker_secret).to_vec(); - let maker_secret = vec![1; 32]; - let maker_secret_hash = sha256(&maker_secret).to_vec(); + let taker_secret = &[0; 32]; + let taker_secret_hash = sha256(taker_secret).to_vec(); + let maker_secret = &[1; 32]; + let maker_secret_hash = sha256(maker_secret).to_vec(); let payment_time_lock = now_sec() + 1000; let maker_address = block_on(maker_coin.my_addr()); @@ -2268,7 +2277,7 @@ fn send_maker_payment_and_refund_secret_eth() { time_lock: payment_time_lock, taker_secret_hash: &taker_secret_hash, maker_secret_hash: &maker_secret_hash, - taker_secret: &taker_secret, + taker_secret, taker_pub, swap_unique_data: &[], amount: trading_amount, @@ -2291,10 +2300,10 @@ fn send_maker_payment_and_refund_secret_erc20() { let taker_coin = get_or_create_sepolia_coin(&MM_CTX1, SEPOLIA_TAKER_PRIV, ERC20, erc20_conf, true); let maker_coin = get_or_create_sepolia_coin(&MM_CTX, SEPOLIA_MAKER_PRIV, ERC20, erc20_conf, true); - let taker_secret = vec![0; 32]; - let taker_secret_hash = sha256(&taker_secret).to_vec(); - let maker_secret = vec![1; 32]; - let maker_secret_hash = sha256(&maker_secret).to_vec(); + let taker_secret = &[0; 32]; + let taker_secret_hash = sha256(taker_secret).to_vec(); + let maker_secret = &[1; 32]; + let maker_secret_hash = sha256(maker_secret).to_vec(); let payment_time_lock = now_sec() + 1000; let maker_address = block_on(maker_coin.my_addr()); @@ -2320,7 +2329,7 @@ fn send_maker_payment_and_refund_secret_erc20() { time_lock: payment_time_lock, taker_secret_hash: &taker_secret_hash, maker_secret_hash: &maker_secret_hash, - taker_secret: &taker_secret, + taker_secret, taker_pub, swap_unique_data: &[], amount: trading_amount, @@ -2342,9 +2351,9 @@ fn send_and_spend_maker_payment_eth() { let taker_coin = get_or_create_sepolia_coin(&MM_CTX1, SEPOLIA_TAKER_PRIV, ETH, ð_sepolia_conf(), false); let maker_coin = get_or_create_sepolia_coin(&MM_CTX, SEPOLIA_MAKER_PRIV, ETH, ð_sepolia_conf(), false); - let taker_secret = vec![0; 32]; + let taker_secret = [0; 32]; let taker_secret_hash = sha256(&taker_secret).to_vec(); - let maker_secret = vec![1; 32]; + let maker_secret = [1; 32]; let maker_secret_hash = sha256(&maker_secret).to_vec(); let payment_time_lock = now_sec() + 1000; @@ -2385,7 +2394,7 @@ fn send_and_spend_maker_payment_eth() { time_lock: payment_time_lock, taker_secret_hash: &taker_secret_hash, maker_secret_hash: &maker_secret_hash, - maker_secret: &maker_secret, + maker_secret, maker_pub, swap_unique_data: &[], amount: trading_amount, @@ -2405,9 +2414,9 @@ fn send_and_spend_maker_payment_erc20() { let taker_coin = get_or_create_sepolia_coin(&MM_CTX1, SEPOLIA_TAKER_PRIV, ERC20, erc20_conf, true); let maker_coin = get_or_create_sepolia_coin(&MM_CTX, SEPOLIA_MAKER_PRIV, ERC20, erc20_conf, true); - let taker_secret = vec![0; 32]; + let taker_secret = [0; 32]; let taker_secret_hash = sha256(&taker_secret).to_vec(); - let maker_secret = vec![1; 32]; + let maker_secret = [1; 32]; let maker_secret_hash = sha256(&maker_secret).to_vec(); let payment_time_lock = now_sec() + 1000; @@ -2448,7 +2457,7 @@ fn send_and_spend_maker_payment_erc20() { time_lock: payment_time_lock, taker_secret_hash: &taker_secret_hash, maker_secret_hash: &maker_secret_hash, - maker_secret: &maker_secret, + maker_secret, maker_pub, swap_unique_data: &[], amount: trading_amount, diff --git a/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs b/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs index 304f6f4819..31cdd4d3ff 100644 --- a/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs @@ -107,8 +107,8 @@ fn send_and_refund_taker_funding_secret() { let (_mm_arc, coin, _privkey) = generate_utxo_coin_with_random_privkey(MYCOIN, 1000.into()); let funding_time_lock = now_sec() - 1000; - let taker_secret = [0; 32]; - let taker_secret_hash_owned = dhash160(&taker_secret); + let taker_secret = &[0; 32]; + let taker_secret_hash_owned = dhash160(taker_secret); let taker_secret_hash = taker_secret_hash_owned.as_slice(); let maker_pub = coin.my_public_key().unwrap(); let dex_fee = &DexFee::Standard("0.01".into()); @@ -158,7 +158,7 @@ fn send_and_refund_taker_funding_secret() { funding_time_lock, payment_time_lock: 0, maker_pubkey: maker_pub, - taker_secret: &taker_secret, + taker_secret, taker_secret_hash, maker_secret_hash: &[], dex_fee, @@ -186,7 +186,7 @@ fn send_and_refund_taker_funding_secret() { match found_refund_tx { Some(FundingTxSpend::RefundedSecret { tx, secret }) => { assert_eq!(refund_tx, tx); - assert_eq!(taker_secret, secret); + assert_eq!(taker_secret, &secret); }, unexpected => panic!("Got unexpected FundingTxSpend variant {:?}", unexpected), }