From 08c9d64a4cf99f037af5d4b14936212f933f4b06 Mon Sep 17 00:00:00 2001 From: dimxy Date: Mon, 15 Jul 2024 15:29:58 +0500 Subject: [PATCH 01/19] add access list to swap eth transactions --- mm2src/coins/eth.rs | 241 ++++++++++++++++++++++------ mm2src/coins/eth/eth_rpc.rs | 24 ++- mm2src/coins/eth/nft_swap_v2/mod.rs | 2 + 3 files changed, 213 insertions(+), 54 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 8ab8b3f113..5575319489 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -99,7 +99,7 @@ use std::sync::atomic::{AtomicU64, Ordering as AtomicOrdering}; use std::sync::{Arc, Mutex}; use std::time::Duration; use web3::types::{Action as TraceAction, BlockId, BlockNumber, Bytes, CallRequest, FilterBuilder, Log, Trace, - TraceFilterBuilder, Transaction as Web3Transaction, TransactionId, U64}; + TraceFilterBuilder, Transaction as Web3Transaction, TransactionId, U64, TransactionRequest}; use web3::{self, Web3}; cfg_wasm32! { @@ -107,7 +107,6 @@ cfg_wasm32! { use crypto::MetamaskArc; use ethereum_types::{H264, H520}; use mm2_metamask::MetamaskError; - use web3::types::TransactionRequest; } use super::{coin_conf, lp_coinfind_or_err, AsyncMutex, BalanceError, BalanceFut, CheckIfMyPaymentSentArgs, @@ -614,6 +613,7 @@ pub struct EthCoinImpl { history_sync_state: Mutex, required_confirmations: AtomicU64, swap_txfee_policy: Mutex, + /// EVM platform coin maximum supported transaction type (for e.g. for ETH currently it is 2) max_eth_tx_type: Option, /// Coin needs access to the context in order to reuse the logging and shutdown facilities. /// Using a weak reference by default in order to avoid circular references and leaks. @@ -2460,6 +2460,7 @@ async fn sign_transaction_with_keypair<'a>( gas: U256, pay_for_gas_option: &PayForGasOption, from_address: Address, + access_list: Option, ) -> Result<(SignedEthTx, Vec), TransactionErr> { info!(target: "sign", "get_addr_nonce…"); let (nonce, web3_instances_with_latest_nonce) = try_tx_s!(coin.clone().get_addr_nonce(from_address).compat().await); @@ -2467,7 +2468,11 @@ async fn sign_transaction_with_keypair<'a>( if !coin.is_tx_type_supported(&tx_type) { return Err(TransactionErr::Plain("Eth transaction type not supported".into())); } + if access_list.is_some() && tx_type == TxType::Legacy { + return Err(TransactionErr::Plain("Eth access list not supported".into())); + } let tx_builder = UnSignedEthTxBuilder::new(tx_type, nonce, gas, action, value, data); + let tx_builder = if let Some(access_list) = access_list { tx_builder.with_access_list(access_list) } else { tx_builder }; let tx_builder = tx_builder_with_pay_for_gas_option(coin, tx_builder, pay_for_gas_option) .map_err(|e| TransactionErr::Plain(e.get_inner().to_string()))?; let tx = tx_builder.build()?; @@ -2488,6 +2493,7 @@ async fn sign_and_send_transaction_with_keypair( action: Action, data: Vec, gas: U256, + access_list: Option, ) -> Result { info!(target: "sign-and-send", "get_gas_price…"); let pay_for_gas_option = try_tx_s!( @@ -2497,7 +2503,7 @@ async fn sign_and_send_transaction_with_keypair( let address_lock = coin.get_address_lock(address.to_string()).await; let _nonce_lock = address_lock.lock().await; let (signed, web3_instances_with_latest_nonce) = - sign_transaction_with_keypair(coin, key_pair, value, action, data, gas, &pay_for_gas_option, address).await?; + sign_transaction_with_keypair(coin, key_pair, value, action, data, gas, &pay_for_gas_option, address, access_list).await?; let bytes = Bytes(rlp::encode(&signed).to_vec()); info!(target: "sign-and-send", "send_raw_transaction…"); @@ -2609,6 +2615,7 @@ async fn sign_raw_eth_tx(coin: &EthCoin, args: &SignEthTransactionParams) -> Raw args.gas_limit, &pay_for_gas_option, my_address, + None, ) .await .map(|(signed_tx, _)| RawTransactionRes { @@ -3551,7 +3558,7 @@ impl EthCoin { impl EthCoin { /// Sign and send eth transaction. /// This function is primarily for swap transactions so internally it relies on the swap tx fee policy - pub(crate) fn sign_and_send_transaction(&self, value: U256, action: Action, data: Vec, gas: U256) -> EthTxFut { + pub(crate) fn sign_and_send_transaction(&self, value: U256, action: Action, data: Vec, gas: U256, access_list: Option) -> EthTxFut { let coin = self.clone(); let fut = async move { match coin.priv_key_policy { @@ -3565,12 +3572,12 @@ impl EthCoin { .single_addr_or_err() .await .map_err(|e| TransactionErr::Plain(ERRL!("{}", e)))?; - sign_and_send_transaction_with_keypair(&coin, key_pair, address, value, action, data, gas).await + sign_and_send_transaction_with_keypair(&coin, key_pair, address, value, action, data, gas, access_list).await }, EthPrivKeyPolicy::Trezor => Err(TransactionErr::Plain(ERRL!("Trezor is not supported for swaps yet!"))), #[cfg(target_arch = "wasm32")] EthPrivKeyPolicy::Metamask(_) => { - sign_and_send_transaction_with_metamask(coin, value, action, data, gas).await + sign_and_send_transaction_with_metamask(coin, value, action, data, gas, None).await }, } }; @@ -3584,6 +3591,7 @@ impl EthCoin { Action::Call(address), vec![], U256::from(gas_limit::ETH_SEND_COINS), + None, ), EthCoinType::Erc20 { platform: _, @@ -3597,6 +3605,7 @@ impl EthCoin { Action::Call(*token_addr), data, U256::from(gas_limit::ETH_SEND_ERC20), + None, ) }, EthCoinType::Nft { .. } => Box::new(futures01::future::err(TransactionErr::ProtocolNotSupported(ERRL!( @@ -3650,12 +3659,27 @@ impl EthCoin { ])), }; let gas = U256::from(gas_limit::ETH_PAYMENT); - self.sign_and_send_transaction(value, Action::Call(swap_contract_address), data, gas) + let access_list_fut = self.create_accesslist(swap_contract_address, Some(value), Some(data.clone()), Some(gas)); + let coin = self.clone(); + Box::new( + access_list_fut + .map(|res| Some(res)) + .or_else(|err| { + println!("create_accesslist error={:?}", err); + futures01::future::ok(None::) // ignore create_accesslist errors and use None value + }) + .and_then(move |list| coin.sign_and_send_transaction(value, Action::Call(swap_contract_address), data, gas, list) + .map(|signed_tx| { + println!("sent ethPayment transaction: {:02x}", signed_tx.tx_hash()); + signed_tx + })) + ) }, EthCoinType::Erc20 { platform: _, token_addr, } => { + println!("swap_contract_address={:02x} token_addr={:02x}", swap_contract_address, token_addr); let allowance_fut = self .allowance(swap_contract_address) .map_err(|e| TransactionErr::Plain(ERRL!("{}", e))); @@ -3743,22 +3767,44 @@ impl EthCoin { )) }) .and_then(move |_| { - arc.sign_and_send_transaction( - value, - Call(swap_contract_address), - data, - gas, - ) + let access_list_fut = arc.create_accesslist(swap_contract_address, Some(value), Some(data.clone()), Some(gas)); + access_list_fut + .map(|res| Some(res)) + .or_else(|err| { + println!("create_accesslist error={:?}", err); + futures01::future::ok(None::) // ignore create_accesslist errors and use None value + }) + .and_then(move |list| { + arc.sign_and_send_transaction( + value, + Call(swap_contract_address), + data, + gas, + list, + ) + .map(|signed_tx| { + println!("sent erc20Payment(1) transaction: {:02x}", signed_tx.tx_hash()); + signed_tx + }) + }) }) }), ) } else { - Box::new(arc.sign_and_send_transaction( - value, - Call(swap_contract_address), - data, - gas, - )) + let access_list_fut = arc.create_accesslist(swap_contract_address, Some(value), Some(data.clone()), Some(gas)); + Box::new( + access_list_fut + .map(|res| Some(res)) + .or_else(|err| { + println!("create_accesslist error={:?}", err); + futures01::future::ok(None::) // ignore create_accesslist errors and use None value + }) + .and_then(move |list| arc.sign_and_send_transaction(value, Action::Call(swap_contract_address), data, gas, list) + .map(|signed_tx| { + println!("sent erc20Payment(2) transaction: {:02x}", signed_tx.tx_hash()); + signed_tx + })) + ) } })) }, @@ -3829,6 +3875,7 @@ impl EthCoin { Call(swap_contract_address), data, U256::from(gas_limit::ETH_RECEIVER_SPEND), + None, ) }), ) @@ -3877,6 +3924,7 @@ impl EthCoin { Call(swap_contract_address), data, U256::from(gas_limit::ERC20_RECEIVER_SPEND), + None, ) }), ) @@ -3949,6 +3997,7 @@ impl EthCoin { Call(swap_contract_address), data, U256::from(gas_limit::ETH_SENDER_REFUND), + None, ) }), ) @@ -4000,6 +4049,7 @@ impl EthCoin { Call(swap_contract_address), data, U256::from(gas_limit::ERC20_SENDER_REFUND), + None, ) }), ) @@ -4066,14 +4116,19 @@ impl EthCoin { ])) }; - self.sign_and_send_transaction( + let gas = U256::from(gas_limit::ETH_RECEIVER_SPEND); + let access_list = self.create_accesslist(swap_contract_address, Some(0.into()), Some(data.clone()), Some(gas)).compat().await.ok(); + let res = self.sign_and_send_transaction( 0.into(), Call(swap_contract_address), data, - U256::from(gas_limit::ETH_RECEIVER_SPEND), + gas, + access_list, ) .compat() - .await + .await; + if let Ok(ref tx) = res { println!("sent spent eth transaction: {:02x}", tx.tx_hash()); } + res }, EthCoinType::Erc20 { platform: _, @@ -4118,14 +4173,19 @@ impl EthCoin { ])) }; - self.sign_and_send_transaction( + let gas = U256::from(gas_limit::ERC20_RECEIVER_SPEND); + let access_list = self.create_accesslist(swap_contract_address, Some(0.into()), Some(data.clone()), Some(gas)).compat().await.ok(); + let res = self.sign_and_send_transaction( 0.into(), Call(swap_contract_address), data, - U256::from(gas_limit::ERC20_RECEIVER_SPEND), + gas, + access_list, ) .compat() - .await + .await; + if let Ok(ref tx) = res { println!("sent spent erc20 transaction: {:02x}", tx.tx_hash()); } + res }, EthCoinType::Nft { .. } => Err(TransactionErr::ProtocolNotSupported(ERRL!( "Nft Protocol is not supported!" @@ -4189,14 +4249,20 @@ impl EthCoin { ])) }; - self.sign_and_send_transaction( + let gas = U256::from(gas_limit::ETH_SENDER_REFUND); + let access_list = self.create_accesslist(swap_contract_address, Some(value), Some(data.clone()), Some(gas)).compat().await.ok(); + let res = self.sign_and_send_transaction( 0.into(), Call(swap_contract_address), data, - U256::from(gas_limit::ETH_SENDER_REFUND), + gas, + access_list, ) .compat() - .await + .await; + if let Ok(ref tx) = res { println!("sent refund eth transaction: {:02x}", tx.tx_hash()); } + res + }, EthCoinType::Erc20 { platform: _, @@ -4241,14 +4307,19 @@ impl EthCoin { ])) }; - self.sign_and_send_transaction( + let gas = U256::from(gas_limit::ERC20_SENDER_REFUND); + let access_list = self.create_accesslist(swap_contract_address, Some(0.into()), Some(data.clone()), Some(gas)).compat().await.ok(); // ignore errors + let res = self.sign_and_send_transaction( 0.into(), Call(swap_contract_address), data, - U256::from(gas_limit::ERC20_SENDER_REFUND), + gas, + access_list, ) .compat() - .await + .await; + if let Ok(ref tx) = res { println!("sent refund erc20 transaction: {:02x}", tx.tx_hash()); } + res }, EthCoinType::Nft { .. } => Err(TransactionErr::ProtocolNotSupported(ERRL!( "Nft Protocol is not supported yet!" @@ -4563,7 +4634,7 @@ impl EthCoin { .await ); - coin.sign_and_send_transaction(0.into(), Call(token_addr), data, gas_limit) + coin.sign_and_send_transaction(0.into(), Call(token_addr), data, gas_limit, None) .compat() .await }; @@ -5236,6 +5307,8 @@ impl EthCoin { ))); } + println!("receipt tx {:02x} gasUsed={:?}", receipt.transaction_hash, receipt.gas_used); + if let Some(confirmed_at) = receipt.block_number { break Ok(confirmed_at); } @@ -5360,6 +5433,40 @@ impl EthCoin { Timer::sleep(1.).await } }; + Box::new(fut.boxed().compat()) + } + + /// Try to create EIP-2930 AccessList which may reduce tx gas consumption + /// Returns error if tx does not support access lists or eth rpc completed with a error. + /// TODO: create error type (?) + fn create_accesslist(&self, to: Address, value: Option, data: Option>, gas: Option) -> Box + Send> { + let coin = self.clone(); + let fut = async move { + let my_address = coin.derivation_method.single_addr_or_err().await + .map_err(|err| err.to_string())?; + let fee_policy_for_estimate = get_swap_fee_policy_for_estimate(coin.get_swap_transaction_fee_policy()); + let pay_for_gas_option = coin.get_swap_pay_for_gas_option(fee_policy_for_estimate).await + .map_err(|err| err.to_string())?; + let tx_type = tx_type_from_pay_for_gas_option!(pay_for_gas_option); + if tx_type == TxType::Legacy { + debug!("create eth access list: tx legacy not supported legacy, coin={}", coin.ticker()); + return Err(String::from("access list not supported for tx type")); + } + println!("create_accesslist is_tx_type2={} is_tx_type_invalid={} coin={}", tx_type == TxType::Type2, tx_type == TxType::Invalid, coin.ticker()); + let tx_req = TransactionRequest { + value, + data: data.map(|data| Bytes(data)), + from: my_address, + to: Some(to), + gas, + ..TransactionRequest::default() + }; + let tx_req = transaction_request_with_pay_for_gas_option(tx_req, pay_for_gas_option); + coin.eth_create_accesslist(tx_req, Some(BlockNumber::Pending)) + .await + .map_err(|err| err.to_string()) + .map(|result| map_web3_access_list(&result.access_list)) // convert web3 access list to ethcore_transaction + }; Box::new(Box::pin(fut).compat()) } } @@ -5945,22 +6052,19 @@ impl Transaction for SignedEthTx { fn tx_hash_as_bytes(&self) -> BytesJson { self.tx_hash().as_bytes().into() } } -fn signed_tx_from_web3_tx(transaction: Web3Transaction) -> Result { - // Local function to map the access list - fn map_access_list(web3_access_list: &Option>) -> ethcore_transaction::AccessList { - match web3_access_list { - Some(list) => ethcore_transaction::AccessList( - list.iter() - .map(|item| ethcore_transaction::AccessListItem { - address: item.address, - storage_keys: item.storage_keys.clone(), - }) - .collect(), - ), - None => ethcore_transaction::AccessList(vec![]), - } - } +// Map the access list from web3 api to eth api +fn map_web3_access_list(web3_list: &Vec) -> ethcore_transaction::AccessList { + ethcore_transaction::AccessList( + web3_list.iter() + .map(|item| ethcore_transaction::AccessListItem { + address: item.address, + storage_keys: item.storage_keys.clone(), + }) + .collect() + ) +} +fn signed_tx_from_web3_tx(transaction: Web3Transaction) -> Result { // Define transaction types let type_0: ethereum_types::U64 = 0.into(); let type_1: ethereum_types::U64 = 1.into(); @@ -6009,10 +6113,15 @@ fn signed_tx_from_web3_tx(transaction: Web3Transaction) -> Result { let max_fee_per_gas = transaction @@ -6027,10 +6136,15 @@ fn signed_tx_from_web3_tx(transaction: Web3Transaction) -> Result return Err(ERRL!("Internal error: 'tx_type' invalid")), }; @@ -6141,6 +6255,7 @@ fn rpc_event_handlers_for_eth_transport(ctx: &MmArc, ticker: String) -> Vec Result, String> { fn check_max_eth_tx_type_conf(conf: &Json) -> Result, String> { if !conf["max_eth_tx_type"].is_null() { @@ -6713,6 +6828,26 @@ fn call_request_with_pay_for_gas_option(call_request: CallRequest, pay_for_gas_o } } +fn transaction_request_with_pay_for_gas_option(tx_request: TransactionRequest, pay_for_gas_option: PayForGasOption) -> TransactionRequest { + match pay_for_gas_option { + PayForGasOption::Legacy(LegacyGasPrice { gas_price }) => TransactionRequest { + gas_price: Some(gas_price), + max_fee_per_gas: None, + max_priority_fee_per_gas: None, + ..tx_request + }, + PayForGasOption::Eip1559(Eip1559FeePerGas { + max_fee_per_gas, + max_priority_fee_per_gas, + }) => TransactionRequest { + gas_price: None, + max_fee_per_gas: Some(max_fee_per_gas), + max_priority_fee_per_gas: Some(max_priority_fee_per_gas), + ..tx_request + }, + } +} + impl ToBytes for Signature { fn to_bytes(&self) -> Vec { self.to_vec() } } diff --git a/mm2src/coins/eth/eth_rpc.rs b/mm2src/coins/eth/eth_rpc.rs index 922e219fbd..d30d33011c 100644 --- a/mm2src/coins/eth/eth_rpc.rs +++ b/mm2src/coins/eth/eth_rpc.rs @@ -7,13 +7,22 @@ use super::{web3_transport::Web3Transport, EthCoin}; use common::{custom_futures::timeout::FutureTimerExt, log::debug}; use instant::Duration; use serde_json::Value; -use web3::types::{Address, Block, BlockId, BlockNumber, Bytes, CallRequest, FeeHistory, Filter, Log, Proof, SyncState, +use web3::types::{AccessList, Address, Block, BlockId, BlockNumber, Bytes, CallRequest, FeeHistory, Filter, Log, Proof, SyncState, Trace, TraceFilter, Transaction, TransactionId, TransactionReceipt, TransactionRequest, Work, H256, H520, H64, U256, U64}; use web3::{helpers, Transport}; pub(crate) const ETH_RPC_REQUEST_TIMEOUT: Duration = Duration::from_secs(10); +/// Result of eth_createAccessList (apparently missed in rust web3 lib). +#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)] +pub struct CreateAccessListResult { + #[serde(rename = "gasUsed")] + pub gas_used: U256, + #[serde(rename = "accessList")] + pub access_list: AccessList, +} + impl EthCoin { async fn try_rpc_send(&self, method: &str, params: Vec) -> Result { let mut clients = self.web3_instances.lock().await; @@ -452,4 +461,17 @@ impl EthCoin { .await .and_then(|t| serde_json::from_value(t).map_err(Into::into)) } + + /// Call eth_createAccessList for a transaction + pub(crate) async fn eth_create_accesslist(&self, tx: TransactionRequest, block: Option) -> Result { + let req = helpers::serialize(&tx); + let block = helpers::serialize(&block.unwrap_or_else(|| BlockNumber::Pending)); + + self.try_rpc_send("eth_createAccessList", vec![req, block]) + .await + .and_then(|t| { + println!("eth_createAccessList result={:?}", t); + serde_json::from_value(t).map_err(Into::into) + }) + } } diff --git a/mm2src/coins/eth/nft_swap_v2/mod.rs b/mm2src/coins/eth/nft_swap_v2/mod.rs index a446efde20..64161d105c 100644 --- a/mm2src/coins/eth/nft_swap_v2/mod.rs +++ b/mm2src/coins/eth/nft_swap_v2/mod.rs @@ -40,6 +40,7 @@ impl EthCoin { Action::Call(*args.nft_swap_info.token_address), data, U256::from(ETH_MAX_TRADE_GAS), // TODO: fix to a more accurate const or estimated value + None, ) .compat() .await @@ -159,6 +160,7 @@ impl EthCoin { Action::Call(*etomic_swap_contract), data, U256::from(ETH_MAX_TRADE_GAS), // TODO: fix to a more accurate const or estimated value + None, ) .compat() .await From a28547b8250c440dd8bfa69bf921bcbe36a920dc Mon Sep 17 00:00:00 2001 From: dimxy Date: Fri, 19 Jul 2024 21:38:32 +0500 Subject: [PATCH 02/19] allow 0x in data in sign raw eth tx --- mm2src/coins/eth.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 5575319489..27fa322188 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -2583,7 +2583,11 @@ async fn sign_raw_eth_tx(coin: &EthCoin, args: &SignEthTransactionParams) -> Raw } else { Create }; - let data = hex::decode(args.data.as_ref().unwrap_or(&String::from("")))?; + let data = hex::decode( + args.data + .as_ref() + .map(|s| s.strip_prefix("0x").unwrap_or(s)) + .unwrap_or(&String::from("")))?; match coin.priv_key_policy { // TODO: use zeroise for privkey EthPrivKeyPolicy::Iguana(ref key_pair) From 8b0c8cb182d4326551e304a24ca580edb2b6d9ec Mon Sep 17 00:00:00 2001 From: dimxy Date: Sun, 21 Jul 2024 15:10:46 +0500 Subject: [PATCH 03/19] add access list to sign raw eth tx --- mm2src/coins/eth.rs | 47 ++++++++++++++++++++++++++++++++++++---- mm2src/coins/lp_coins.rs | 8 +++++++ 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 27fa322188..2cacc8ec4c 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -2485,6 +2485,7 @@ async fn sign_transaction_with_keypair<'a>( /// Sign and send eth transaction with provided keypair, /// This fn is primarily for swap transactions so it uses swap tx fee policy +#[allow(clippy::too_many_arguments)] async fn sign_and_send_transaction_with_keypair( coin: &EthCoin, key_pair: &KeyPair, @@ -2502,8 +2503,18 @@ async fn sign_and_send_transaction_with_keypair( ); let address_lock = coin.get_address_lock(address.to_string()).await; let _nonce_lock = address_lock.lock().await; - let (signed, web3_instances_with_latest_nonce) = - sign_transaction_with_keypair(coin, key_pair, value, action, data, gas, &pay_for_gas_option, address, access_list).await?; + let (signed, web3_instances_with_latest_nonce) = sign_transaction_with_keypair( + coin, + key_pair, + value, + action, + data, + gas, + &pay_for_gas_option, + address, + access_list, + ) + .await?; let bytes = Bytes(rlp::encode(&signed).to_vec()); info!(target: "sign-and-send", "send_raw_transaction…"); @@ -2527,6 +2538,7 @@ async fn sign_and_send_transaction_with_metamask( action: Action, data: Vec, gas: U256, + access_list: Option, ) -> Result { let to = match action { Action::Create => None, @@ -2550,6 +2562,7 @@ async fn sign_and_send_transaction_with_metamask( value: Some(value), data: Some(data.clone().into()), nonce: None, + access_list: access_list.map(|eth_list| map_eth_access_list(ð_list)), ..TransactionRequest::default() }; @@ -2587,7 +2600,8 @@ async fn sign_raw_eth_tx(coin: &EthCoin, args: &SignEthTransactionParams) -> Raw args.data .as_ref() .map(|s| s.strip_prefix("0x").unwrap_or(s)) - .unwrap_or(&String::from("")))?; + .unwrap_or(""), + )?; match coin.priv_key_policy { // TODO: use zeroise for privkey EthPrivKeyPolicy::Iguana(ref key_pair) @@ -2610,6 +2624,31 @@ async fn sign_raw_eth_tx(coin: &EthCoin, args: &SignEthTransactionParams) -> Raw let gas_price = coin.get_gas_price().await?; PayForGasOption::Legacy(LegacyGasPrice { gas_price }) }; + + let access_list = if let Some(access_list_str) = &args.access_list { + let eth_access_list = ethcore_transaction::AccessList( + access_list_str + .iter() + .map(|item_str| { + Ok(ethcore_transaction::AccessListItem { + address: Address::from_str(item_str.address.as_str()) + .map_err(|err| RawTransactionError::InvalidParam(err.to_string()))?, + storage_keys: item_str + .storage_keys + .iter() + .map(|key_str| { + H256::from_str(key_str.as_str()) + .map_err(|err| RawTransactionError::InvalidParam(err.to_string())) + }) + .collect::, RawTransactionError>>()?, + }) + }) + .collect::, RawTransactionError>>()?, + ); + Some(eth_access_list) + } else { + None + }; sign_transaction_with_keypair( coin, key_pair, @@ -2619,7 +2658,7 @@ async fn sign_raw_eth_tx(coin: &EthCoin, args: &SignEthTransactionParams) -> Raw args.gas_limit, &pay_for_gas_option, my_address, - None, + access_list, ) .await .map(|(signed_tx, _)| RawTransactionRes { diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 6d0e82c756..d1edfbef58 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -526,6 +526,8 @@ pub struct SignEthTransactionParams { gas_limit: U256, /// Optional gas price or fee per gas params pay_for_gas: Option, + /// Optional access list + access_list: Option>, } #[derive(Clone, Debug, Deserialize)] @@ -556,6 +558,12 @@ pub struct MyWalletAddress { wallet_address: String, } +#[derive(Clone, Debug, Deserialize)] +pub struct EthAccessListItem { + pub address: String, + pub storage_keys: Vec, +} + pub type SignatureResult = Result>; pub type VerificationResult = Result>; From 1304d852ebc51caaf766b3a2e506e9ef4b689067 Mon Sep 17 00:00:00 2001 From: dimxy Date: Sun, 21 Jul 2024 15:15:05 +0500 Subject: [PATCH 04/19] more access lists: fix web3 tx build with access list, add list for metamask, add conf to use access list, edit lists to remove contract address item (no gas reducing) --- mm2src/coins/eth.rs | 322 ++++++++++++++++++------------ mm2src/coins/eth/eth_rpc.rs | 22 +- mm2src/coins/eth/eth_withdraw.rs | 10 +- mm2src/coins/eth/for_tests.rs | 3 +- mm2src/coins/eth/v2_activation.rs | 6 + 5 files changed, 220 insertions(+), 143 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 2cacc8ec4c..af357fb3f1 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -99,7 +99,7 @@ use std::sync::atomic::{AtomicU64, Ordering as AtomicOrdering}; use std::sync::{Arc, Mutex}; use std::time::Duration; use web3::types::{Action as TraceAction, BlockId, BlockNumber, Bytes, CallRequest, FilterBuilder, Log, Trace, - TraceFilterBuilder, Transaction as Web3Transaction, TransactionId, U64, TransactionRequest}; + TraceFilterBuilder, Transaction as Web3Transaction, TransactionId, TransactionRequest, U64}; use web3::{self, Web3}; cfg_wasm32! { @@ -615,6 +615,8 @@ pub struct EthCoinImpl { swap_txfee_policy: Mutex, /// EVM platform coin maximum supported transaction type (for e.g. for ETH currently it is 2) max_eth_tx_type: Option, + /// true to get and use EIP-2930 access list for swaps + use_access_list: bool, /// Coin needs access to the context in order to reuse the logging and shutdown facilities. /// Using a weak reference by default in order to avoid circular references and leaks. pub ctx: MmWeak, @@ -669,16 +671,22 @@ pub enum EthAddressFormat { MixedCase, } -/// get tx type from pay_for_gas_option -/// currently only type2 and legacy supported -/// if for Eth Classic we also want support for type 1 then use a fn +/// set tx type if pay_for_gas_option requires type 2 #[macro_export] -macro_rules! tx_type_from_pay_for_gas_option { - ($pay_for_gas_option: expr) => { +macro_rules! set_tx_type_from_pay_for_gas_option { + ($tx_type: ident, $pay_for_gas_option: expr) => { if matches!($pay_for_gas_option, PayForGasOption::Eip1559(..)) { - ethcore_transaction::TxType::Type2 - } else { - ethcore_transaction::TxType::Legacy + $tx_type = ethcore_transaction::TxType::Type2; + } + }; +} + +/// set tx type if pay_for_gas_option requires type 2 +#[macro_export] +macro_rules! set_tx_type_from_access_list { + ($tx_type: ident, $access_list: expr) => { + if $access_list.is_some() { + $tx_type = ethcore_transaction::TxType::Type1; } }; } @@ -912,7 +920,9 @@ pub async fn withdraw_erc1155(ctx: MmArc, withdraw_type: WithdrawErc1155) -> Wit .await? .map_to_mm(WithdrawError::Transport)?; - let tx_type = tx_type_from_pay_for_gas_option!(pay_for_gas_option); + let mut tx_type = TxType::Legacy; + set_tx_type_from_pay_for_gas_option!(tx_type, pay_for_gas_option); + drop_mutability!(tx_type); if !eth_coin.is_tx_type_supported(&tx_type) { return MmError::err(WithdrawError::TxTypeNotSupported); } @@ -1003,7 +1013,9 @@ pub async fn withdraw_erc721(ctx: MmArc, withdraw_type: WithdrawErc721) -> Withd .await? .map_to_mm(WithdrawError::Transport)?; - let tx_type = tx_type_from_pay_for_gas_option!(pay_for_gas_option); + let mut tx_type = TxType::Legacy; + set_tx_type_from_pay_for_gas_option!(tx_type, pay_for_gas_option); + drop_mutability!(tx_type); if !eth_coin.is_tx_type_supported(&tx_type) { return MmError::err(WithdrawError::TxTypeNotSupported); } @@ -2464,21 +2476,27 @@ async fn sign_transaction_with_keypair<'a>( ) -> Result<(SignedEthTx, Vec), TransactionErr> { info!(target: "sign", "get_addr_nonce…"); let (nonce, web3_instances_with_latest_nonce) = try_tx_s!(coin.clone().get_addr_nonce(from_address).compat().await); - let tx_type = tx_type_from_pay_for_gas_option!(pay_for_gas_option); + let mut tx_type = TxType::Legacy; + set_tx_type_from_access_list!(tx_type, access_list); + set_tx_type_from_pay_for_gas_option!(tx_type, pay_for_gas_option); + drop_mutability!(tx_type); if !coin.is_tx_type_supported(&tx_type) { return Err(TransactionErr::Plain("Eth transaction type not supported".into())); } - if access_list.is_some() && tx_type == TxType::Legacy { - return Err(TransactionErr::Plain("Eth access list not supported".into())); - } + let tx_builder = UnSignedEthTxBuilder::new(tx_type, nonce, gas, action, value, data); - let tx_builder = if let Some(access_list) = access_list { tx_builder.with_access_list(access_list) } else { tx_builder }; + let tx_builder = if let Some(access_list) = access_list { + tx_builder.with_access_list(access_list) + } else { + tx_builder + }; let tx_builder = tx_builder_with_pay_for_gas_option(coin, tx_builder, pay_for_gas_option) .map_err(|e| TransactionErr::Plain(e.get_inner().to_string()))?; + let tx_builder = tx_builder.with_chain_id(coin.chain_id); let tx = tx_builder.build()?; Ok(( - tx.sign(key_pair.secret(), Some(coin.chain_id))?, + tx.sign(key_pair.secret(), Some(coin.chain_id))?, // TODO: this chain_id could be mandatory web3_instances_with_latest_nonce, )) } @@ -3601,7 +3619,14 @@ impl EthCoin { impl EthCoin { /// Sign and send eth transaction. /// This function is primarily for swap transactions so internally it relies on the swap tx fee policy - pub(crate) fn sign_and_send_transaction(&self, value: U256, action: Action, data: Vec, gas: U256, access_list: Option) -> EthTxFut { + pub(crate) fn sign_and_send_transaction( + &self, + value: U256, + action: Action, + data: Vec, + gas: U256, + access_list: Option, + ) -> EthTxFut { let coin = self.clone(); let fut = async move { match coin.priv_key_policy { @@ -3615,12 +3640,22 @@ impl EthCoin { .single_addr_or_err() .await .map_err(|e| TransactionErr::Plain(ERRL!("{}", e)))?; - sign_and_send_transaction_with_keypair(&coin, key_pair, address, value, action, data, gas, access_list).await + sign_and_send_transaction_with_keypair( + &coin, + key_pair, + address, + value, + action, + data, + gas, + access_list, + ) + .await }, EthPrivKeyPolicy::Trezor => Err(TransactionErr::Plain(ERRL!("Trezor is not supported for swaps yet!"))), #[cfg(target_arch = "wasm32")] EthPrivKeyPolicy::Metamask(_) => { - sign_and_send_transaction_with_metamask(coin, value, action, data, gas, None).await + sign_and_send_transaction_with_metamask(coin, value, action, data, gas, access_list).await }, } }; @@ -3702,27 +3737,27 @@ impl EthCoin { ])), }; let gas = U256::from(gas_limit::ETH_PAYMENT); - let access_list_fut = self.create_accesslist(swap_contract_address, Some(value), Some(data.clone()), Some(gas)); + let access_list_fut = self.create_access_list_if_configured( + swap_contract_address, + Some(value), + Some(data.clone()), + Some(gas), + ); let coin = self.clone(); Box::new( access_list_fut - .map(|res| Some(res)) - .or_else(|err| { - println!("create_accesslist error={:?}", err); - futures01::future::ok(None::) // ignore create_accesslist errors and use None value - }) - .and_then(move |list| coin.sign_and_send_transaction(value, Action::Call(swap_contract_address), data, gas, list) - .map(|signed_tx| { - println!("sent ethPayment transaction: {:02x}", signed_tx.tx_hash()); - signed_tx - })) + .map(move |list| remove_from_access_list(&list, &[swap_contract_address])) // edit access list to remove an item that does not reduce gas + .map(Some) + .or_else(|_err| futures01::future::ok(None::)) // ignore create_access_list_if_configured errors and use None value + .and_then(move |list| { + coin.sign_and_send_transaction(value, Action::Call(swap_contract_address), data, gas, list) + }), ) }, EthCoinType::Erc20 { platform: _, token_addr, } => { - println!("swap_contract_address={:02x} token_addr={:02x}", swap_contract_address, token_addr); let allowance_fut = self .allowance(swap_contract_address) .map_err(|e| TransactionErr::Plain(ERRL!("{}", e))); @@ -3810,13 +3845,11 @@ impl EthCoin { )) }) .and_then(move |_| { - let access_list_fut = arc.create_accesslist(swap_contract_address, Some(value), Some(data.clone()), Some(gas)); + let access_list_fut = arc.create_access_list_if_configured(swap_contract_address, Some(value), Some(data.clone()), Some(gas)); access_list_fut - .map(|res| Some(res)) - .or_else(|err| { - println!("create_accesslist error={:?}", err); - futures01::future::ok(None::) // ignore create_accesslist errors and use None value - }) + .map(move |list| remove_from_access_list(&list, &[swap_contract_address])) + .map(Some) + .or_else(|_err| futures01::future::ok(None::)) .and_then(move |list| { arc.sign_and_send_transaction( value, @@ -3825,28 +3858,18 @@ impl EthCoin { gas, list, ) - .map(|signed_tx| { - println!("sent erc20Payment(1) transaction: {:02x}", signed_tx.tx_hash()); - signed_tx - }) }) }) }), ) } else { - let access_list_fut = arc.create_accesslist(swap_contract_address, Some(value), Some(data.clone()), Some(gas)); + let access_list_fut = arc.create_access_list_if_configured(swap_contract_address, Some(value), Some(data.clone()), Some(gas)); Box::new( access_list_fut - .map(|res| Some(res)) - .or_else(|err| { - println!("create_accesslist error={:?}", err); - futures01::future::ok(None::) // ignore create_accesslist errors and use None value - }) - .and_then(move |list| arc.sign_and_send_transaction(value, Action::Call(swap_contract_address), data, gas, list) - .map(|signed_tx| { - println!("sent erc20Payment(2) transaction: {:02x}", signed_tx.tx_hash()); - signed_tx - })) + .map(move |list| remove_from_access_list(&list, &[swap_contract_address])) + .map(Some) + .or_else(|_err| futures01::future::ok(None::)) + .and_then(move |list| arc.sign_and_send_transaction(value, Action::Call(swap_contract_address), data, gas, list)) ) } })) @@ -4160,18 +4183,20 @@ impl EthCoin { }; let gas = U256::from(gas_limit::ETH_RECEIVER_SPEND); - let access_list = self.create_accesslist(swap_contract_address, Some(0.into()), Some(data.clone()), Some(gas)).compat().await.ok(); - let res = self.sign_and_send_transaction( - 0.into(), - Call(swap_contract_address), - data, - gas, - access_list, - ) - .compat() - .await; - if let Ok(ref tx) = res { println!("sent spent eth transaction: {:02x}", tx.tx_hash()); } - res + let access_list = self + .create_access_list_if_configured( + swap_contract_address, + Some(0.into()), + Some(data.clone()), + Some(gas), + ) + .compat() + .await + .map(|list| remove_from_access_list(&list, &[swap_contract_address])) + .ok(); + self.sign_and_send_transaction(0.into(), Call(swap_contract_address), data, gas, access_list) + .compat() + .await }, EthCoinType::Erc20 { platform: _, @@ -4217,18 +4242,20 @@ impl EthCoin { }; let gas = U256::from(gas_limit::ERC20_RECEIVER_SPEND); - let access_list = self.create_accesslist(swap_contract_address, Some(0.into()), Some(data.clone()), Some(gas)).compat().await.ok(); - let res = self.sign_and_send_transaction( - 0.into(), - Call(swap_contract_address), - data, - gas, - access_list, - ) - .compat() - .await; - if let Ok(ref tx) = res { println!("sent spent erc20 transaction: {:02x}", tx.tx_hash()); } - res + let access_list = self + .create_access_list_if_configured( + swap_contract_address, + Some(0.into()), + Some(data.clone()), + Some(gas), + ) + .compat() + .await + .map(|list| remove_from_access_list(&list, &[swap_contract_address])) + .ok(); + self.sign_and_send_transaction(0.into(), Call(swap_contract_address), data, gas, access_list) + .compat() + .await }, EthCoinType::Nft { .. } => Err(TransactionErr::ProtocolNotSupported(ERRL!( "Nft Protocol is not supported!" @@ -4293,19 +4320,15 @@ impl EthCoin { }; let gas = U256::from(gas_limit::ETH_SENDER_REFUND); - let access_list = self.create_accesslist(swap_contract_address, Some(value), Some(data.clone()), Some(gas)).compat().await.ok(); - let res = self.sign_and_send_transaction( - 0.into(), - Call(swap_contract_address), - data, - gas, - access_list, - ) - .compat() - .await; - if let Ok(ref tx) = res { println!("sent refund eth transaction: {:02x}", tx.tx_hash()); } - res - + let access_list = self + .create_access_list_if_configured(swap_contract_address, Some(value), Some(data.clone()), Some(gas)) + .compat() + .await + .map(|list| remove_from_access_list(&list, &[swap_contract_address])) + .ok(); + self.sign_and_send_transaction(0.into(), Call(swap_contract_address), data, gas, access_list) + .compat() + .await }, EthCoinType::Erc20 { platform: _, @@ -4351,18 +4374,20 @@ impl EthCoin { }; let gas = U256::from(gas_limit::ERC20_SENDER_REFUND); - let access_list = self.create_accesslist(swap_contract_address, Some(0.into()), Some(data.clone()), Some(gas)).compat().await.ok(); // ignore errors - let res = self.sign_and_send_transaction( - 0.into(), - Call(swap_contract_address), - data, - gas, - access_list, - ) - .compat() - .await; - if let Ok(ref tx) = res { println!("sent refund erc20 transaction: {:02x}", tx.tx_hash()); } - res + let access_list = self + .create_access_list_if_configured( + swap_contract_address, + Some(0.into()), + Some(data.clone()), + Some(gas), + ) + .compat() + .await + .map(|list| remove_from_access_list(&list, &[swap_contract_address])) + .ok(); // ignore errors + self.sign_and_send_transaction(0.into(), Call(swap_contract_address), data, gas, access_list) + .compat() + .await }, EthCoinType::Nft { .. } => Err(TransactionErr::ProtocolNotSupported(ERRL!( "Nft Protocol is not supported yet!" @@ -5350,8 +5375,6 @@ impl EthCoin { ))); } - println!("receipt tx {:02x} gasUsed={:?}", receipt.transaction_hash, receipt.gas_used); - if let Some(confirmed_at) = receipt.block_number { break Ok(confirmed_at); } @@ -5482,33 +5505,45 @@ impl EthCoin { /// Try to create EIP-2930 AccessList which may reduce tx gas consumption /// Returns error if tx does not support access lists or eth rpc completed with a error. /// TODO: create error type (?) - fn create_accesslist(&self, to: Address, value: Option, data: Option>, gas: Option) -> Box + Send> { + fn create_access_list_if_configured( + &self, + to: Address, + value: Option, + data: Option>, + gas: Option, + ) -> Box + Send> { let coin = self.clone(); let fut = async move { - let my_address = coin.derivation_method.single_addr_or_err().await + if !coin.use_access_list { + return Ok(ethcore_transaction::AccessList(vec![])); + } + let my_address = coin + .derivation_method + .single_addr_or_err() + .await .map_err(|err| err.to_string())?; let fee_policy_for_estimate = get_swap_fee_policy_for_estimate(coin.get_swap_transaction_fee_policy()); - let pay_for_gas_option = coin.get_swap_pay_for_gas_option(fee_policy_for_estimate).await + let pay_for_gas_option = coin + .get_swap_pay_for_gas_option(fee_policy_for_estimate) + .await .map_err(|err| err.to_string())?; - let tx_type = tx_type_from_pay_for_gas_option!(pay_for_gas_option); - if tx_type == TxType::Legacy { - debug!("create eth access list: tx legacy not supported legacy, coin={}", coin.ticker()); - return Err(String::from("access list not supported for tx type")); - } - println!("create_accesslist is_tx_type2={} is_tx_type_invalid={} coin={}", tx_type == TxType::Type2, tx_type == TxType::Invalid, coin.ticker()); let tx_req = TransactionRequest { value, - data: data.map(|data| Bytes(data)), + data: data.map(Bytes), from: my_address, to: Some(to), gas, ..TransactionRequest::default() }; let tx_req = transaction_request_with_pay_for_gas_option(tx_req, pay_for_gas_option); - coin.eth_create_accesslist(tx_req, Some(BlockNumber::Pending)) + coin.eth_create_access_list(tx_req, Some(BlockNumber::Pending)) .await + .map_err(|err| { + debug!("create EIP-2930 access list error: {}, coin={}", err, coin.ticker()); + err + }) .map_err(|err| err.to_string()) - .map(|result| map_web3_access_list(&result.access_list)) // convert web3 access list to ethcore_transaction + .map(|result| map_web3_access_list(&result.access_list)) }; Box::new(Box::pin(fut).compat()) } @@ -6095,15 +6130,44 @@ impl Transaction for SignedEthTx { fn tx_hash_as_bytes(&self) -> BytesJson { self.tx_hash().as_bytes().into() } } -// Map the access list from web3 api to eth api -fn map_web3_access_list(web3_list: &Vec) -> ethcore_transaction::AccessList { +/// Map the access list from web3 api to eth api +fn map_web3_access_list(web3_list: &[web3::types::AccessListItem]) -> ethcore_transaction::AccessList { ethcore_transaction::AccessList( - web3_list.iter() + web3_list + .iter() .map(|item| ethcore_transaction::AccessListItem { address: item.address, storage_keys: item.storage_keys.clone(), }) - .collect() + .collect(), + ) +} + +/// Map the access list from web3 api to eth api +#[cfg(target_arch = "wasm32")] +fn map_eth_access_list(eth_list: ðcore_transaction::AccessList) -> Vec { + eth_list + .0 + .iter() + .map(|item| web3::types::AccessListItem { + address: item.address, + storage_keys: item.storage_keys.clone(), + }) + .collect() +} + +/// Edit access list to remove addresses (usually the swap contract), which we know won't not help to reduce gas +fn remove_from_access_list( + access_list: ðcore_transaction::AccessList, + addresses_to_remove: &[Address], +) -> ethcore_transaction::AccessList { + ethcore_transaction::AccessList( + access_list + .0 + .iter() + .filter(|item| !addresses_to_remove.contains(&item.address)) + .cloned() + .collect::>(), ) } @@ -6156,12 +6220,9 @@ fn signed_tx_from_web3_tx(transaction: Web3Transaction) -> Result Result TransactionRequest { +fn transaction_request_with_pay_for_gas_option( + tx_request: TransactionRequest, + pay_for_gas_option: PayForGasOption, +) -> TransactionRequest { match pay_for_gas_option { PayForGasOption::Legacy(LegacyGasPrice { gas_price }) => TransactionRequest { gas_price: Some(gas_price), diff --git a/mm2src/coins/eth/eth_rpc.rs b/mm2src/coins/eth/eth_rpc.rs index d30d33011c..9af7999c07 100644 --- a/mm2src/coins/eth/eth_rpc.rs +++ b/mm2src/coins/eth/eth_rpc.rs @@ -7,9 +7,9 @@ use super::{web3_transport::Web3Transport, EthCoin}; use common::{custom_futures::timeout::FutureTimerExt, log::debug}; use instant::Duration; use serde_json::Value; -use web3::types::{AccessList, Address, Block, BlockId, BlockNumber, Bytes, CallRequest, FeeHistory, Filter, Log, Proof, SyncState, - Trace, TraceFilter, Transaction, TransactionId, TransactionReceipt, TransactionRequest, Work, H256, - H520, H64, U256, U64}; +use web3::types::{AccessList, Address, Block, BlockId, BlockNumber, Bytes, CallRequest, FeeHistory, Filter, Log, + Proof, SyncState, Trace, TraceFilter, Transaction, TransactionId, TransactionReceipt, + TransactionRequest, Work, H256, H520, H64, U256, U64}; use web3::{helpers, Transport}; pub(crate) const ETH_RPC_REQUEST_TIMEOUT: Duration = Duration::from_secs(10); @@ -319,7 +319,7 @@ impl EthCoin { } /// Get transaction receipt - pub(crate) async fn transaction_receipt(&self, hash: H256) -> Result, web3::Error> { + pub async fn transaction_receipt(&self, hash: H256) -> Result, web3::Error> { let hash = helpers::serialize(&hash); self.try_rpc_send("eth_getTransactionReceipt", vec![hash]) @@ -463,15 +463,15 @@ impl EthCoin { } /// Call eth_createAccessList for a transaction - pub(crate) async fn eth_create_accesslist(&self, tx: TransactionRequest, block: Option) -> Result { + pub(crate) async fn eth_create_access_list( + &self, + tx: TransactionRequest, + block: Option, + ) -> Result { let req = helpers::serialize(&tx); - let block = helpers::serialize(&block.unwrap_or_else(|| BlockNumber::Pending)); - + let block = helpers::serialize(&block.unwrap_or(BlockNumber::Pending)); self.try_rpc_send("eth_createAccessList", vec![req, block]) .await - .and_then(|t| { - println!("eth_createAccessList result={:?}", t); - serde_json::from_value(t).map_err(Into::into) - }) + .and_then(|t| serde_json::from_value(t).map_err(Into::into)) } } diff --git a/mm2src/coins/eth/eth_withdraw.rs b/mm2src/coins/eth/eth_withdraw.rs index b3de177a89..c2ca549fd7 100644 --- a/mm2src/coins/eth/eth_withdraw.rs +++ b/mm2src/coins/eth/eth_withdraw.rs @@ -1,7 +1,7 @@ use super::{checksum_address, u256_to_big_decimal, wei_from_big_decimal, EthCoinType, EthDerivationMethod, EthPrivKeyPolicy, Public, WithdrawError, WithdrawRequest, WithdrawResult, ERC20_CONTRACT, H160, H256}; -use crate::eth::{calc_total_fee, get_eth_gas_details_from_withdraw_fee, tx_builder_with_pay_for_gas_option, - tx_type_from_pay_for_gas_option, Action, Address, EthTxFeeDetails, KeyPair, PayForGasOption, +use crate::eth::{calc_total_fee, get_eth_gas_details_from_withdraw_fee, set_tx_type_from_pay_for_gas_option, + tx_builder_with_pay_for_gas_option, Action, Address, EthTxFeeDetails, KeyPair, PayForGasOption, SignedEthTx, TransactionWrapper, UnSignedEthTxBuilder}; use crate::hd_wallet::{HDCoinWithdrawOps, HDWalletOps, WithdrawFrom, WithdrawSenderAddress}; use crate::rpc_command::init_withdraw::{WithdrawInProgressStatus, WithdrawTaskHandleShared}; @@ -15,6 +15,7 @@ use crypto::hw_rpc_task::HwRpcTaskAwaitingStatus; use crypto::trezor::trezor_rpc_task::{TrezorRequestStatuses, TrezorRpcTaskProcessor}; use crypto::{CryptoCtx, HwRpcError}; use ethabi::Token; +use ethcore_transaction::TxType; use futures::compat::Future01CompatExt; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::map_mm_error::MapMmError; @@ -262,7 +263,10 @@ where .await? .map_to_mm(WithdrawError::Transport)?; - let tx_type = tx_type_from_pay_for_gas_option!(pay_for_gas_option); + let mut tx_type = TxType::Legacy; + set_tx_type_from_pay_for_gas_option!(tx_type, pay_for_gas_option); + drop_mutability!(tx_type); + if !coin.is_tx_type_supported(&tx_type) { return MmError::err(WithdrawError::TxTypeNotSupported); } diff --git a/mm2src/coins/eth/for_tests.rs b/mm2src/coins/eth/for_tests.rs index 9ea93188ea..59e45d9a42 100644 --- a/mm2src/coins/eth/for_tests.rs +++ b/mm2src/coins/eth/for_tests.rs @@ -69,7 +69,8 @@ pub(crate) fn eth_coin_from_keypair( trezor_coin: None, logs_block_range: DEFAULT_LOGS_BLOCK_RANGE, address_nonce_locks: Arc::new(AsyncMutex::new(new_nonce_lock())), - max_eth_tx_type: None, + max_eth_tx_type: Some(2), + use_access_list: false, erc20_tokens_infos: Default::default(), nfts_infos: Arc::new(Default::default()), platform_fee_estimator_state: Arc::new(FeeEstimatorState::CoinNotSupported), diff --git a/mm2src/coins/eth/v2_activation.rs b/mm2src/coins/eth/v2_activation.rs index e41921508d..8fcd3be59a 100644 --- a/mm2src/coins/eth/v2_activation.rs +++ b/mm2src/coins/eth/v2_activation.rs @@ -394,6 +394,7 @@ impl EthCoin { }; let platform_fee_estimator_state = FeeEstimatorState::init_fee_estimator(&ctx, &conf, &coin_type).await?; let max_eth_tx_type = get_max_eth_tx_type_conf(&ctx, &conf, &coin_type).await?; + let use_access_list = conf["use_access_list"].as_bool().unwrap_or(false); let token = EthCoinImpl { priv_key_policy: self.priv_key_policy.clone(), @@ -412,6 +413,7 @@ impl EthCoin { history_sync_state: Mutex::new(self.history_sync_state.lock().unwrap().clone()), swap_txfee_policy: Mutex::new(SwapTxFeePolicy::Internal), max_eth_tx_type, + use_access_list, ctx: self.ctx.clone(), required_confirmations, chain_id: self.chain_id, @@ -457,6 +459,7 @@ impl EthCoin { }; let platform_fee_estimator_state = FeeEstimatorState::init_fee_estimator(&ctx, &conf, &coin_type).await?; let max_eth_tx_type = get_max_eth_tx_type_conf(&ctx, &conf, &coin_type).await?; + let use_access_list = conf["use_access_list"].as_bool().unwrap_or(false); let global_nft = EthCoinImpl { ticker, @@ -472,6 +475,7 @@ impl EthCoin { history_sync_state: Mutex::new(self.history_sync_state.lock().unwrap().clone()), swap_txfee_policy: Mutex::new(SwapTxFeePolicy::Internal), max_eth_tx_type, + use_access_list, required_confirmations: AtomicU64::new(self.required_confirmations.load(Ordering::Relaxed)), ctx: self.ctx.clone(), chain_id: self.chain_id, @@ -593,6 +597,7 @@ pub async fn eth_coin_from_conf_and_request_v2( let coin_type = EthCoinType::Eth; let platform_fee_estimator_state = FeeEstimatorState::init_fee_estimator(ctx, conf, &coin_type).await?; let max_eth_tx_type = get_max_eth_tx_type_conf(ctx, conf, &coin_type).await?; + let use_access_list = conf["use_access_list"].as_bool().unwrap_or(false); let coin = EthCoinImpl { priv_key_policy, @@ -608,6 +613,7 @@ pub async fn eth_coin_from_conf_and_request_v2( history_sync_state: Mutex::new(HistorySyncState::NotEnabled), swap_txfee_policy: Mutex::new(SwapTxFeePolicy::Internal), max_eth_tx_type, + use_access_list, ctx: ctx.weak(), required_confirmations, chain_id, From b85d22f0766b1d36f2accdd9ba0d08a5b57b6099 Mon Sep 17 00:00:00 2001 From: dimxy Date: Sun, 21 Jul 2024 15:18:58 +0500 Subject: [PATCH 05/19] add printing of gasUsed to docker test to show access list effect --- .../tests/docker_tests/eth_docker_tests.rs | 33 +++++++++++++++++++ mm2src/mm2_test_helpers/src/for_tests.rs | 3 +- 2 files changed, 35 insertions(+), 1 deletion(-) 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 1109e9f159..b479e1fb71 100644 --- a/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs @@ -16,6 +16,7 @@ use ethereum_types::U256; use futures01::Future; use mm2_number::{BigDecimal, BigUint}; use mm2_test_helpers::for_tests::{erc20_dev_conf, eth_dev_conf, nft_dev_conf}; +use std::str::FromStr; use std::thread; use std::time::Duration; use web3::contract::{Contract, Options}; @@ -683,6 +684,22 @@ fn send_and_spend_erc20_maker_payment_impl(swap_txfee_policy: SwapTxFeePolicy) { }; taker_erc20_coin.wait_for_confirmations(confirm_input).wait().unwrap(); + // This code was added to study gas consumption when the access list is used or not. To use access list set 'use_access_list' to true on coin conf + if let Some(receipt) = + block_on(maker_erc20_coin.transaction_receipt( + H256::from_str(&hex::encode(eth_maker_payment.tx_hash_as_bytes().into_vec())).unwrap(), + )) + .unwrap() + { + log!( + "eth_maker_payment tx {:02x} gasUsed={:?}", + receipt.transaction_hash, + receipt.gas_used + ); + } else { + log!("eth_maker_payment tx no receipt received"); + } + let spend_args = SpendPaymentArgs { other_payment_tx: ð_maker_payment.tx_hex(), time_lock, @@ -705,6 +722,22 @@ fn send_and_spend_erc20_maker_payment_impl(swap_txfee_policy: SwapTxFeePolicy) { }; taker_erc20_coin.wait_for_confirmations(confirm_input).wait().unwrap(); + // This code was added to study gas consumption when the access list is used or not. To use access list set 'use_access_list' to true on coin conf + if let Some(receipt) = block_on( + taker_erc20_coin + .transaction_receipt(H256::from_str(&hex::encode(payment_spend.tx_hash_as_bytes().into_vec())).unwrap()), + ) + .unwrap() + { + log!( + "payment_spend tx {:02x} gasUsed={:?}", + receipt.transaction_hash, + receipt.gas_used + ); + } else { + log!("payment_spend tx no receipt received"); + } + let search_input = SearchForSwapTxSpendInput { time_lock, other_pub: &taker_pubkey, diff --git a/mm2src/mm2_test_helpers/src/for_tests.rs b/mm2src/mm2_test_helpers/src/for_tests.rs index 0a637f5d15..5dcda537fc 100644 --- a/mm2src/mm2_test_helpers/src/for_tests.rs +++ b/mm2src/mm2_test_helpers/src/for_tests.rs @@ -824,7 +824,8 @@ pub fn erc20_dev_conf(contract_address: &str) -> Json { "contract_address": contract_address, } }, - "max_eth_tx_type": 2 + "max_eth_tx_type": 2, + "use_access_list": true }) } From 9aa5422596801723f9439a5c2abf1f13a4ec9741 Mon Sep 17 00:00:00 2001 From: dimxy Date: Thu, 15 Aug 2024 17:07:17 +0500 Subject: [PATCH 06/19] fix after merge --- mm2src/coins/eth/eth_swap_v2.rs | 2 ++ mm2src/coins/eth/nft_swap_v2/mod.rs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/mm2src/coins/eth/eth_swap_v2.rs b/mm2src/coins/eth/eth_swap_v2.rs index 295fd7af2c..0a7083bb37 100644 --- a/mm2src/coins/eth/eth_swap_v2.rs +++ b/mm2src/coins/eth/eth_swap_v2.rs @@ -59,6 +59,7 @@ impl EthCoin { data, // TODO need new consts and params for v2 calls. now it uses v1 U256::from(self.gas_limit.eth_payment), + None, ) .compat() .await @@ -92,6 +93,7 @@ impl EthCoin { data, // TODO need new consts and params for v2 calls. now it uses v1 U256::from(self.gas_limit.erc20_payment), + None, ) .compat() .await diff --git a/mm2src/coins/eth/nft_swap_v2/mod.rs b/mm2src/coins/eth/nft_swap_v2/mod.rs index 04a485b641..a1ed637f9f 100644 --- a/mm2src/coins/eth/nft_swap_v2/mod.rs +++ b/mm2src/coins/eth/nft_swap_v2/mod.rs @@ -200,6 +200,7 @@ impl EthCoin { Action::Call(*etomic_swap_contract), data, U256::from(self.gas_limit.eth_max_trade_gas), // TODO: fix to a more accurate const or estimated value + None, ) .compat() .await @@ -240,6 +241,7 @@ impl EthCoin { Action::Call(*etomic_swap_contract), data, U256::from(self.gas_limit.eth_max_trade_gas), // TODO: fix to a more accurate const or estimated value + None, ) .compat() .await From eb53960fca8f04748816016ae4ee0f03b5ae73be Mon Sep 17 00:00:00 2001 From: dimxy Date: Fri, 16 Aug 2024 14:14:35 +0500 Subject: [PATCH 07/19] fixed getting max_eth_tx_type from platform coin for token activation --- mm2src/coins/eth.rs | 3 +++ mm2src/coins/eth/v2_activation.rs | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index e18774ca4a..d12987bfac 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -6440,6 +6440,9 @@ async fn get_max_eth_tx_type_conf(ctx: &MmArc, conf: &Json, coin_type: &EthCoinT if let Some(coin_max_eth_tx_type) = coin_max_eth_tx_type { Ok(Some(coin_max_eth_tx_type)) } else { + // Note that platform_coin is not available when the token is enabled via "enable_eth_with_tokens" + // (but we access platform coin directly - see initialise_erc20_token fn). + // So this code works only for the legacy "enable" rpc, called first for the platform coin then for a token let platform_coin = lp_coinfind_or_err(ctx, platform).await; match platform_coin { Ok(MmCoinEnum::EthCoin(eth_coin)) => Ok(eth_coin.max_eth_tx_type), diff --git a/mm2src/coins/eth/v2_activation.rs b/mm2src/coins/eth/v2_activation.rs index 75b52ac323..541a11655c 100644 --- a/mm2src/coins/eth/v2_activation.rs +++ b/mm2src/coins/eth/v2_activation.rs @@ -431,7 +431,7 @@ impl EthCoin { token_addr: protocol.token_addr, }; let platform_fee_estimator_state = FeeEstimatorState::init_fee_estimator(&ctx, &conf, &coin_type).await?; - let max_eth_tx_type = get_max_eth_tx_type_conf(&ctx, &conf, &coin_type).await?; + let max_eth_tx_type = get_max_eth_tx_type_conf(&ctx, &conf, &coin_type).await?.or_else(|| self.max_eth_tx_type); // use the platform coin setting if token setting is none let use_access_list = conf["use_access_list"].as_bool().unwrap_or(false); let gas_limit = extract_gas_limit_from_conf(&conf) .map_to_mm(|e| EthTokenActivationError::InternalError(format!("invalid gas_limit config {}", e)))?; @@ -532,7 +532,7 @@ impl EthCoin { platform: self.ticker.clone(), }; let platform_fee_estimator_state = FeeEstimatorState::init_fee_estimator(&ctx, &conf, &coin_type).await?; - let max_eth_tx_type = get_max_eth_tx_type_conf(&ctx, &conf, &coin_type).await?; + let max_eth_tx_type = get_max_eth_tx_type_conf(&ctx, &conf, &coin_type).await?.or_else(|| self.max_eth_tx_type); // use the platform coin setting if token setting is none let use_access_list = conf["use_access_list"].as_bool().unwrap_or(false); let gas_limit = extract_gas_limit_from_conf(&conf) .map_to_mm(|e| EthTokenActivationError::InternalError(format!("invalid gas_limit config {}", e)))?; From df99cc56ca107407e3a56a13845ded8d820a6792 Mon Sep 17 00:00:00 2001 From: dimxy Date: Fri, 16 Aug 2024 14:31:58 +0500 Subject: [PATCH 08/19] fix clippy --- mm2src/coins/eth.rs | 4 ++-- mm2src/coins/eth/v2_activation.rs | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index d12987bfac..e9456c0074 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -6440,9 +6440,9 @@ async fn get_max_eth_tx_type_conf(ctx: &MmArc, conf: &Json, coin_type: &EthCoinT if let Some(coin_max_eth_tx_type) = coin_max_eth_tx_type { Ok(Some(coin_max_eth_tx_type)) } else { - // Note that platform_coin is not available when the token is enabled via "enable_eth_with_tokens" + // Note that platform_coin is not available when the token is enabled via "enable_eth_with_tokens" // (but we access platform coin directly - see initialise_erc20_token fn). - // So this code works only for the legacy "enable" rpc, called first for the platform coin then for a token + // So this code works only for the legacy "enable" rpc, called first for the platform coin then for a token let platform_coin = lp_coinfind_or_err(ctx, platform).await; match platform_coin { Ok(MmCoinEnum::EthCoin(eth_coin)) => Ok(eth_coin.max_eth_tx_type), diff --git a/mm2src/coins/eth/v2_activation.rs b/mm2src/coins/eth/v2_activation.rs index 541a11655c..61488fe65a 100644 --- a/mm2src/coins/eth/v2_activation.rs +++ b/mm2src/coins/eth/v2_activation.rs @@ -431,7 +431,9 @@ impl EthCoin { token_addr: protocol.token_addr, }; let platform_fee_estimator_state = FeeEstimatorState::init_fee_estimator(&ctx, &conf, &coin_type).await?; - let max_eth_tx_type = get_max_eth_tx_type_conf(&ctx, &conf, &coin_type).await?.or_else(|| self.max_eth_tx_type); // use the platform coin setting if token setting is none + let max_eth_tx_type = get_max_eth_tx_type_conf(&ctx, &conf, &coin_type) + .await? + .or(self.max_eth_tx_type); // use the platform coin setting if token setting is none let use_access_list = conf["use_access_list"].as_bool().unwrap_or(false); let gas_limit = extract_gas_limit_from_conf(&conf) .map_to_mm(|e| EthTokenActivationError::InternalError(format!("invalid gas_limit config {}", e)))?; @@ -532,7 +534,9 @@ impl EthCoin { platform: self.ticker.clone(), }; let platform_fee_estimator_state = FeeEstimatorState::init_fee_estimator(&ctx, &conf, &coin_type).await?; - let max_eth_tx_type = get_max_eth_tx_type_conf(&ctx, &conf, &coin_type).await?.or_else(|| self.max_eth_tx_type); // use the platform coin setting if token setting is none + let max_eth_tx_type = get_max_eth_tx_type_conf(&ctx, &conf, &coin_type) + .await? + .or(self.max_eth_tx_type); // use the platform coin setting if token setting is none let use_access_list = conf["use_access_list"].as_bool().unwrap_or(false); let gas_limit = extract_gas_limit_from_conf(&conf) .map_to_mm(|e| EthTokenActivationError::InternalError(format!("invalid gas_limit config {}", e)))?; From a7ec766ade5cb498c62c9a6961c322558134c11f Mon Sep 17 00:00:00 2001 From: dimxy Date: Fri, 16 Aug 2024 16:20:37 +0500 Subject: [PATCH 09/19] fix getting gas fee estimator state from platform coin --- mm2src/coins/eth/v2_activation.rs | 20 +++++++++++++++++-- .../coins/rpc_command/get_estimated_fees.rs | 2 +- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/mm2src/coins/eth/v2_activation.rs b/mm2src/coins/eth/v2_activation.rs index 61488fe65a..a998adffb3 100644 --- a/mm2src/coins/eth/v2_activation.rs +++ b/mm2src/coins/eth/v2_activation.rs @@ -430,7 +430,15 @@ impl EthCoin { platform: protocol.platform, token_addr: protocol.token_addr, }; - let platform_fee_estimator_state = FeeEstimatorState::init_fee_estimator(&ctx, &conf, &coin_type).await?; + let mut platform_fee_estimator_state = FeeEstimatorState::init_fee_estimator(&ctx, &conf, &coin_type).await?; + if matches!( + platform_fee_estimator_state.as_ref(), + FeeEstimatorState::PlatformCoinRequired + ) { + platform_fee_estimator_state = self.platform_fee_estimator_state.clone(); + // try to use the state from the platform coin + } + drop_mutability!(platform_fee_estimator_state); let max_eth_tx_type = get_max_eth_tx_type_conf(&ctx, &conf, &coin_type) .await? .or(self.max_eth_tx_type); // use the platform coin setting if token setting is none @@ -533,7 +541,15 @@ impl EthCoin { let coin_type = EthCoinType::Nft { platform: self.ticker.clone(), }; - let platform_fee_estimator_state = FeeEstimatorState::init_fee_estimator(&ctx, &conf, &coin_type).await?; + let mut platform_fee_estimator_state = FeeEstimatorState::init_fee_estimator(&ctx, &conf, &coin_type).await?; + if matches!( + platform_fee_estimator_state.as_ref(), + FeeEstimatorState::PlatformCoinRequired + ) { + platform_fee_estimator_state = self.platform_fee_estimator_state.clone(); + // try to use the state from the platform coin + } + drop_mutability!(platform_fee_estimator_state); let max_eth_tx_type = get_max_eth_tx_type_conf(&ctx, &conf, &coin_type) .await? .or(self.max_eth_tx_type); // use the platform coin setting if token setting is none diff --git a/mm2src/coins/rpc_command/get_estimated_fees.rs b/mm2src/coins/rpc_command/get_estimated_fees.rs index b62e572756..a6fcf41edf 100644 --- a/mm2src/coins/rpc_command/get_estimated_fees.rs +++ b/mm2src/coins/rpc_command/get_estimated_fees.rs @@ -179,7 +179,7 @@ impl FeeEstimatorState { Ok(Arc::new(fee_estimator_state)) }, (_, EthCoinType::Erc20 { platform, .. }) | (_, EthCoinType::Nft { platform, .. }) => { - let platform_coin = lp_coinfind_or_err(ctx, platform).await; + let platform_coin = lp_coinfind_or_err(ctx, platform).await; // NOTE: this won't find the platform coin when "enable_eth_with_tokens" rpc is used. So we need to get platform coin config from 'self', see initialize_erc20_token fn match platform_coin { Ok(MmCoinEnum::EthCoin(eth_coin)) => Ok(eth_coin.platform_fee_estimator_state.clone()), _ => Ok(Arc::new(FeeEstimatorState::PlatformCoinRequired)), From 9688ec997ff3b1e5925502cf1399d1b18903511e Mon Sep 17 00:00:00 2001 From: dimxy Date: Fri, 16 Aug 2024 23:30:57 +0500 Subject: [PATCH 10/19] add swap negotiation protocol version support and eth tx type activation by remote version --- mm2src/coins/eth.rs | 54 +++++++++++--- mm2src/coins/eth/eth_swap_v2.rs | 2 + mm2src/coins/eth/nft_swap_v2/mod.rs | 4 + mm2src/coins/lp_coins.rs | 15 ++++ mm2src/coins/solana/solana_tests.rs | 4 +- mm2src/coins/swap_features.rs | 18 +++++ mm2src/coins/z_coin/z_coin_native_tests.rs | 4 +- mm2src/mm2_main/Cargo.toml | 2 + mm2src/mm2_main/src/lp_swap.rs | 73 +++++++++++++++++-- mm2src/mm2_main/src/lp_swap/maker_swap.rs | 59 +++++++++++++-- .../src/lp_swap/recreate_swap_data.rs | 6 +- mm2src/mm2_main/src/lp_swap/taker_swap.rs | 47 +++++++++++- .../tests/docker_tests/docker_tests_inner.rs | 7 +- .../tests/docker_tests/eth_docker_tests.rs | 7 +- .../tests/docker_tests/qrc20_tests.rs | 14 +++- .../tests/docker_tests/swap_watcher_tests.rs | 17 ++++- 16 files changed, 299 insertions(+), 34 deletions(-) create mode 100644 mm2src/coins/swap_features.rs diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index e9456c0074..cdfa5cefe0 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -97,6 +97,7 @@ use std::str::FromStr; use std::sync::atomic::{AtomicU64, Ordering as AtomicOrdering}; use std::sync::{Arc, Mutex}; use std::time::Duration; +use swap_features::SwapFeature; use web3::types::{Action as TraceAction, BlockId, BlockNumber, Bytes, CallRequest, FilterBuilder, Log, Trace, TraceFilterBuilder, Transaction as Web3Transaction, TransactionId, TransactionRequest, U64}; use web3::{self, Web3}; @@ -2529,13 +2530,23 @@ async fn sign_transaction_with_keypair<'a>( pay_for_gas_option: &PayForGasOption, from_address: Address, access_list: Option, + can_use_tx_type: bool, // normally true, set to false if remote taker or maker is not upgraded to support typed tx ) -> Result<(SignedEthTx, Vec), TransactionErr> { info!(target: "sign", "get_addr_nonce…"); let (nonce, web3_instances_with_latest_nonce) = try_tx_s!(coin.clone().get_addr_nonce(from_address).compat().await); let mut tx_type = TxType::Legacy; - set_tx_type_from_access_list!(tx_type, access_list); - set_tx_type_from_pay_for_gas_option!(tx_type, pay_for_gas_option); + if can_use_tx_type { + set_tx_type_from_access_list!(tx_type, access_list); + set_tx_type_from_pay_for_gas_option!(tx_type, pay_for_gas_option); + } drop_mutability!(tx_type); + let tx_type_as_num = match tx_type { + TxType::Legacy => 0_u64, + TxType::Type1 => 1_u64, + TxType::Type2 => 2_u64, + TxType::Invalid => 99_u64, + }; + debug!("sign_transaction_with_keypair tx_type={tx_type_as_num} can_use_tx_type={can_use_tx_type}"); if !coin.is_tx_type_supported(&tx_type) { return Err(TransactionErr::Plain("Eth transaction type not supported".into())); } @@ -2569,6 +2580,7 @@ async fn sign_and_send_transaction_with_keypair( data: Vec, gas: U256, access_list: Option, + can_use_tx_type: bool, ) -> Result { info!(target: "sign-and-send", "get_gas_price…"); let pay_for_gas_option = try_tx_s!( @@ -2587,6 +2599,7 @@ async fn sign_and_send_transaction_with_keypair( &pay_for_gas_option, address, access_list, + can_use_tx_type, ) .await?; let bytes = Bytes(rlp::encode(&signed).to_vec()); @@ -2733,6 +2746,7 @@ async fn sign_raw_eth_tx(coin: &EthCoin, args: &SignEthTransactionParams) -> Raw &pay_for_gas_option, my_address, access_list, + true, ) .await .map(|(signed_tx, _)| RawTransactionRes { @@ -3682,6 +3696,7 @@ impl EthCoin { data: Vec, gas: U256, access_list: Option, + can_use_tx_type: bool, ) -> EthTxFut { let coin = self.clone(); let fut = async move { @@ -3705,6 +3720,7 @@ impl EthCoin { data, gas, access_list, + can_use_tx_type, ) .await }, @@ -3726,6 +3742,7 @@ impl EthCoin { vec![], U256::from(self.gas_limit.eth_send_coins), None, + true, ), EthCoinType::Erc20 { platform: _, @@ -3740,6 +3757,7 @@ impl EthCoin { data, U256::from(self.gas_limit.eth_send_erc20), None, + true, ) }, EthCoinType::Nft { .. } => Box::new(futures01::future::err(TransactionErr::ProtocolNotSupported(ERRL!( @@ -3761,6 +3779,12 @@ impl EthCoin { } else { args.secret_hash.to_vec() }; + let can_use_tx_type = SwapFeature::is_active(SwapFeature::EthTypeTx, args.other_version); + debug!( + "send_hash_time_locked_payment coin={} can_use_tx_type={can_use_tx_type} args.other_version={}", + self.ticker(), + args.other_version + ); match &self.coin_type { EthCoinType::Eth => { @@ -3806,7 +3830,14 @@ impl EthCoin { .map(Some) .or_else(|_err| futures01::future::ok(None::)) // ignore create_access_list_if_configured errors and use None value .and_then(move |list| { - coin.sign_and_send_transaction(value, Action::Call(swap_contract_address), data, gas, list) + coin.sign_and_send_transaction( + value, + Action::Call(swap_contract_address), + data, + gas, + list, + can_use_tx_type, + ) }), ) }, @@ -3913,6 +3944,7 @@ impl EthCoin { data, gas, list, + can_use_tx_type, ) }) }) @@ -3925,7 +3957,7 @@ impl EthCoin { .map(move |list| remove_from_access_list(&list, &[swap_contract_address])) .map(Some) .or_else(|_err| futures01::future::ok(None::)) - .and_then(move |list| arc.sign_and_send_transaction(value, Action::Call(swap_contract_address), data, gas, list)) + .and_then(move |list| arc.sign_and_send_transaction(value, Action::Call(swap_contract_address), data, gas, list, can_use_tx_type)) ) } })) @@ -3998,6 +4030,7 @@ impl EthCoin { data, U256::from(clone.gas_limit.eth_receiver_spend), None, + true, ) }), ) @@ -4047,6 +4080,7 @@ impl EthCoin { data, U256::from(clone.gas_limit.erc20_receiver_spend), None, + true, ) }), ) @@ -4120,6 +4154,7 @@ impl EthCoin { data, U256::from(clone.gas_limit.eth_sender_refund), None, + true, ) }), ) @@ -4172,6 +4207,7 @@ impl EthCoin { data, U256::from(clone.gas_limit.erc20_sender_refund), None, + true, ) }), ) @@ -4250,7 +4286,7 @@ impl EthCoin { .await .map(|list| remove_from_access_list(&list, &[swap_contract_address])) .ok(); - self.sign_and_send_transaction(0.into(), Call(swap_contract_address), data, gas, access_list) + self.sign_and_send_transaction(0.into(), Call(swap_contract_address), data, gas, access_list, true) .compat() .await }, @@ -4309,7 +4345,7 @@ impl EthCoin { .await .map(|list| remove_from_access_list(&list, &[swap_contract_address])) .ok(); - self.sign_and_send_transaction(0.into(), Call(swap_contract_address), data, gas, access_list) + self.sign_and_send_transaction(0.into(), Call(swap_contract_address), data, gas, access_list, true) .compat() .await }, @@ -4382,7 +4418,7 @@ impl EthCoin { .await .map(|list| remove_from_access_list(&list, &[swap_contract_address])) .ok(); - self.sign_and_send_transaction(0.into(), Call(swap_contract_address), data, gas, access_list) + self.sign_and_send_transaction(0.into(), Call(swap_contract_address), data, gas, access_list, true) .compat() .await }, @@ -4441,7 +4477,7 @@ impl EthCoin { .await .map(|list| remove_from_access_list(&list, &[swap_contract_address])) .ok(); // ignore errors - self.sign_and_send_transaction(0.into(), Call(swap_contract_address), data, gas, access_list) + self.sign_and_send_transaction(0.into(), Call(swap_contract_address), data, gas, access_list, true) .compat() .await }, @@ -4758,7 +4794,7 @@ impl EthCoin { .await ); - coin.sign_and_send_transaction(0.into(), Call(token_addr), data, gas_limit, None) + coin.sign_and_send_transaction(0.into(), Call(token_addr), data, gas_limit, None, true) .compat() .await }; diff --git a/mm2src/coins/eth/eth_swap_v2.rs b/mm2src/coins/eth/eth_swap_v2.rs index 0a7083bb37..2e44a674bc 100644 --- a/mm2src/coins/eth/eth_swap_v2.rs +++ b/mm2src/coins/eth/eth_swap_v2.rs @@ -60,6 +60,7 @@ impl EthCoin { // TODO need new consts and params for v2 calls. now it uses v1 U256::from(self.gas_limit.eth_payment), None, + true, ) .compat() .await @@ -94,6 +95,7 @@ impl EthCoin { // TODO need new consts and params for v2 calls. now it uses v1 U256::from(self.gas_limit.erc20_payment), None, + true, ) .compat() .await diff --git a/mm2src/coins/eth/nft_swap_v2/mod.rs b/mm2src/coins/eth/nft_swap_v2/mod.rs index a1ed637f9f..8c3443378c 100644 --- a/mm2src/coins/eth/nft_swap_v2/mod.rs +++ b/mm2src/coins/eth/nft_swap_v2/mod.rs @@ -42,6 +42,7 @@ impl EthCoin { data, U256::from(self.gas_limit.eth_max_trade_gas), // TODO: fix to a more accurate const or estimated value None, + true, ) .compat() .await @@ -161,6 +162,7 @@ impl EthCoin { data, U256::from(self.gas_limit.eth_max_trade_gas), // TODO: fix to a more accurate const or estimated value None, + true, ) .compat() .await @@ -201,6 +203,7 @@ impl EthCoin { data, U256::from(self.gas_limit.eth_max_trade_gas), // TODO: fix to a more accurate const or estimated value None, + true, ) .compat() .await @@ -242,6 +245,7 @@ impl EthCoin { data, U256::from(self.gas_limit.eth_max_trade_gas), // TODO: fix to a more accurate const or estimated value None, + true, ) .compat() .await diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index b611e59759..8da088f107 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -321,6 +321,19 @@ use z_coin::{ZCoin, ZcoinProtocolInfo}; #[cfg(feature = "enable-sia")] pub mod sia; #[cfg(feature = "enable-sia")] use sia::SiaCoin; +mod swap_features; + +/// Default swap protocol version before version field added to NegotiationDataMsg +pub const LEGACY_PROTOCOL_VERSION: u16 = 0; + +/// Current swap protocol version +pub const SWAP_PROTOCOL_VERSION: u16 = 1; + +/// Minimal supported swap protocol version implemented by remote peer +pub const MIN_SWAP_PROTOCOL_VERSION: u16 = LEGACY_PROTOCOL_VERSION; + +// TODO: add version field to the SWAP V2 negotiation protocol + pub type TransactionFut = Box + Send>; pub type TransactionResult = Result; pub type BalanceResult = Result>; @@ -959,6 +972,8 @@ pub struct SendPaymentArgs<'a> { pub watcher_reward: Option, /// As of now, this field is specifically used to wait for confirmations of ERC20 approval transaction. pub wait_for_confirmation_until: u64, + /// other party version + pub other_version: u16, } #[derive(Clone, Debug)] diff --git a/mm2src/coins/solana/solana_tests.rs b/mm2src/coins/solana/solana_tests.rs index fe2b104293..e5cda39139 100644 --- a/mm2src/coins/solana/solana_tests.rs +++ b/mm2src/coins/solana/solana_tests.rs @@ -11,7 +11,7 @@ use super::solana_common_tests::{generate_key_pair_from_iguana_seed, generate_ke solana_coin_for_test, SolanaNet}; use super::solana_decode_tx_helpers::SolanaConfirmedTransaction; use super::*; -use crate::{MarketCoinOps, SwapTxTypeWithSecretHash}; +use crate::{MarketCoinOps, SwapTxTypeWithSecretHash, SWAP_PROTOCOL_VERSION}; #[test] #[cfg(not(target_arch = "wasm32"))] @@ -356,6 +356,7 @@ fn solana_coin_send_and_refund_maker_payment() { let secret_hash = sha256(&secret); let args = SendPaymentArgs { + other_version: SWAP_PROTOCOL_VERSION, time_lock_duration: 0, time_lock, other_pubkey: taker_pub.as_ref(), @@ -403,6 +404,7 @@ fn solana_coin_send_and_spend_maker_payment() { let secret_hash = sha256(&secret); let maker_payment_args = SendPaymentArgs { + other_version: SWAP_PROTOCOL_VERSION, time_lock_duration: 0, time_lock: lock_time, other_pubkey: taker_pub.as_ref(), diff --git a/mm2src/coins/swap_features.rs b/mm2src/coins/swap_features.rs new file mode 100644 index 0000000000..1f2c2e2ba4 --- /dev/null +++ b/mm2src/coins/swap_features.rs @@ -0,0 +1,18 @@ +/// Framework to activate new swap protocol features at certain protocol version + +#[derive(PartialEq)] +pub(crate) enum SwapFeature { + EthTypeTx, // eth type transaction EIP-2718 supported +} + +impl SwapFeature { + // add new features to activate + const SWAP_FEATURE_ACTIVATION: &[(u16, SwapFeature)] = &[(1, SwapFeature::EthTypeTx)]; + + pub(crate) fn is_active(feature: SwapFeature, version: u16) -> bool { + if let Some(found) = Self::SWAP_FEATURE_ACTIVATION.iter().find(|fv| fv.1 == feature) { + return version >= found.0; + } + false + } +} diff --git a/mm2src/coins/z_coin/z_coin_native_tests.rs b/mm2src/coins/z_coin/z_coin_native_tests.rs index 9e4358727d..295858ee63 100644 --- a/mm2src/coins/z_coin/z_coin_native_tests.rs +++ b/mm2src/coins/z_coin/z_coin_native_tests.rs @@ -11,7 +11,7 @@ use super::{z_coin_from_conf_and_params_with_z_key, z_mainnet_constants, Future, ZTransaction}; use crate::z_coin::{z_htlc::z_send_dex_fee, ZcoinActivationParams, ZcoinRpcMode}; use crate::DexFee; -use crate::{CoinProtocol, SwapTxTypeWithSecretHash}; +use crate::{CoinProtocol, SwapTxTypeWithSecretHash, SWAP_PROTOCOL_VERSION}; use mm2_number::MmNumber; #[test] @@ -44,6 +44,7 @@ fn zombie_coin_send_and_refund_maker_payment() { let secret_hash = [0; 20]; let args = SendPaymentArgs { + other_version: SWAP_PROTOCOL_VERSION, time_lock_duration: 0, time_lock, other_pubkey: taker_pub, @@ -104,6 +105,7 @@ fn zombie_coin_send_and_spend_maker_payment() { let secret_hash = dhash160(&secret); let maker_payment_args = SendPaymentArgs { + other_version: SWAP_PROTOCOL_VERSION, time_lock_duration: 0, time_lock: lock_time, other_pubkey: taker_pub, diff --git a/mm2src/mm2_main/Cargo.toml b/mm2src/mm2_main/Cargo.toml index f3232ac91e..9c35951193 100644 --- a/mm2src/mm2_main/Cargo.toml +++ b/mm2src/mm2_main/Cargo.toml @@ -24,6 +24,8 @@ default = [] trezor-udp = ["crypto/trezor-udp"] # use for tests to connect to trezor emulator over udp run-device-tests = [] enable-sia = [] +test-use-old-maker = [] +test-use-old-taker = [] [dependencies] async-std = { version = "1.5", features = ["unstable"] } diff --git a/mm2src/mm2_main/src/lp_swap.rs b/mm2src/mm2_main/src/lp_swap.rs index c7665da02e..23cf1fda6f 100644 --- a/mm2src/mm2_main/src/lp_swap.rs +++ b/mm2src/mm2_main/src/lp_swap.rs @@ -62,7 +62,8 @@ use crate::lp_network::{broadcast_p2p_msg, Libp2pPeerId, P2PProcessError, P2PPro use crate::lp_swap::maker_swap_v2::{MakerSwapStateMachine, MakerSwapStorage}; use crate::lp_swap::taker_swap_v2::{TakerSwapStateMachine, TakerSwapStorage}; use bitcrypto::{dhash160, sha256}; -use coins::{lp_coinfind, lp_coinfind_or_err, CoinFindError, DexFee, MmCoin, MmCoinEnum, TradeFee, TransactionEnum}; +use coins::{lp_coinfind, lp_coinfind_or_err, CoinFindError, DexFee, MmCoin, MmCoinEnum, TradeFee, TransactionEnum, + LEGACY_PROTOCOL_VERSION}; use common::log::{debug, warn}; use common::now_sec; use common::time_cache::DuplicateCache; @@ -169,7 +170,9 @@ cfg_wasm32! { #[derive(Clone, Debug, Eq, Deserialize, PartialEq, Serialize)] pub enum SwapMsg { Negotiation(NegotiationDataMsg), + NegotiationVersioned(NegotiationDataMsgVersion), NegotiationReply(NegotiationDataMsg), + NegotiationReplyVersioned(NegotiationDataMsgVersion), Negotiated(bool), TakerFee(SwapTxDataMsg), MakerPayment(SwapTxDataMsg), @@ -178,8 +181,8 @@ pub enum SwapMsg { #[derive(Debug, Default)] pub struct SwapMsgStore { - negotiation: Option, - negotiation_reply: Option, + negotiation: Option, + negotiation_reply: Option, negotiated: Option, taker_fee: Option, maker_payment: Option, @@ -257,18 +260,23 @@ pub fn p2p_private_and_peer_id_to_broadcast(ctx: &MmArc, p2p_privkey: Option<&Ke } } -/// Spawns the loop that broadcasts message every `interval` seconds returning the AbortOnDropHandle +/// Spawns the loop that broadcasts group of messages every `interval` seconds returning the AbortOnDropHandle /// to stop it +/// TODO: several messages allowed to try first new version of requests. We can deprecate this when all nodes upgrade pub fn broadcast_swap_msg_every( ctx: MmArc, topic: String, - msg: T, + msgs: Vec, interval_sec: f64, p2p_privkey: Option, ) -> AbortOnDropHandle { let fut = async move { loop { - broadcast_swap_message(&ctx, topic.clone(), msg.clone(), &p2p_privkey); + let msgs_cloned = msgs.clone(); + for msg in msgs_cloned { + broadcast_swap_message(&ctx, topic.clone(), msg, &p2p_privkey); + Timer::sleep(5.0).await; // this a delay between tries of new and old requests (to ensure the receiver processes the new message first) + } Timer::sleep(interval_sec).await; } }; @@ -361,8 +369,32 @@ pub async fn process_swap_msg(ctx: MmArc, topic: &str, msg: &[u8]) -> P2PRequest if let Some(msg_store) = msgs.get_mut(&uuid) { if msg_store.accept_only_from.bytes == msg.2.unprefixed() { match msg.0 { - SwapMsg::Negotiation(data) => msg_store.negotiation = Some(data), - SwapMsg::NegotiationReply(data) => msg_store.negotiation_reply = Some(data), + // build NegotiationDataMsgVersion from legacy NegotiationDataMsg with default version: + SwapMsg::Negotiation(data) => { + msg_store.negotiation = Some(NegotiationDataMsgVersion { + version: LEGACY_PROTOCOL_VERSION, + msg: data, + }) + }, + SwapMsg::NegotiationVersioned(data) => { + if cfg!(not(feature = "test-use-old-taker")) { + // ignore versioned msg for old taker emulation + msg_store.negotiation = Some(data); + } + }, + // build NegotiationDataMsgVersion from legacy NegotiationDataMsg with default version: + SwapMsg::NegotiationReply(data) => { + msg_store.negotiation_reply = Some(NegotiationDataMsgVersion { + version: LEGACY_PROTOCOL_VERSION, + msg: data, + }) + }, + SwapMsg::NegotiationReplyVersioned(data) => { + if cfg!(not(feature = "test-use-old-maker")) { + // ignore versioned msg for old maker emulation + msg_store.negotiation_reply = Some(data); + } + }, SwapMsg::Negotiated(negotiated) => msg_store.negotiated = Some(negotiated), SwapMsg::TakerFee(data) => msg_store.taker_fee = Some(data), SwapMsg::MakerPayment(data) => msg_store.maker_payment = Some(data), @@ -940,6 +972,31 @@ impl NegotiationDataMsg { } } +/// NegotiationDataMsg with version +#[derive(Clone, Debug, Eq, Deserialize, PartialEq, Serialize)] +pub struct NegotiationDataMsgVersion { + version: u16, + msg: NegotiationDataMsg, +} + +impl NegotiationDataMsgVersion { + pub fn version(&self) -> u16 { self.version } + + pub fn started_at(&self) -> u64 { self.msg.started_at() } + + pub fn payment_locktime(&self) -> u64 { self.msg.payment_locktime() } + + pub fn secret_hash(&self) -> &[u8] { self.msg.secret_hash() } + + pub fn maker_coin_htlc_pub(&self) -> &[u8] { self.msg.maker_coin_htlc_pub() } + + pub fn taker_coin_htlc_pub(&self) -> &[u8] { self.msg.taker_coin_htlc_pub() } + + pub fn maker_coin_swap_contract(&self) -> Option<&[u8]> { self.msg.maker_coin_swap_contract() } + + pub fn taker_coin_swap_contract(&self) -> Option<&[u8]> { self.msg.taker_coin_swap_contract() } +} + #[derive(Clone, Debug, Eq, Deserialize, PartialEq, Serialize)] pub struct PaymentWithInstructions { data: Vec, diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap.rs b/mm2src/mm2_main/src/lp_swap/maker_swap.rs index 82e3809c6c..785be634d5 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap.rs @@ -14,12 +14,17 @@ use crate::lp_dispatcher::{DispatcherContext, LpEvents}; use crate::lp_network::subscribe_to_topic; use crate::lp_ordermatch::MakerOrderBuilder; use crate::lp_swap::swap_v2_common::mark_swap_as_finished; +#[cfg(not(feature = "test-use-old-maker"))] +use crate::lp_swap::NegotiationDataMsgVersion; use crate::lp_swap::{broadcast_swap_message, taker_payment_spend_duration, MAX_STARTED_AT_DIFF}; use coins::lp_price::fetch_swap_coins_price; +#[cfg(not(feature = "test-use-old-maker"))] +use coins::SWAP_PROTOCOL_VERSION; use coins::{CanRefundHtlc, CheckIfMyPaymentSentArgs, ConfirmPaymentInput, FeeApproxStage, FoundSwapTxSpend, MmCoin, MmCoinEnum, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, RefundPaymentArgs, SearchForSwapTxSpendInput, SendPaymentArgs, SpendPaymentArgs, SwapTxTypeWithSecretHash, TradeFee, - TradePreimageValue, TransactionEnum, ValidateFeeArgs, ValidatePaymentInput}; + TradePreimageValue, TransactionEnum, ValidateFeeArgs, ValidatePaymentInput, LEGACY_PROTOCOL_VERSION, + MIN_SWAP_PROTOCOL_VERSION}; 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}; @@ -120,6 +125,9 @@ async fn save_my_maker_swap_event(ctx: &MmArc, swap: &MakerSwap, event: MakerSav #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] pub struct TakerNegotiationData { + /// Protocol version supported by taker. Optional because it was added in V4 of NegotiationDataMsg + /// so it could be read as None from a saved swap if the swap started before upgrade to V4 + pub taker_version: Option, pub taker_payment_locktime: u64, pub taker_pubkey: H264Json, pub maker_coin_swap_contract_addr: Option, @@ -154,6 +162,8 @@ pub struct MakerSwapData { pub maker_payment_lock: u64, /// Allows to recognize one SWAP from the other in the logs. #274. pub uuid: Uuid, + /// Swap protocol version that taker supports. This field introduced with NegotiationDataMsgVersion so may be optional for past messages + pub taker_version: Option, pub started_at: u64, pub maker_coin_start_block: u64, pub taker_coin_start_block: u64, @@ -289,6 +299,7 @@ impl MakerSwap { }, MakerSwapEvent::StartFailed(err) => self.errors.lock().push(err), MakerSwapEvent::Negotiated(data) => { + self.w().data.taker_version = Some(fix_taker_version(&data)); self.taker_payment_lock .store(data.taker_payment_locktime, Ordering::Relaxed); self.w().other_maker_coin_htlc_pub = data.other_maker_coin_htlc_pub(); @@ -550,6 +561,7 @@ impl MakerSwap { taker: self.taker.bytes.into(), secret: self.secret.into(), secret_hash: Some(self.secret_hash().into()), + taker_version: None, // not yet known before the negotiation stage started_at, lock_duration: self.payment_locktime, maker_amount: self.maker_amount.clone(), @@ -581,15 +593,31 @@ impl MakerSwap { async fn negotiate(&self) -> Result<(Option, Vec), String> { let negotiation_data = self.get_my_negotiation_data(); + let mut msgs = vec![]; + + // Important to try the new versioned negotiation message first + // (Run swap tests with "test-use-old-maker" feature to emulate old maker, sending non-versioned message only) + #[cfg(not(feature = "test-use-old-maker"))] + { + let maker_versioned_negotiation_msg = SwapMsg::NegotiationVersioned(NegotiationDataMsgVersion { + version: SWAP_PROTOCOL_VERSION, + msg: negotiation_data.clone(), + }); + msgs.push(maker_versioned_negotiation_msg); + } + + let maker_old_negotiation_msg = SwapMsg::Negotiation(negotiation_data); + msgs.push(maker_old_negotiation_msg); - let maker_negotiation_data = SwapMsg::Negotiation(negotiation_data); const NEGOTIATION_TIMEOUT_SEC: u64 = 90; - debug!("Sending maker negotiation data {:?}", maker_negotiation_data); + debug!("Sending maker negotiation data {:?}", msgs); + // Send both old and new negotiation data to determine whether the remote node is old (supports NegotiationDataMsg) or new (supports NegotiationDataMsgVersion). + // When all nodes upgrade to NegotiationDataMsgVersion we won't need to send both messages and will use 'version' field in NegotiationDataMsgVersion let send_abort_handle = broadcast_swap_msg_every( self.ctx.clone(), swap_topic(&self.uuid), - maker_negotiation_data, + msgs, NEGOTIATION_TIMEOUT_SEC as f64 / 6., self.p2p_privkey, ); @@ -609,6 +637,17 @@ impl MakerSwap { }, }; drop(send_abort_handle); + + let remote_version = taker_data.version(); + // this check will work when we raise minimal swap protocol version + #[allow(clippy::absurd_extreme_comparisons)] + if remote_version < MIN_SWAP_PROTOCOL_VERSION { + self.broadcast_negotiated_false(); + return Ok((Some(MakerSwapCommand::Finish), vec![MakerSwapEvent::NegotiateFailed( + ERRL!("Taker protocol version {} too old", remote_version).into(), + )])); + } + let time_dif = self.r().data.started_at.abs_diff(taker_data.started_at()); if time_dif > MAX_STARTED_AT_DIFF { self.broadcast_negotiated_false(); @@ -674,6 +713,7 @@ impl MakerSwap { Ok((Some(MakerSwapCommand::WaitForTakerFee), vec![ MakerSwapEvent::Negotiated(TakerNegotiationData { + taker_version: Some(remote_version), taker_payment_locktime: taker_data.payment_locktime(), // using default to avoid misuse of this field // maker_coin_htlc_pubkey and taker_coin_htlc_pubkey must be used instead @@ -692,7 +732,7 @@ impl MakerSwap { let send_abort_handle = broadcast_swap_msg_every( self.ctx.clone(), swap_topic(&self.uuid), - negotiated, + vec![negotiated], TAKER_FEE_RECV_TIMEOUT_SEC as f64 / 6., self.p2p_privkey, ); @@ -842,6 +882,7 @@ impl MakerSwap { Some(tx) => tx, None => { let payment_fut = self.maker_coin.send_maker_payment(SendPaymentArgs { + other_version: self.r().data.taker_version.unwrap_or(LEGACY_PROTOCOL_VERSION), // normally taker_version should unwrap okay time_lock_duration: self.r().data.lock_duration, time_lock: self.r().data.maker_payment_lock, other_pubkey: &*self.r().other_maker_coin_htlc_pub, @@ -903,7 +944,7 @@ impl MakerSwap { let abort_send_handle = broadcast_swap_msg_every( self.ctx.clone(), swap_topic(&self.uuid), - msg, + vec![msg], PAYMENT_MSG_INTERVAL_SEC, self.p2p_privkey, ); @@ -1831,6 +1872,7 @@ impl MakerSavedSwap { maker_payment_lock: 0, uuid: Default::default(), started_at: 0, + taker_version: None, maker_coin_start_block: 0, taker_coin_start_block: 0, maker_payment_trade_fee: None, @@ -2351,6 +2393,11 @@ pub async fn calc_max_maker_vol( }) } +/// Determine version from negotiation data if saved swap data does not store version +/// (if the swap started before the upgrade to versioned negotiation message) +/// In any case it is very undesirable to upgrade mm2 when any swaps are active +fn fix_taker_version(negotiation_data: &TakerNegotiationData) -> u16 { negotiation_data.taker_version.unwrap_or(0) } + #[cfg(all(test, not(target_arch = "wasm32")))] mod maker_swap_tests { use super::*; diff --git a/mm2src/mm2_main/src/lp_swap/recreate_swap_data.rs b/mm2src/mm2_main/src/lp_swap/recreate_swap_data.rs index 0d139df62f..820bbb2ef2 100644 --- a/mm2src/mm2_main/src/lp_swap/recreate_swap_data.rs +++ b/mm2src/mm2_main/src/lp_swap/recreate_swap_data.rs @@ -4,7 +4,7 @@ use crate::lp_swap::taker_swap::{MakerNegotiationData, TakerPaymentSpentData, Ta TakerSwapEvent, TAKER_ERROR_EVENTS, TAKER_SUCCESS_EVENTS}; use crate::lp_swap::{wait_for_maker_payment_conf_until, MakerSavedEvent, MakerSavedSwap, SavedSwap, SwapError, TakerSavedSwap}; -use coins::{lp_coinfind, MmCoinEnum}; +use coins::{lp_coinfind, MmCoinEnum, SWAP_PROTOCOL_VERSION}; use common::{HttpStatusCode, StatusCode}; use derive_more::Display; use mm2_core::mm_ctx::MmArc; @@ -138,6 +138,7 @@ fn recreate_maker_swap(ctx: MmArc, taker_swap: TakerSavedSwap) -> RecreateSwapRe maker_payment_lock: negotiated_event.maker_payment_locktime, uuid: started_event.uuid, started_at: started_event.started_at, + taker_version: None, maker_coin_start_block: started_event.maker_coin_start_block, taker_coin_start_block: started_event.taker_coin_start_block, // Don't set the fee since the value is used when we calculate locked by other swaps amount only. @@ -158,6 +159,7 @@ fn recreate_maker_swap(ctx: MmArc, taker_swap: TakerSavedSwap) -> RecreateSwapRe // Generate `Negotiated` event let maker_negotiated_event = MakerSwapEvent::Negotiated(TakerNegotiationData { + taker_version: Some(SWAP_PROTOCOL_VERSION), taker_payment_locktime: started_event.taker_payment_lock, taker_pubkey: started_event.my_persistent_pub, maker_coin_swap_contract_addr: negotiated_event.maker_coin_swap_contract_addr, @@ -319,6 +321,7 @@ async fn recreate_taker_swap(ctx: MmArc, maker_swap: MakerSavedSwap) -> Recreate let mut maker_p2p_pubkey = [0; 32]; maker_p2p_pubkey.copy_from_slice(&started_event.my_persistent_pub.0[1..33]); let taker_started_event = TakerSwapEvent::Started(TakerSwapData { + maker_version: Some(SWAP_PROTOCOL_VERSION), taker_coin: started_event.taker_coin, maker_coin: started_event.maker_coin.clone(), maker: H256Json::from(maker_p2p_pubkey), @@ -358,6 +361,7 @@ async fn recreate_taker_swap(ctx: MmArc, maker_swap: MakerSavedSwap) -> Recreate .or_mm_err(|| RecreateSwapError::NoSecretHash)?; let taker_negotiated_event = TakerSwapEvent::Negotiated(MakerNegotiationData { + maker_version: Some(SWAP_PROTOCOL_VERSION), maker_payment_locktime: started_event.maker_payment_lock, maker_pubkey: started_event.my_persistent_pub, secret_hash: secret_hash.clone(), diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap.rs b/mm2src/mm2_main/src/lp_swap/taker_swap.rs index 1b72199244..aaddd7afc6 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap.rs @@ -14,13 +14,18 @@ use crate::lp_network::subscribe_to_topic; use crate::lp_ordermatch::TakerOrderBuilder; use crate::lp_swap::swap_v2_common::mark_swap_as_finished; use crate::lp_swap::taker_restart::get_command_based_on_maker_or_watcher_activity; +#[cfg(not(feature = "test-use-old-taker"))] +use crate::lp_swap::NegotiationDataMsgVersion; use crate::lp_swap::{broadcast_p2p_tx_msg, broadcast_swap_msg_every_delayed, tx_helper_topic, wait_for_maker_payment_conf_duration, TakerSwapWatcherData, MAX_STARTED_AT_DIFF}; use coins::lp_price::fetch_swap_coins_price; +#[cfg(not(feature = "test-use-old-taker"))] +use coins::SWAP_PROTOCOL_VERSION; use coins::{lp_coinfind, CanRefundHtlc, CheckIfMyPaymentSentArgs, ConfirmPaymentInput, FeeApproxStage, FoundSwapTxSpend, MmCoin, MmCoinEnum, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, RefundPaymentArgs, SearchForSwapTxSpendInput, SendPaymentArgs, SpendPaymentArgs, SwapTxTypeWithSecretHash, - TradeFee, TradePreimageValue, ValidatePaymentInput, WaitForHTLCTxSpendArgs}; + TradeFee, TradePreimageValue, ValidatePaymentInput, WaitForHTLCTxSpendArgs, LEGACY_PROTOCOL_VERSION, + MIN_SWAP_PROTOCOL_VERSION}; use common::executor::Timer; use common::log::{debug, error, info, warn}; use common::{bits256, now_ms, now_sec, wait_until_sec, DEX_FEE_ADDR_RAW_PUBKEY}; @@ -522,6 +527,7 @@ pub struct TakerSwapData { /// Allows to recognize one SWAP from the other in the logs. #274. pub uuid: Uuid, pub started_at: u64, + pub maker_version: Option, pub maker_payment_wait: u64, pub maker_coin_start_block: u64, pub taker_coin_start_block: u64, @@ -621,6 +627,7 @@ pub struct TakerPaymentSpentData { #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] pub struct MakerNegotiationData { + pub maker_version: Option, pub maker_payment_locktime: u64, pub maker_pubkey: H264Json, pub secret_hash: BytesJson, @@ -798,6 +805,7 @@ impl TakerSwap { TakerSwapEvent::Negotiated(data) => { self.maker_payment_lock .store(data.maker_payment_locktime, Ordering::Relaxed); + self.w().data.maker_version = Some(fix_maker_version(&data)); self.w().other_maker_coin_htlc_pub = data.other_maker_coin_htlc_pub(); self.w().other_taker_coin_htlc_pub = data.other_taker_coin_htlc_pub(); self.w().secret_hash = data.secret_hash; @@ -1083,6 +1091,7 @@ impl TakerSwap { maker_coin: self.maker_coin.ticker().to_owned(), maker: self.maker.bytes.into(), started_at, + maker_version: None, // not yet known on start lock_duration: self.payment_locktime, maker_amount: self.maker_amount.to_decimal(), taker_amount: self.taker_amount.to_decimal(), @@ -1138,6 +1147,15 @@ impl TakerSwap { )])); } + let remote_version = maker_data.version(); + // this check will work when we raise minimal swap protocol version + #[allow(clippy::absurd_extreme_comparisons)] + if remote_version < MIN_SWAP_PROTOCOL_VERSION { + return Ok((Some(TakerSwapCommand::Finish), vec![TakerSwapEvent::NegotiateFailed( + ERRL!("Maker protocol version {} too old", remote_version).into(), + )])); + } + let customized_lock_duration = (self.r().data.lock_duration as f64 * self.taker_coin.maker_locktime_multiplier()).ceil() as u64; let expected_lock_time = maker_data.started_at().checked_add(customized_lock_duration); @@ -1217,12 +1235,26 @@ impl TakerSwap { taker_coin_swap_contract_bytes, ); + // Emulate old node (not supporting version in negotiation msg) + // (Run swap tests with "test-use-old-taker" feature to emulate old taker, sending non-versioned message) + #[cfg(feature = "test-use-old-taker")] let taker_data = SwapMsg::NegotiationReply(my_negotiation_data); + + // Normal path + #[cfg(not(feature = "test-use-old-taker"))] + let taker_data = match remote_version >= SWAP_PROTOCOL_VERSION { + true => SwapMsg::NegotiationReplyVersioned(NegotiationDataMsgVersion { + version: SWAP_PROTOCOL_VERSION, + msg: my_negotiation_data, + }), + false => SwapMsg::NegotiationReply(my_negotiation_data), // remote node is old + }; + debug!("Sending taker negotiation data {:?}", taker_data); let send_abort_handle = broadcast_swap_msg_every( self.ctx.clone(), swap_topic(&self.uuid), - taker_data, + vec![taker_data], NEGOTIATE_TIMEOUT_SEC as f64 / 6., self.p2p_privkey, ); @@ -1250,6 +1282,7 @@ impl TakerSwap { Ok((Some(TakerSwapCommand::SendTakerFee), vec![TakerSwapEvent::Negotiated( MakerNegotiationData { + maker_version: Some(remote_version), maker_payment_locktime: maker_data.payment_locktime(), // using default to avoid misuse of this field // maker_coin_htlc_pubkey and taker_coin_htlc_pubkey must be used instead @@ -1316,7 +1349,7 @@ impl TakerSwap { let abort_send_handle = broadcast_swap_msg_every( self.ctx.clone(), swap_topic(&self.uuid), - msg, + vec![msg], MAKER_PAYMENT_WAIT_TIMEOUT_SEC as f64 / 6., self.p2p_privkey, ); @@ -1541,6 +1574,7 @@ impl TakerSwap { Err(_) => self.r().data.taker_payment_lock, }; let payment_fut = self.taker_coin.send_taker_payment(SendPaymentArgs { + other_version: self.r().data.maker_version.unwrap_or(LEGACY_PROTOCOL_VERSION), // normally maker_version should unwrap okay time_lock_duration: self.r().data.lock_duration, time_lock, other_pubkey: &*self.r().other_taker_coin_htlc_pub, @@ -1680,7 +1714,7 @@ impl TakerSwap { let send_abort_handle = broadcast_swap_msg_every( self.ctx.clone(), swap_topic(&self.uuid), - msg, + vec![msg], BROADCAST_MSG_INTERVAL_SEC, self.p2p_privkey, ); @@ -2673,6 +2707,11 @@ pub fn max_taker_vol_from_available( Ok(max_vol) } +/// Determine version from negotiation data if saved swap data does not store version +/// (if the swap started before the upgrade to versioned negotiation message) +/// In any case it is very undesirable to upgrade mm2 when any swaps are active +fn fix_maker_version(negotiation_data: &MakerNegotiationData) -> u16 { negotiation_data.maker_version.unwrap_or(0) } + #[cfg(all(test, not(target_arch = "wasm32")))] mod taker_swap_tests { use super::*; diff --git a/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs b/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs index 330dec30de..86864a420b 100644 --- a/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs +++ b/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs @@ -11,7 +11,7 @@ use coins::utxo::{GetUtxoListOps, UtxoCommonOps}; use coins::TxFeeDetails; use coins::{ConfirmPaymentInput, FoundSwapTxSpend, MarketCoinOps, MmCoin, RefundPaymentArgs, SearchForSwapTxSpendInput, SendPaymentArgs, SpendPaymentArgs, SwapOps, SwapTxTypeWithSecretHash, - TransactionEnum, WithdrawRequest}; + TransactionEnum, WithdrawRequest, SWAP_PROTOCOL_VERSION}; use common::{block_on, executor::Timer, get_utc_timestamp, now_sec, wait_until_sec}; use crypto::privkey::key_pair_from_seed; use crypto::{CryptoCtx, DerivationPath, KeyPairPolicy}; @@ -40,6 +40,7 @@ fn test_search_for_swap_tx_spend_native_was_refunded_taker() { let time_lock = now_sec() - 3600; let taker_payment_args = SendPaymentArgs { + other_version: SWAP_PROTOCOL_VERSION, time_lock_duration: 0, time_lock, other_pubkey: my_public_key, @@ -127,6 +128,7 @@ fn test_search_for_swap_tx_spend_native_was_refunded_maker() { let time_lock = now_sec() - 3600; let maker_payment_args = SendPaymentArgs { + other_version: SWAP_PROTOCOL_VERSION, time_lock_duration: 0, time_lock, other_pubkey: my_public_key, @@ -196,6 +198,7 @@ fn test_search_for_taker_swap_tx_spend_native_was_spent_by_maker() { let secret_hash = dhash160(&secret); let time_lock = now_sec() - 3600; let taker_payment_args = SendPaymentArgs { + other_version: SWAP_PROTOCOL_VERSION, time_lock_duration: 0, time_lock, other_pubkey: my_pubkey, @@ -264,6 +267,7 @@ fn test_search_for_maker_swap_tx_spend_native_was_spent_by_taker() { let time_lock = now_sec() - 3600; let secret_hash = dhash160(&secret); let maker_payment_args = SendPaymentArgs { + other_version: SWAP_PROTOCOL_VERSION, time_lock_duration: 0, time_lock, other_pubkey: my_pubkey, @@ -335,6 +339,7 @@ fn test_one_hundred_maker_payments_in_a_row_native() { let mut sent_tx = vec![]; for i in 0..100 { let maker_payment_args = SendPaymentArgs { + other_version: SWAP_PROTOCOL_VERSION, time_lock_duration: 0, time_lock: time_lock + i, other_pubkey: my_pubkey, 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 44032de384..98e9b2b9d0 100644 --- a/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs @@ -14,7 +14,7 @@ use coins::{lp_coinfind, CoinProtocol, CoinWithDerivationMethod, CoinsContext, C MmCoinStruct, NftSwapInfo, ParseCoinAssocTypes, ParseNftAssocTypes, PrivKeyBuildPolicy, RefundNftMakerPaymentArgs, RefundPaymentArgs, SearchForSwapTxSpendInput, SendNftMakerPaymentArgs, SendPaymentArgs, SpendNftMakerPaymentArgs, SpendPaymentArgs, SwapOps, SwapTxFeePolicy, - SwapTxTypeWithSecretHash, ToBytes, Transaction, ValidateNftMakerPaymentArgs}; + SwapTxTypeWithSecretHash, ToBytes, Transaction, ValidateNftMakerPaymentArgs, SWAP_PROTOCOL_VERSION}; use common::{block_on, now_sec}; use crypto::Secp256k1Secret; use ethcore_transaction::Action; @@ -505,6 +505,7 @@ fn send_safe_transfer_from( data, U256::from(ETH_MAX_TRADE_GAS), None, + true, ) .compat(), ) @@ -574,6 +575,7 @@ fn send_and_refund_eth_maker_payment_impl(swap_txfee_policy: SwapTxFeePolicy) { ]; let send_payment_args = SendPaymentArgs { + other_version: SWAP_PROTOCOL_VERSION, time_lock_duration: 100, time_lock, other_pubkey, @@ -660,6 +662,7 @@ fn send_and_spend_eth_maker_payment_impl(swap_txfee_policy: SwapTxFeePolicy) { let secret_hash = secret_hash_owned.as_slice(); let send_payment_args = SendPaymentArgs { + other_version: SWAP_PROTOCOL_VERSION, time_lock_duration: 1000, time_lock, other_pubkey: &taker_pubkey, @@ -743,6 +746,7 @@ fn send_and_refund_erc20_maker_payment_impl(swap_txfee_policy: SwapTxFeePolicy) let secret_hash = &[1; 20]; let send_payment_args = SendPaymentArgs { + other_version: SWAP_PROTOCOL_VERSION, time_lock_duration: 100, time_lock, other_pubkey, @@ -832,6 +836,7 @@ fn send_and_spend_erc20_maker_payment_impl(swap_txfee_policy: SwapTxFeePolicy) { let secret_hash = secret_hash_owned.as_slice(); let send_payment_args = SendPaymentArgs { + other_version: SWAP_PROTOCOL_VERSION, time_lock_duration: 1000, time_lock, other_pubkey: &taker_pubkey, diff --git a/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs b/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs index e66a3a5852..34f73a732a 100644 --- a/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs @@ -9,7 +9,7 @@ use coins::utxo::{UtxoActivationParams, UtxoCommonOps}; use coins::{CheckIfMyPaymentSentArgs, ConfirmPaymentInput, DexFee, FeeApproxStage, FoundSwapTxSpend, MarketCoinOps, MmCoin, RefundPaymentArgs, SearchForSwapTxSpendInput, SendPaymentArgs, SpendPaymentArgs, SwapOps, SwapTxTypeWithSecretHash, TradePreimageValue, TransactionEnum, ValidateFeeArgs, ValidatePaymentInput, - WaitForHTLCTxSpendArgs}; + WaitForHTLCTxSpendArgs, SWAP_PROTOCOL_VERSION}; use common::log::debug; use common::{temp_dir, DEX_FEE_ADDR_RAW_PUBKEY}; use crypto::Secp256k1Secret; @@ -183,6 +183,7 @@ fn test_taker_spends_maker_payment() { let secret_hash = dhash160(secret).to_vec(); let amount = BigDecimal::try_from(0.2).unwrap(); let maker_payment_args = SendPaymentArgs { + other_version: SWAP_PROTOCOL_VERSION, time_lock_duration: 0, time_lock: timelock, other_pubkey: &taker_pub, @@ -285,6 +286,7 @@ fn test_maker_spends_taker_payment() { let secret_hash = dhash160(secret).to_vec(); let amount = BigDecimal::try_from(0.2).unwrap(); let taker_payment_args = SendPaymentArgs { + other_version: SWAP_PROTOCOL_VERSION, time_lock_duration: 0, time_lock: timelock, other_pubkey: &maker_pub, @@ -376,6 +378,7 @@ fn test_maker_refunds_payment() { let secret_hash = &[1; 20]; let amount = BigDecimal::from_str("0.2").unwrap(); let maker_payment = SendPaymentArgs { + other_version: SWAP_PROTOCOL_VERSION, time_lock_duration: 0, time_lock: timelock, other_pubkey: &taker_pub, @@ -448,6 +451,7 @@ fn test_taker_refunds_payment() { let secret_hash = &[1; 20]; let amount = BigDecimal::from_str("0.2").unwrap(); let taker_payment_args = SendPaymentArgs { + other_version: SWAP_PROTOCOL_VERSION, time_lock_duration: 0, time_lock: timelock, other_pubkey: &maker_pub, @@ -517,6 +521,7 @@ fn test_check_if_my_payment_sent() { let secret_hash = &[1; 20]; let amount = BigDecimal::from_str("0.2").unwrap(); let maker_payment_args = SendPaymentArgs { + other_version: SWAP_PROTOCOL_VERSION, time_lock_duration: 0, time_lock: timelock, other_pubkey: &taker_pub, @@ -574,6 +579,7 @@ fn test_search_for_swap_tx_spend_taker_spent() { let secret_hash = dhash160(secret); let amount = BigDecimal::try_from(0.2).unwrap(); let maker_payment_args = SendPaymentArgs { + other_version: SWAP_PROTOCOL_VERSION, time_lock_duration: 0, time_lock: timelock, other_pubkey: taker_pub, @@ -653,6 +659,7 @@ fn test_search_for_swap_tx_spend_maker_refunded() { let secret_hash = &*dhash160(secret); let amount = BigDecimal::try_from(0.2).unwrap(); let maker_payment_args = SendPaymentArgs { + other_version: SWAP_PROTOCOL_VERSION, time_lock_duration: 0, time_lock: timelock, other_pubkey: &taker_pub, @@ -733,6 +740,7 @@ fn test_search_for_swap_tx_spend_not_spent() { let secret_hash = &*dhash160(secret); let amount = BigDecimal::try_from(0.2).unwrap(); let maker_payment_args = SendPaymentArgs { + other_version: SWAP_PROTOCOL_VERSION, time_lock_duration: 0, time_lock: timelock, other_pubkey: &taker_pub, @@ -790,6 +798,7 @@ fn test_wait_for_tx_spend() { let secret_hash = dhash160(secret); let amount = BigDecimal::try_from(0.2).unwrap(); let maker_payment_args = SendPaymentArgs { + other_version: SWAP_PROTOCOL_VERSION, time_lock_duration: 0, time_lock: timelock, other_pubkey: taker_pub, @@ -1108,6 +1117,7 @@ fn test_get_max_taker_vol_and_trade_with_dynamic_trade_fee(coin: QtumCoin, priv_ .wait() .expect("!send_taker_fee"); let taker_payment_args = SendPaymentArgs { + other_version: SWAP_PROTOCOL_VERSION, time_lock_duration: 0, time_lock: timelock, other_pubkey: &DEX_FEE_ADDR_RAW_PUBKEY, @@ -1508,6 +1518,7 @@ fn test_search_for_segwit_swap_tx_spend_native_was_refunded_maker() { let time_lock = now_sec() - 3600; let maker_payment = SendPaymentArgs { + other_version: SWAP_PROTOCOL_VERSION, time_lock_duration: 0, time_lock, other_pubkey: my_public_key, @@ -1576,6 +1587,7 @@ fn test_search_for_segwit_swap_tx_spend_native_was_refunded_taker() { let time_lock = now_sec() - 3600; let taker_payment = SendPaymentArgs { + other_version: SWAP_PROTOCOL_VERSION, time_lock_duration: 0, time_lock, other_pubkey: my_public_key, diff --git a/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs b/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs index 22c17c1444..052be5907d 100644 --- a/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs @@ -12,7 +12,7 @@ use coins::{ConfirmPaymentInput, FoundSwapTxSpend, MarketCoinOps, MmCoin, MmCoin WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, EARLY_CONFIRMATION_ERR_LOG, INVALID_CONTRACT_ADDRESS_ERR_LOG, INVALID_PAYMENT_STATE_ERR_LOG, INVALID_RECEIVER_ERR_LOG, INVALID_REFUND_TX_ERR_LOG, INVALID_SCRIPT_ERR_LOG, INVALID_SENDER_ERR_LOG, INVALID_SWAP_ID_ERR_LOG, - OLD_TRANSACTION_ERR_LOG}; + OLD_TRANSACTION_ERR_LOG, SWAP_PROTOCOL_VERSION}; use common::{block_on, now_sec, wait_until_sec, DEX_FEE_ADDR_RAW_PUBKEY}; use crypto::privkey::{key_pair_from_secret, key_pair_from_seed}; use futures01::Future; @@ -1554,6 +1554,7 @@ fn test_watcher_validate_taker_payment_utxo() { let taker_payment = taker_coin .send_taker_payment(SendPaymentArgs { + other_version: SWAP_PROTOCOL_VERSION, time_lock_duration, time_lock, other_pubkey: maker_pubkey, @@ -1658,6 +1659,7 @@ fn test_watcher_validate_taker_payment_utxo() { let taker_payment_wrong_secret = taker_coin .send_taker_payment(SendPaymentArgs { + other_version: SWAP_PROTOCOL_VERSION, time_lock_duration, time_lock, other_pubkey: maker_pubkey, @@ -1779,6 +1781,7 @@ fn test_watcher_validate_taker_payment_eth() { let taker_payment = taker_coin .send_taker_payment(SendPaymentArgs { + other_version: SWAP_PROTOCOL_VERSION, time_lock_duration, time_lock, other_pubkey: maker_pub, @@ -1845,6 +1848,7 @@ fn test_watcher_validate_taker_payment_eth() { let taker_payment_wrong_contract = taker_coin .send_taker_payment(SendPaymentArgs { + other_version: SWAP_PROTOCOL_VERSION, time_lock_duration, time_lock, other_pubkey: maker_pub, @@ -1915,6 +1919,7 @@ fn test_watcher_validate_taker_payment_eth() { let taker_payment_wrong_secret = taker_coin .send_taker_payment(SendPaymentArgs { + other_version: SWAP_PROTOCOL_VERSION, time_lock_duration, time_lock, other_pubkey: maker_pub, @@ -2025,6 +2030,7 @@ fn test_watcher_validate_taker_payment_erc20() { let taker_payment = taker_coin .send_taker_payment(SendPaymentArgs { + other_version: SWAP_PROTOCOL_VERSION, time_lock_duration, time_lock, other_pubkey: maker_pub, @@ -2091,6 +2097,7 @@ fn test_watcher_validate_taker_payment_erc20() { let taker_payment_wrong_contract = taker_coin .send_taker_payment(SendPaymentArgs { + other_version: SWAP_PROTOCOL_VERSION, time_lock_duration, time_lock, other_pubkey: maker_pub, @@ -2161,6 +2168,7 @@ fn test_watcher_validate_taker_payment_erc20() { let taker_payment_wrong_secret = taker_coin .send_taker_payment(SendPaymentArgs { + other_version: SWAP_PROTOCOL_VERSION, time_lock_duration, time_lock, other_pubkey: maker_pub, @@ -2252,6 +2260,7 @@ fn test_taker_validates_taker_payment_refund_utxo() { let taker_payment = taker_coin .send_taker_payment(SendPaymentArgs { + other_version: SWAP_PROTOCOL_VERSION, time_lock_duration, time_lock, other_pubkey: maker_pubkey, @@ -2349,6 +2358,7 @@ fn test_taker_validates_taker_payment_refund_eth() { let taker_payment = taker_coin .send_taker_payment(SendPaymentArgs { + other_version: SWAP_PROTOCOL_VERSION, time_lock_duration, time_lock, other_pubkey: maker_pub, @@ -2674,6 +2684,7 @@ fn test_taker_validates_taker_payment_refund_erc20() { let taker_payment = taker_coin .send_taker_payment(SendPaymentArgs { + other_version: SWAP_PROTOCOL_VERSION, time_lock_duration, time_lock, other_pubkey: maker_pub, @@ -2785,6 +2796,7 @@ fn test_taker_validates_maker_payment_spend_utxo() { let maker_payment = maker_coin .send_maker_payment(SendPaymentArgs { + other_version: SWAP_PROTOCOL_VERSION, time_lock_duration, time_lock, other_pubkey: taker_pubkey, @@ -2879,6 +2891,7 @@ fn test_taker_validates_maker_payment_spend_eth() { let maker_payment = maker_coin .send_maker_payment(SendPaymentArgs { + other_version: SWAP_PROTOCOL_VERSION, time_lock_duration, time_lock, other_pubkey: taker_pub, @@ -3208,6 +3221,7 @@ fn test_taker_validates_maker_payment_spend_erc20() { let maker_payment = maker_coin .send_maker_payment(SendPaymentArgs { + other_version: SWAP_PROTOCOL_VERSION, time_lock_duration, time_lock, other_pubkey: taker_pub, @@ -3318,6 +3332,7 @@ fn test_send_taker_payment_refund_preimage_utxo() { let time_lock = now_sec() - 3600; let taker_payment_args = SendPaymentArgs { + other_version: SWAP_PROTOCOL_VERSION, time_lock_duration: 0, time_lock, other_pubkey: my_public_key, From d7c80c3d10b3c32879b3d2618d84d5ac88f69087 Mon Sep 17 00:00:00 2001 From: dimxy Date: Sat, 17 Aug 2024 23:54:37 +0500 Subject: [PATCH 11/19] fix negotiation version as dedicate p2p topic (prev solution to extend swap msg enum was not compatible with old nodes) --- mm2src/mm2_main/src/lp_network.rs | 14 ++- mm2src/mm2_main/src/lp_swap.rs | 117 +++++++++++++++------- mm2src/mm2_main/src/lp_swap/maker_swap.rs | 15 ++- mm2src/mm2_main/src/lp_swap/taker_swap.rs | 9 +- 4 files changed, 101 insertions(+), 54 deletions(-) diff --git a/mm2src/mm2_main/src/lp_network.rs b/mm2src/mm2_main/src/lp_network.rs index eb84f390d1..e3e2eab578 100644 --- a/mm2src/mm2_main/src/lp_network.rs +++ b/mm2src/mm2_main/src/lp_network.rs @@ -39,6 +39,7 @@ use serde::de; use std::net::ToSocketAddrs; use crate::lp_ordermatch; +use crate::lp_swap::{SwapMsg, SwapMsgExt}; use crate::{lp_stats, lp_swap}; pub type P2PRequestResult = Result>; @@ -169,7 +170,18 @@ async fn process_p2p_message( }, Some(lp_swap::SWAP_PREFIX) => { if let Err(e) = - lp_swap::process_swap_msg(ctx.clone(), split.next().unwrap_or_default(), &message.data).await + lp_swap::process_swap_msg::(ctx.clone(), split.next().unwrap_or_default(), &message.data).await + { + log::error!("{}", e); + return; + } + + to_propagate = true; + }, + Some(lp_swap::SWAP_PREFIX_EXT) => { + if let Err(e) = + lp_swap::process_swap_msg::(ctx.clone(), split.next().unwrap_or_default(), &message.data) + .await { log::error!("{}", e); return; diff --git a/mm2src/mm2_main/src/lp_swap.rs b/mm2src/mm2_main/src/lp_swap.rs index 23cf1fda6f..6a8372458f 100644 --- a/mm2src/mm2_main/src/lp_swap.rs +++ b/mm2src/mm2_main/src/lp_swap.rs @@ -145,6 +145,7 @@ pub use taker_swap::{calc_max_taker_vol, check_balance_for_taker_swap, max_taker pub use trade_preimage::trade_preimage_rpc; pub const SWAP_PREFIX: TopicPrefix = "swap"; +pub const SWAP_PREFIX_EXT: TopicPrefix = "swapv1ext"; pub const SWAP_V2_PREFIX: TopicPrefix = "swapv2"; pub const SWAP_FINISHED_LOG: &str = "Swap finished: "; pub const TX_HELPER_PREFIX: TopicPrefix = "txhlp"; @@ -179,6 +180,13 @@ pub enum SwapMsg { TakerPayment(Vec), } +// Extension to SwapMsg with version added to negotiation exchange +#[derive(Clone, Debug, Eq, Deserialize, PartialEq, Serialize)] +pub enum SwapMsgExt { + NegotiationVersioned(NegotiationDataMsgVersion), + NegotiationReplyVersioned(NegotiationDataMsgVersion), +} + #[derive(Debug, Default)] pub struct SwapMsgStore { negotiation: Option, @@ -199,6 +207,11 @@ impl SwapMsgStore { } } +/// Process swap v1 or v1 extension messages +pub trait ProcessSwapMsg { + fn swap_msg_to_store(self, msg_store: &mut SwapMsgStore); +} + /// Storage for P2P messages, which are exchanged during SwapV2 protocol execution. #[derive(Debug)] pub struct SwapV2MsgStore { @@ -262,11 +275,10 @@ pub fn p2p_private_and_peer_id_to_broadcast(ctx: &MmArc, p2p_privkey: Option<&Ke /// Spawns the loop that broadcasts group of messages every `interval` seconds returning the AbortOnDropHandle /// to stop it -/// TODO: several messages allowed to try first new version of requests. We can deprecate this when all nodes upgrade +/// TODO: now several messages are allowed to try first new version of requests. We can deprecate this when all nodes upgrade pub fn broadcast_swap_msg_every( ctx: MmArc, - topic: String, - msgs: Vec, + msgs: Vec<(String, T)>, // topic and message interval_sec: f64, p2p_privkey: Option, ) -> AbortOnDropHandle { @@ -274,7 +286,7 @@ pub fn broadcast_swap_msg_every( loop { let msgs_cloned = msgs.clone(); for msg in msgs_cloned { - broadcast_swap_message(&ctx, topic.clone(), msg, &p2p_privkey); + broadcast_swap_message(&ctx, msg.0, msg.1, &p2p_privkey); Timer::sleep(5.0).await; // this a delay between tries of new and old requests (to ensure the receiver processes the new message first) } Timer::sleep(interval_sec).await; @@ -330,11 +342,70 @@ pub fn broadcast_p2p_tx_msg(ctx: &MmArc, topic: String, msg: &TransactionEnum, p }; broadcast_p2p_msg(ctx, topic, encoded_msg, from); } +impl ProcessSwapMsg for SwapMsg { + fn swap_msg_to_store(self, msg_store: &mut SwapMsgStore) { + match self { + // build NegotiationDataMsgVersion from legacy NegotiationDataMsg with default version: + SwapMsg::Negotiation(data) => { + msg_store.negotiation = Some(NegotiationDataMsgVersion { + version: LEGACY_PROTOCOL_VERSION, + msg: data, + }) + }, + SwapMsg::NegotiationVersioned(data) => { + if cfg!(not(feature = "test-use-old-taker")) { + // ignore versioned msg for old taker emulation + msg_store.negotiation = Some(data); + } + }, + // build NegotiationDataMsgVersion from legacy NegotiationDataMsg with default version: + SwapMsg::NegotiationReply(data) => { + msg_store.negotiation_reply = Some(NegotiationDataMsgVersion { + version: LEGACY_PROTOCOL_VERSION, + msg: data, + }) + }, + SwapMsg::NegotiationReplyVersioned(data) => { + if cfg!(not(feature = "test-use-old-maker")) { + // ignore versioned msg for old maker emulation + msg_store.negotiation_reply = Some(data); + } + }, + SwapMsg::Negotiated(negotiated) => msg_store.negotiated = Some(negotiated), + SwapMsg::TakerFee(data) => msg_store.taker_fee = Some(data), + SwapMsg::MakerPayment(data) => msg_store.maker_payment = Some(data), + SwapMsg::TakerPayment(taker_payment) => msg_store.taker_payment = Some(taker_payment), + } + } +} -pub async fn process_swap_msg(ctx: MmArc, topic: &str, msg: &[u8]) -> P2PRequestResult<()> { +impl ProcessSwapMsg for SwapMsgExt { + fn swap_msg_to_store(self, msg_store: &mut SwapMsgStore) { + match self { + SwapMsgExt::NegotiationVersioned(data) => { + if cfg!(not(feature = "test-use-old-taker")) { + // ignore versioned msg for old taker emulation + msg_store.negotiation = Some(data); + } + }, + SwapMsgExt::NegotiationReplyVersioned(data) => { + if cfg!(not(feature = "test-use-old-maker")) { + // ignore versioned msg for old maker emulation + msg_store.negotiation_reply = Some(data); + } + }, + } + } +} + +pub async fn process_swap_msg<'life, SwapMsgT: serde::Deserialize<'life> + ProcessSwapMsg + std::fmt::Debug>( + ctx: MmArc, + topic: &str, + msg: &'life [u8], +) -> P2PRequestResult<()> { let uuid = Uuid::from_str(topic).map_to_mm(|e| P2PRequestError::DecodeError(e.to_string()))?; - let msg = match decode_signed::(msg) { + let msg = match decode_signed::(msg) { Ok(m) => m, Err(swap_msg_err) => { #[cfg(not(target_arch = "wasm32"))] @@ -368,38 +439,7 @@ pub async fn process_swap_msg(ctx: MmArc, topic: &str, msg: &[u8]) -> P2PRequest let mut msgs = swap_ctx.swap_msgs.lock().unwrap(); if let Some(msg_store) = msgs.get_mut(&uuid) { if msg_store.accept_only_from.bytes == msg.2.unprefixed() { - match msg.0 { - // build NegotiationDataMsgVersion from legacy NegotiationDataMsg with default version: - SwapMsg::Negotiation(data) => { - msg_store.negotiation = Some(NegotiationDataMsgVersion { - version: LEGACY_PROTOCOL_VERSION, - msg: data, - }) - }, - SwapMsg::NegotiationVersioned(data) => { - if cfg!(not(feature = "test-use-old-taker")) { - // ignore versioned msg for old taker emulation - msg_store.negotiation = Some(data); - } - }, - // build NegotiationDataMsgVersion from legacy NegotiationDataMsg with default version: - SwapMsg::NegotiationReply(data) => { - msg_store.negotiation_reply = Some(NegotiationDataMsgVersion { - version: LEGACY_PROTOCOL_VERSION, - msg: data, - }) - }, - SwapMsg::NegotiationReplyVersioned(data) => { - if cfg!(not(feature = "test-use-old-maker")) { - // ignore versioned msg for old maker emulation - msg_store.negotiation_reply = Some(data); - } - }, - SwapMsg::Negotiated(negotiated) => msg_store.negotiated = Some(negotiated), - SwapMsg::TakerFee(data) => msg_store.taker_fee = Some(data), - SwapMsg::MakerPayment(data) => msg_store.maker_payment = Some(data), - SwapMsg::TakerPayment(taker_payment) => msg_store.taker_payment = Some(taker_payment), - } + msg.0.swap_msg_to_store(msg_store); } else { warn!("Received message from unexpected sender for swap {}", uuid); } @@ -409,6 +449,7 @@ pub async fn process_swap_msg(ctx: MmArc, topic: &str, msg: &[u8]) -> P2PRequest } pub fn swap_topic(uuid: &Uuid) -> String { pub_sub_topic(SWAP_PREFIX, &uuid.to_string()) } +pub fn swap_ext_topic(uuid: &Uuid) -> String { pub_sub_topic(SWAP_PREFIX_EXT, &uuid.to_string()) } /// Formats and returns a topic format for `txhlp`. /// diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap.rs b/mm2src/mm2_main/src/lp_swap/maker_swap.rs index 785be634d5..e46f5cd494 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap.rs @@ -14,9 +14,9 @@ use crate::lp_dispatcher::{DispatcherContext, LpEvents}; use crate::lp_network::subscribe_to_topic; use crate::lp_ordermatch::MakerOrderBuilder; use crate::lp_swap::swap_v2_common::mark_swap_as_finished; -#[cfg(not(feature = "test-use-old-maker"))] -use crate::lp_swap::NegotiationDataMsgVersion; use crate::lp_swap::{broadcast_swap_message, taker_payment_spend_duration, MAX_STARTED_AT_DIFF}; +#[cfg(not(feature = "test-use-old-maker"))] +use crate::lp_swap::{swap_ext_topic, NegotiationDataMsgVersion}; use coins::lp_price::fetch_swap_coins_price; #[cfg(not(feature = "test-use-old-maker"))] use coins::SWAP_PROTOCOL_VERSION; @@ -603,11 +603,11 @@ impl MakerSwap { version: SWAP_PROTOCOL_VERSION, msg: negotiation_data.clone(), }); - msgs.push(maker_versioned_negotiation_msg); + msgs.push((swap_ext_topic(&self.uuid), maker_versioned_negotiation_msg)); } let maker_old_negotiation_msg = SwapMsg::Negotiation(negotiation_data); - msgs.push(maker_old_negotiation_msg); + msgs.push((swap_topic(&self.uuid), maker_old_negotiation_msg)); const NEGOTIATION_TIMEOUT_SEC: u64 = 90; @@ -616,7 +616,6 @@ impl MakerSwap { // When all nodes upgrade to NegotiationDataMsgVersion we won't need to send both messages and will use 'version' field in NegotiationDataMsgVersion let send_abort_handle = broadcast_swap_msg_every( self.ctx.clone(), - swap_topic(&self.uuid), msgs, NEGOTIATION_TIMEOUT_SEC as f64 / 6., self.p2p_privkey, @@ -731,8 +730,7 @@ impl MakerSwap { let negotiated = SwapMsg::Negotiated(true); let send_abort_handle = broadcast_swap_msg_every( self.ctx.clone(), - swap_topic(&self.uuid), - vec![negotiated], + vec![(swap_topic(&self.uuid), negotiated)], TAKER_FEE_RECV_TIMEOUT_SEC as f64 / 6., self.p2p_privkey, ); @@ -943,8 +941,7 @@ impl MakerSwap { let msg = SwapMsg::MakerPayment(payment_data_msg); let abort_send_handle = broadcast_swap_msg_every( self.ctx.clone(), - swap_topic(&self.uuid), - vec![msg], + vec![(swap_topic(&self.uuid), msg)], PAYMENT_MSG_INTERVAL_SEC, self.p2p_privkey, ); diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap.rs b/mm2src/mm2_main/src/lp_swap/taker_swap.rs index aaddd7afc6..eeb9ac4d93 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap.rs @@ -1253,8 +1253,7 @@ impl TakerSwap { debug!("Sending taker negotiation data {:?}", taker_data); let send_abort_handle = broadcast_swap_msg_every( self.ctx.clone(), - swap_topic(&self.uuid), - vec![taker_data], + vec![(swap_topic(&self.uuid), taker_data)], NEGOTIATE_TIMEOUT_SEC as f64 / 6., self.p2p_privkey, ); @@ -1348,8 +1347,7 @@ impl TakerSwap { let msg = SwapMsg::TakerFee(payment_data_msg); let abort_send_handle = broadcast_swap_msg_every( self.ctx.clone(), - swap_topic(&self.uuid), - vec![msg], + vec![(swap_topic(&self.uuid), msg)], MAKER_PAYMENT_WAIT_TIMEOUT_SEC as f64 / 6., self.p2p_privkey, ); @@ -1713,8 +1711,7 @@ impl TakerSwap { let msg = SwapMsg::TakerPayment(tx_hex); let send_abort_handle = broadcast_swap_msg_every( self.ctx.clone(), - swap_topic(&self.uuid), - vec![msg], + vec![(swap_topic(&self.uuid), msg)], BROADCAST_MSG_INTERVAL_SEC, self.p2p_privkey, ); From 91078a6fcda6046a5a8fee88366f21d8a59ad3fa Mon Sep 17 00:00:00 2001 From: dimxy Date: Sun, 18 Aug 2024 13:00:25 +0500 Subject: [PATCH 12/19] fix SwapMsg back to make it compatible with old nodes, add SwapExt and "swapext" topic to provide versioning to negotiation --- mm2src/mm2_main/src/lp_swap.rs | 46 +++++++++++++---------- mm2src/mm2_main/src/lp_swap/maker_swap.rs | 16 +++++--- mm2src/mm2_main/src/lp_swap/taker_swap.rs | 32 ++++++++++------ 3 files changed, 59 insertions(+), 35 deletions(-) diff --git a/mm2src/mm2_main/src/lp_swap.rs b/mm2src/mm2_main/src/lp_swap.rs index 6a8372458f..f28c41571a 100644 --- a/mm2src/mm2_main/src/lp_swap.rs +++ b/mm2src/mm2_main/src/lp_swap.rs @@ -145,7 +145,7 @@ pub use taker_swap::{calc_max_taker_vol, check_balance_for_taker_swap, max_taker pub use trade_preimage::trade_preimage_rpc; pub const SWAP_PREFIX: TopicPrefix = "swap"; -pub const SWAP_PREFIX_EXT: TopicPrefix = "swapv1ext"; +pub const SWAP_PREFIX_EXT: TopicPrefix = "swapext"; pub const SWAP_V2_PREFIX: TopicPrefix = "swapv2"; pub const SWAP_FINISHED_LOG: &str = "Swap finished: "; pub const TX_HELPER_PREFIX: TopicPrefix = "txhlp"; @@ -171,9 +171,7 @@ cfg_wasm32! { #[derive(Clone, Debug, Eq, Deserialize, PartialEq, Serialize)] pub enum SwapMsg { Negotiation(NegotiationDataMsg), - NegotiationVersioned(NegotiationDataMsgVersion), NegotiationReply(NegotiationDataMsg), - NegotiationReplyVersioned(NegotiationDataMsgVersion), Negotiated(bool), TakerFee(SwapTxDataMsg), MakerPayment(SwapTxDataMsg), @@ -187,6 +185,26 @@ pub enum SwapMsgExt { NegotiationReplyVersioned(NegotiationDataMsgVersion), } +/// Utility wrapper to allow manage both SwapMsg and SwapMsgExt as one entity +#[derive(Clone, Debug)] +pub enum SwapMsgWrapper { + Legacy(SwapMsg), + Ext(SwapMsgExt), +} + +// Do not use deived serilizer to provider compatibility with old nodes supporting only SwapMsg +impl Serialize for SwapMsgWrapper { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + match self { + SwapMsgWrapper::Legacy(swap_msg) => swap_msg.serialize(serializer), + SwapMsgWrapper::Ext(swap_msg_ext) => swap_msg_ext.serialize(serializer), + } + } +} + #[derive(Debug, Default)] pub struct SwapMsgStore { negotiation: Option, @@ -352,12 +370,6 @@ impl ProcessSwapMsg for SwapMsg { msg: data, }) }, - SwapMsg::NegotiationVersioned(data) => { - if cfg!(not(feature = "test-use-old-taker")) { - // ignore versioned msg for old taker emulation - msg_store.negotiation = Some(data); - } - }, // build NegotiationDataMsgVersion from legacy NegotiationDataMsg with default version: SwapMsg::NegotiationReply(data) => { msg_store.negotiation_reply = Some(NegotiationDataMsgVersion { @@ -365,12 +377,6 @@ impl ProcessSwapMsg for SwapMsg { msg: data, }) }, - SwapMsg::NegotiationReplyVersioned(data) => { - if cfg!(not(feature = "test-use-old-maker")) { - // ignore versioned msg for old maker emulation - msg_store.negotiation_reply = Some(data); - } - }, SwapMsg::Negotiated(negotiated) => msg_store.negotiated = Some(negotiated), SwapMsg::TakerFee(data) => msg_store.taker_fee = Some(data), SwapMsg::MakerPayment(data) => msg_store.maker_payment = Some(data), @@ -383,14 +389,16 @@ impl ProcessSwapMsg for SwapMsgExt { fn swap_msg_to_store(self, msg_store: &mut SwapMsgStore) { match self { SwapMsgExt::NegotiationVersioned(data) => { - if cfg!(not(feature = "test-use-old-taker")) { - // ignore versioned msg for old taker emulation + if cfg!(not(feature = "test-use-old-taker")) + // ignore to emulate old node in tests + { msg_store.negotiation = Some(data); } }, SwapMsgExt::NegotiationReplyVersioned(data) => { - if cfg!(not(feature = "test-use-old-maker")) { - // ignore versioned msg for old maker emulation + if cfg!(not(feature = "test-use-old-maker")) + // ignore to emulate old node in tests + { msg_store.negotiation_reply = Some(data); } }, diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap.rs b/mm2src/mm2_main/src/lp_swap/maker_swap.rs index e46f5cd494..cd63928821 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap.rs @@ -14,9 +14,9 @@ use crate::lp_dispatcher::{DispatcherContext, LpEvents}; use crate::lp_network::subscribe_to_topic; use crate::lp_ordermatch::MakerOrderBuilder; use crate::lp_swap::swap_v2_common::mark_swap_as_finished; -use crate::lp_swap::{broadcast_swap_message, taker_payment_spend_duration, MAX_STARTED_AT_DIFF}; +use crate::lp_swap::{broadcast_swap_message, taker_payment_spend_duration, SwapMsgWrapper, MAX_STARTED_AT_DIFF}; #[cfg(not(feature = "test-use-old-maker"))] -use crate::lp_swap::{swap_ext_topic, NegotiationDataMsgVersion}; +use crate::lp_swap::{swap_ext_topic, NegotiationDataMsgVersion, SwapMsgExt}; use coins::lp_price::fetch_swap_coins_price; #[cfg(not(feature = "test-use-old-maker"))] use coins::SWAP_PROTOCOL_VERSION; @@ -599,15 +599,21 @@ impl MakerSwap { // (Run swap tests with "test-use-old-maker" feature to emulate old maker, sending non-versioned message only) #[cfg(not(feature = "test-use-old-maker"))] { - let maker_versioned_negotiation_msg = SwapMsg::NegotiationVersioned(NegotiationDataMsgVersion { + let maker_versioned_negotiation_msg = SwapMsgExt::NegotiationVersioned(NegotiationDataMsgVersion { version: SWAP_PROTOCOL_VERSION, msg: negotiation_data.clone(), }); - msgs.push((swap_ext_topic(&self.uuid), maker_versioned_negotiation_msg)); + msgs.push(( + swap_ext_topic(&self.uuid), + SwapMsgWrapper::Ext(maker_versioned_negotiation_msg), + )); } let maker_old_negotiation_msg = SwapMsg::Negotiation(negotiation_data); - msgs.push((swap_topic(&self.uuid), maker_old_negotiation_msg)); + msgs.push(( + swap_topic(&self.uuid), + SwapMsgWrapper::Legacy(maker_old_negotiation_msg), + )); const NEGOTIATION_TIMEOUT_SEC: u64 = 90; diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap.rs b/mm2src/mm2_main/src/lp_swap/taker_swap.rs index eeb9ac4d93..26e8ea4a10 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap.rs @@ -14,10 +14,10 @@ use crate::lp_network::subscribe_to_topic; use crate::lp_ordermatch::TakerOrderBuilder; use crate::lp_swap::swap_v2_common::mark_swap_as_finished; use crate::lp_swap::taker_restart::get_command_based_on_maker_or_watcher_activity; -#[cfg(not(feature = "test-use-old-taker"))] -use crate::lp_swap::NegotiationDataMsgVersion; use crate::lp_swap::{broadcast_p2p_tx_msg, broadcast_swap_msg_every_delayed, tx_helper_topic, - wait_for_maker_payment_conf_duration, TakerSwapWatcherData, MAX_STARTED_AT_DIFF}; + wait_for_maker_payment_conf_duration, SwapMsgWrapper, TakerSwapWatcherData, MAX_STARTED_AT_DIFF}; +#[cfg(not(feature = "test-use-old-taker"))] +use crate::lp_swap::{swap_ext_topic, NegotiationDataMsgVersion, SwapMsgExt}; use coins::lp_price::fetch_swap_coins_price; #[cfg(not(feature = "test-use-old-taker"))] use coins::SWAP_PROTOCOL_VERSION; @@ -1238,25 +1238,35 @@ impl TakerSwap { // Emulate old node (not supporting version in negotiation msg) // (Run swap tests with "test-use-old-taker" feature to emulate old taker, sending non-versioned message) #[cfg(feature = "test-use-old-taker")] - let taker_data = SwapMsg::NegotiationReply(my_negotiation_data); + let (topic, taker_data) = ( + swap_topic(&self.uuid), + SwapMsgWrapper::Legacy(SwapMsg::NegotiationReply(my_negotiation_data)), + ); // Normal path #[cfg(not(feature = "test-use-old-taker"))] - let taker_data = match remote_version >= SWAP_PROTOCOL_VERSION { - true => SwapMsg::NegotiationReplyVersioned(NegotiationDataMsgVersion { - version: SWAP_PROTOCOL_VERSION, - msg: my_negotiation_data, - }), - false => SwapMsg::NegotiationReply(my_negotiation_data), // remote node is old + let (topic, taker_data) = match remote_version >= SWAP_PROTOCOL_VERSION { + true => ( + swap_ext_topic(&self.uuid), + SwapMsgWrapper::Ext(SwapMsgExt::NegotiationReplyVersioned(NegotiationDataMsgVersion { + version: SWAP_PROTOCOL_VERSION, + msg: my_negotiation_data, + })), + ), + false => ( + swap_topic(&self.uuid), + SwapMsgWrapper::Legacy(SwapMsg::NegotiationReply(my_negotiation_data)), // remote node is old + ), }; debug!("Sending taker negotiation data {:?}", taker_data); let send_abort_handle = broadcast_swap_msg_every( self.ctx.clone(), - vec![(swap_topic(&self.uuid), taker_data)], + vec![(topic, taker_data)], NEGOTIATE_TIMEOUT_SEC as f64 / 6., self.p2p_privkey, ); + let recv_fut = recv_swap_msg( self.ctx.clone(), |store| store.negotiated.take(), From fc78c9e20fbe1e9a25b979e66f075fa6e8d3cfd5 Mon Sep 17 00:00:00 2001 From: dimxy Date: Sun, 18 Aug 2024 13:32:08 +0500 Subject: [PATCH 13/19] fix comment --- mm2src/mm2_main/src/lp_swap.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm2src/mm2_main/src/lp_swap.rs b/mm2src/mm2_main/src/lp_swap.rs index f28c41571a..862580bf5a 100644 --- a/mm2src/mm2_main/src/lp_swap.rs +++ b/mm2src/mm2_main/src/lp_swap.rs @@ -192,7 +192,7 @@ pub enum SwapMsgWrapper { Ext(SwapMsgExt), } -// Do not use deived serilizer to provider compatibility with old nodes supporting only SwapMsg +// We use own Serializer instead of derived one to preserve compatibility with old nodes supporting only SwapMsg impl Serialize for SwapMsgWrapper { fn serialize(&self, serializer: S) -> Result where From 95fa60c54929b78ce257529e9ba3d7030831359e Mon Sep 17 00:00:00 2001 From: dimxy Date: Sun, 18 Aug 2024 18:14:53 +0500 Subject: [PATCH 14/19] fix subscribe to new topic --- mm2src/mm2_main/src/lp_swap/maker_swap.rs | 1 + mm2src/mm2_main/src/lp_swap/taker_swap.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap.rs b/mm2src/mm2_main/src/lp_swap/maker_swap.rs index cd63928821..012f2a8e10 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap.rs @@ -2130,6 +2130,7 @@ pub async fn run_maker_swap(swap: RunMakerSwapInput, ctx: MmArc) { let ctx = swap.ctx.clone(); subscribe_to_topic(&ctx, swap_topic(&swap.uuid)); + subscribe_to_topic(&ctx, swap_ext_topic(&swap.uuid)); let mut status = ctx.log.status_handle(); let uuid_str = swap.uuid.to_string(); let to_broadcast = !(swap.maker_coin.is_privacy() || swap.taker_coin.is_privacy()); diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap.rs b/mm2src/mm2_main/src/lp_swap/taker_swap.rs index 26e8ea4a10..e8ef39ce61 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap.rs @@ -443,6 +443,7 @@ pub async fn run_taker_swap(swap: RunTakerSwapInput, ctx: MmArc) { let ctx = swap.ctx.clone(); subscribe_to_topic(&ctx, swap_topic(&swap.uuid)); + subscribe_to_topic(&ctx, swap_ext_topic(&swap.uuid)); let mut status = ctx.log.status_handle(); let uuid = swap.uuid.to_string(); let to_broadcast = !(swap.maker_coin.is_privacy() || swap.taker_coin.is_privacy()); From 87058452b585d5ffdc526e2c1cff9979fb9af331 Mon Sep 17 00:00:00 2001 From: dimxy Date: Sun, 18 Aug 2024 18:27:34 +0500 Subject: [PATCH 15/19] fix clippy --- mm2src/mm2_main/src/lp_swap/maker_swap.rs | 5 +++-- mm2src/mm2_main/src/lp_swap/taker_swap.rs | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap.rs b/mm2src/mm2_main/src/lp_swap/maker_swap.rs index 012f2a8e10..063a1ccf10 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap.rs @@ -14,9 +14,10 @@ use crate::lp_dispatcher::{DispatcherContext, LpEvents}; use crate::lp_network::subscribe_to_topic; use crate::lp_ordermatch::MakerOrderBuilder; use crate::lp_swap::swap_v2_common::mark_swap_as_finished; -use crate::lp_swap::{broadcast_swap_message, taker_payment_spend_duration, SwapMsgWrapper, MAX_STARTED_AT_DIFF}; +use crate::lp_swap::{broadcast_swap_message, swap_ext_topic, taker_payment_spend_duration, SwapMsgWrapper, + MAX_STARTED_AT_DIFF}; #[cfg(not(feature = "test-use-old-maker"))] -use crate::lp_swap::{swap_ext_topic, NegotiationDataMsgVersion, SwapMsgExt}; +use crate::lp_swap::{NegotiationDataMsgVersion, SwapMsgExt}; use coins::lp_price::fetch_swap_coins_price; #[cfg(not(feature = "test-use-old-maker"))] use coins::SWAP_PROTOCOL_VERSION; diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap.rs b/mm2src/mm2_main/src/lp_swap/taker_swap.rs index e8ef39ce61..a7c3f3c82d 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap.rs @@ -14,10 +14,10 @@ use crate::lp_network::subscribe_to_topic; use crate::lp_ordermatch::TakerOrderBuilder; use crate::lp_swap::swap_v2_common::mark_swap_as_finished; use crate::lp_swap::taker_restart::get_command_based_on_maker_or_watcher_activity; -use crate::lp_swap::{broadcast_p2p_tx_msg, broadcast_swap_msg_every_delayed, tx_helper_topic, +use crate::lp_swap::{broadcast_p2p_tx_msg, broadcast_swap_msg_every_delayed, swap_ext_topic, tx_helper_topic, wait_for_maker_payment_conf_duration, SwapMsgWrapper, TakerSwapWatcherData, MAX_STARTED_AT_DIFF}; #[cfg(not(feature = "test-use-old-taker"))] -use crate::lp_swap::{swap_ext_topic, NegotiationDataMsgVersion, SwapMsgExt}; +use crate::lp_swap::{NegotiationDataMsgVersion, SwapMsgExt}; use coins::lp_price::fetch_swap_coins_price; #[cfg(not(feature = "test-use-old-taker"))] use coins::SWAP_PROTOCOL_VERSION; From 1ee0949f125b4e34d7b0c3cbd42d6efd59033b70 Mon Sep 17 00:00:00 2001 From: dimxy Date: Sun, 18 Aug 2024 21:47:33 +0500 Subject: [PATCH 16/19] add check of remote version for spending htlc tx (no type tx if version low) --- mm2src/coins/eth.rs | 27 ++++++++++++++----- mm2src/coins/lp_coins.rs | 2 ++ mm2src/coins/solana/solana_tests.rs | 1 + mm2src/coins/utxo/utxo_tests.rs | 3 ++- mm2src/coins/z_coin/z_coin_native_tests.rs | 1 + mm2src/mm2_main/src/lp_swap/maker_swap.rs | 3 +++ mm2src/mm2_main/src/lp_swap/taker_swap.rs | 4 +++ .../tests/docker_tests/docker_tests_inner.rs | 2 ++ .../tests/docker_tests/eth_docker_tests.rs | 2 ++ .../tests/docker_tests/qrc20_tests.rs | 4 +++ 10 files changed, 42 insertions(+), 7 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index cdfa5cefe0..983766e3c5 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -4232,6 +4232,7 @@ impl EthCoin { let secret_vec = args.secret.to_vec(); let watcher_reward = args.watcher_reward; + let can_use_tx_type = SwapFeature::is_active(SwapFeature::EthTypeTx, args.other_version); match self.coin_type { EthCoinType::Eth => { @@ -4286,9 +4287,16 @@ impl EthCoin { .await .map(|list| remove_from_access_list(&list, &[swap_contract_address])) .ok(); - self.sign_and_send_transaction(0.into(), Call(swap_contract_address), data, gas, access_list, true) - .compat() - .await + self.sign_and_send_transaction( + 0.into(), + Call(swap_contract_address), + data, + gas, + access_list, + can_use_tx_type, + ) + .compat() + .await }, EthCoinType::Erc20 { platform: _, @@ -4345,9 +4353,16 @@ impl EthCoin { .await .map(|list| remove_from_access_list(&list, &[swap_contract_address])) .ok(); - self.sign_and_send_transaction(0.into(), Call(swap_contract_address), data, gas, access_list, true) - .compat() - .await + self.sign_and_send_transaction( + 0.into(), + Call(swap_contract_address), + data, + gas, + access_list, + can_use_tx_type, + ) + .compat() + .await }, EthCoinType::Nft { .. } => Err(TransactionErr::ProtocolNotSupported(ERRL!( "Nft Protocol is not supported!" diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 8da088f107..713ec9789d 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -992,6 +992,8 @@ pub struct SpendPaymentArgs<'a> { pub swap_contract_address: &'a Option, pub swap_unique_data: &'a [u8], pub watcher_reward: bool, + /// other party version + pub other_version: u16, } #[derive(Debug)] diff --git a/mm2src/coins/solana/solana_tests.rs b/mm2src/coins/solana/solana_tests.rs index e5cda39139..ca7ef77f80 100644 --- a/mm2src/coins/solana/solana_tests.rs +++ b/mm2src/coins/solana/solana_tests.rs @@ -423,6 +423,7 @@ fn solana_coin_send_and_spend_maker_payment() { let maker_pub = taker_pub; let spends_payment_args = SpendPaymentArgs { + other_version: SWAP_PROTOCOL_VERSION, other_payment_tx: &tx.tx_hex(), time_lock: lock_time, other_pubkey: maker_pub.as_ref(), diff --git a/mm2src/coins/utxo/utxo_tests.rs b/mm2src/coins/utxo/utxo_tests.rs index 868f558f1f..5d6d6019b1 100644 --- a/mm2src/coins/utxo/utxo_tests.rs +++ b/mm2src/coins/utxo/utxo_tests.rs @@ -29,7 +29,7 @@ use crate::utxo::utxo_standard::{utxo_standard_coin_with_priv_key, UtxoStandardC use crate::utxo::utxo_tx_history_v2::{UtxoTxDetailsParams, UtxoTxHistoryOps}; use crate::{BlockHeightAndTime, CoinBalance, ConfirmPaymentInput, DexFee, IguanaPrivKey, PrivKeyBuildPolicy, SearchForSwapTxSpendInput, SpendPaymentArgs, StakingInfosDetails, SwapOps, TradePreimageValue, - TxFeeDetails, TxMarshalingErr, ValidateFeeArgs, INVALID_SENDER_ERR_LOG}; + TxFeeDetails, TxMarshalingErr, ValidateFeeArgs, INVALID_SENDER_ERR_LOG, SWAP_PROTOCOL_VERSION}; #[cfg(not(target_arch = "wasm32"))] use crate::{WaitForHTLCTxSpendArgs, WithdrawFee}; use chain::{BlockHeader, BlockHeaderBits, OutPoint}; @@ -165,6 +165,7 @@ fn test_send_maker_spends_taker_payment_recoverable_tx() { let tx_hex = hex::decode("0100000001de7aa8d29524906b2b54ee2e0281f3607f75662cbc9080df81d1047b78e21dbc00000000d7473044022079b6c50820040b1fbbe9251ced32ab334d33830f6f8d0bf0a40c7f1336b67d5b0220142ccf723ddabb34e542ed65c395abc1fbf5b6c3e730396f15d25c49b668a1a401209da937e5609680cb30bff4a7661364ca1d1851c2506fa80c443f00a3d3bf7365004c6b6304f62b0e5cb175210270e75970bb20029b3879ec76c4acd320a8d0589e003636264d01a7d566504bfbac6782012088a9142fb610d856c19fd57f2d0cffe8dff689074b3d8a882103f368228456c940ac113e53dad5c104cf209f2f102a409207269383b6ab9b03deac68ffffffff01d0dc9800000000001976a9146d9d2b554d768232320587df75c4338ecc8bf37d88ac40280e5c").unwrap(); let secret = hex::decode("9da937e5609680cb30bff4a7661364ca1d1851c2506fa80c443f00a3d3bf7365").unwrap(); let maker_spends_payment_args = SpendPaymentArgs { + other_version: SWAP_PROTOCOL_VERSION, other_payment_tx: &tx_hex, time_lock: 777, other_pubkey: coin.my_public_key().unwrap(), diff --git a/mm2src/coins/z_coin/z_coin_native_tests.rs b/mm2src/coins/z_coin/z_coin_native_tests.rs index 295858ee63..ac1d3f234e 100644 --- a/mm2src/coins/z_coin/z_coin_native_tests.rs +++ b/mm2src/coins/z_coin/z_coin_native_tests.rs @@ -124,6 +124,7 @@ fn zombie_coin_send_and_spend_maker_payment() { let maker_pub = taker_pub; let spends_payment_args = SpendPaymentArgs { + other_version: SWAP_PROTOCOL_VERSION, other_payment_tx: &tx.tx_hex(), time_lock: lock_time, other_pubkey: maker_pub, diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap.rs b/mm2src/mm2_main/src/lp_swap/maker_swap.rs index 063a1ccf10..a2e7188ba0 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap.rs @@ -1124,6 +1124,7 @@ impl MakerSwap { let other_taker_coin_htlc_pub = self.r().other_taker_coin_htlc_pub; let secret = self.r().data.secret; let maker_spends_payment_args = SpendPaymentArgs { + other_version: self.r().data.taker_version.unwrap_or(LEGACY_PROTOCOL_VERSION), other_payment_tx: &self.r().taker_payment.clone().unwrap().tx_hex.clone(), time_lock: self.taker_payment_lock.load(Ordering::Relaxed), other_pubkey: &*other_taker_coin_htlc_pub, @@ -1438,6 +1439,7 @@ impl MakerSwap { let secret = selfi.r().data.secret.0; let unique_data = selfi.unique_swap_data(); let watcher_reward = selfi.r().watcher_reward; + let other_version = selfi.r().data.taker_version.unwrap_or(LEGACY_PROTOCOL_VERSION); let search_input = SearchForSwapTxSpendInput { time_lock: timelock, @@ -1472,6 +1474,7 @@ impl MakerSwap { selfi .taker_coin .send_maker_spends_taker_payment(SpendPaymentArgs { + other_version, other_payment_tx: taker_payment_hex, time_lock: timelock, other_pubkey: other_taker_coin_htlc_pub.as_slice(), diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap.rs b/mm2src/mm2_main/src/lp_swap/taker_swap.rs index a7c3f3c82d..71a1a22f43 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap.rs @@ -1826,6 +1826,7 @@ impl TakerSwap { let other_maker_coin_htlc_pub = self.r().other_maker_coin_htlc_pub; let secret = self.r().secret; let taker_spends_payment_args = SpendPaymentArgs { + other_version: self.r().data.maker_version.unwrap_or(LEGACY_PROTOCOL_VERSION), other_payment_tx: &self.r().maker_payment.clone().unwrap().tx_hex, time_lock: self.maker_payment_lock.load(Ordering::Relaxed), other_pubkey: &*other_maker_coin_htlc_pub, @@ -2193,10 +2194,12 @@ impl TakerSwap { let secret = self.r().secret.0; let maker_coin_swap_contract_address = self.r().data.maker_coin_swap_contract_address.clone(); let watcher_reward = self.r().watcher_reward; + let other_version = self.r().data.maker_version.unwrap_or(LEGACY_PROTOCOL_VERSION); let maybe_spend_tx = self .maker_coin .send_taker_spends_maker_payment(SpendPaymentArgs { + other_version, other_payment_tx: &maker_payment, time_lock: self.maker_payment_lock.load(Ordering::Relaxed), other_pubkey: other_maker_coin_htlc_pub.as_slice(), @@ -2256,6 +2259,7 @@ impl TakerSwap { ); let taker_spends_payment_args = SpendPaymentArgs { + other_version: self.r().data.maker_version.unwrap_or(LEGACY_PROTOCOL_VERSION), other_payment_tx: &maker_payment, time_lock: self.maker_payment_lock.load(Ordering::Relaxed), other_pubkey: other_maker_coin_htlc_pub.as_slice(), diff --git a/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs b/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs index 86864a420b..da305f9674 100644 --- a/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs +++ b/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs @@ -221,6 +221,7 @@ fn test_search_for_taker_swap_tx_spend_native_was_spent_by_maker() { }; coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); let maker_spends_payment_args = SpendPaymentArgs { + other_version: SWAP_PROTOCOL_VERSION, other_payment_tx: &tx.tx_hex(), time_lock, other_pubkey: my_pubkey, @@ -290,6 +291,7 @@ fn test_search_for_maker_swap_tx_spend_native_was_spent_by_taker() { }; coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); let taker_spends_payment_args = SpendPaymentArgs { + other_version: SWAP_PROTOCOL_VERSION, other_payment_tx: &tx.tx_hex(), time_lock, other_pubkey: my_pubkey, 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 98e9b2b9d0..af3588d787 100644 --- a/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs @@ -686,6 +686,7 @@ fn send_and_spend_eth_maker_payment_impl(swap_txfee_policy: SwapTxFeePolicy) { taker_eth_coin.wait_for_confirmations(confirm_input).wait().unwrap(); let spend_args = SpendPaymentArgs { + other_version: SWAP_PROTOCOL_VERSION, other_payment_tx: ð_maker_payment.tx_hex(), time_lock, other_pubkey: &maker_pubkey, @@ -876,6 +877,7 @@ fn send_and_spend_erc20_maker_payment_impl(swap_txfee_policy: SwapTxFeePolicy) { } let spend_args = SpendPaymentArgs { + other_version: SWAP_PROTOCOL_VERSION, other_payment_tx: ð_maker_payment.tx_hex(), time_lock, other_pubkey: &maker_pubkey, diff --git a/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs b/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs index 34f73a732a..5d09d0dbd1 100644 --- a/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs @@ -228,6 +228,7 @@ fn test_taker_spends_maker_payment() { }; block_on(taker_coin.validate_maker_payment(input)).unwrap(); let taker_spends_payment_args = SpendPaymentArgs { + other_version: SWAP_PROTOCOL_VERSION, other_payment_tx: &payment_tx_hex, time_lock: timelock, other_pubkey: &maker_pub, @@ -331,6 +332,7 @@ fn test_maker_spends_taker_payment() { }; block_on(maker_coin.validate_taker_payment(input)).unwrap(); let maker_spends_payment_args = SpendPaymentArgs { + other_version: SWAP_PROTOCOL_VERSION, other_payment_tx: &payment_tx_hex, time_lock: timelock, other_pubkey: &taker_pub, @@ -609,6 +611,7 @@ fn test_search_for_swap_tx_spend_taker_spent() { }; taker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); let taker_spends_payment_args = SpendPaymentArgs { + other_version: SWAP_PROTOCOL_VERSION, other_payment_tx: &payment_tx_hex, time_lock: timelock, other_pubkey: maker_pub, @@ -855,6 +858,7 @@ fn test_wait_for_tx_spend() { thread::spawn(move || { thread::sleep(Duration::from_secs(5)); let taker_spends_payment_args = SpendPaymentArgs { + other_version: SWAP_PROTOCOL_VERSION, other_payment_tx: &payment_hex, time_lock: timelock, other_pubkey: &maker_pub_c, From 35de66361e35686668918d50e0233b707fec99d2 Mon Sep 17 00:00:00 2001 From: dimxy Date: Mon, 19 Aug 2024 22:17:30 +0500 Subject: [PATCH 17/19] fix creating access_list as None when not configured --- mm2src/coins/eth.rs | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 983766e3c5..3145297807 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -738,7 +738,7 @@ macro_rules! set_tx_type_from_pay_for_gas_option { }; } -/// set tx type if pay_for_gas_option requires type 2 +/// set tx type if access_list requires type 1 #[macro_export] macro_rules! set_tx_type_from_access_list { ($tx_type: ident, $access_list: expr) => { @@ -3826,8 +3826,9 @@ impl EthCoin { let coin = self.clone(); Box::new( access_list_fut - .map(move |list| remove_from_access_list(&list, &[swap_contract_address])) // edit access list to remove an item that does not reduce gas - .map(Some) + .map(move |opt_list| { + opt_list.map(|list| remove_from_access_list(&list, &[swap_contract_address])) + }) // edit access list to remove an item that does not reduce gas .or_else(|_err| futures01::future::ok(None::)) // ignore create_access_list_if_configured errors and use None value .and_then(move |list| { coin.sign_and_send_transaction( @@ -3934,8 +3935,7 @@ impl EthCoin { .and_then(move |_| { let access_list_fut = arc.create_access_list_if_configured(swap_contract_address, Some(value), Some(data.clone()), Some(gas)); access_list_fut - .map(move |list| remove_from_access_list(&list, &[swap_contract_address])) - .map(Some) + .map(move |opt_list| opt_list.map(|list| remove_from_access_list(&list, &[swap_contract_address]))) .or_else(|_err| futures01::future::ok(None::)) .and_then(move |list| { arc.sign_and_send_transaction( @@ -3954,8 +3954,7 @@ impl EthCoin { let access_list_fut = arc.create_access_list_if_configured(swap_contract_address, Some(value), Some(data.clone()), Some(gas)); Box::new( access_list_fut - .map(move |list| remove_from_access_list(&list, &[swap_contract_address])) - .map(Some) + .map(move |opt_list| opt_list.map(|list| remove_from_access_list(&list, &[swap_contract_address]))) .or_else(|_err| futures01::future::ok(None::)) .and_then(move |list| arc.sign_and_send_transaction(value, Action::Call(swap_contract_address), data, gas, list, can_use_tx_type)) ) @@ -4285,8 +4284,8 @@ impl EthCoin { ) .compat() .await - .map(|list| remove_from_access_list(&list, &[swap_contract_address])) - .ok(); + .map(|opt_list| opt_list.map(|list| remove_from_access_list(&list, &[swap_contract_address]))) + .unwrap_or(None); self.sign_and_send_transaction( 0.into(), Call(swap_contract_address), @@ -4351,8 +4350,8 @@ impl EthCoin { ) .compat() .await - .map(|list| remove_from_access_list(&list, &[swap_contract_address])) - .ok(); + .map(|opt_list| opt_list.map(|list| remove_from_access_list(&list, &[swap_contract_address]))) + .unwrap_or(None); self.sign_and_send_transaction( 0.into(), Call(swap_contract_address), @@ -4431,8 +4430,8 @@ impl EthCoin { .create_access_list_if_configured(swap_contract_address, Some(value), Some(data.clone()), Some(gas)) .compat() .await - .map(|list| remove_from_access_list(&list, &[swap_contract_address])) - .ok(); + .map(|opt_list| opt_list.map(|list| remove_from_access_list(&list, &[swap_contract_address]))) + .unwrap_or(None); self.sign_and_send_transaction(0.into(), Call(swap_contract_address), data, gas, access_list, true) .compat() .await @@ -4490,8 +4489,8 @@ impl EthCoin { ) .compat() .await - .map(|list| remove_from_access_list(&list, &[swap_contract_address])) - .ok(); // ignore errors + .map(|opt_list| opt_list.map(|list| remove_from_access_list(&list, &[swap_contract_address]))) + .unwrap_or(None); // ignore errors self.sign_and_send_transaction(0.into(), Call(swap_contract_address), data, gas, access_list, true) .compat() .await @@ -5618,11 +5617,11 @@ impl EthCoin { value: Option, data: Option>, gas: Option, - ) -> Box + Send> { + ) -> Box, Error = String> + Send> { let coin = self.clone(); let fut = async move { if !coin.use_access_list { - return Ok(ethcore_transaction::AccessList(vec![])); + return Ok(None); } let my_address = coin .derivation_method @@ -5650,7 +5649,7 @@ impl EthCoin { err }) .map_err(|err| err.to_string()) - .map(|result| map_web3_access_list(&result.access_list)) + .map(|result| Some(map_web3_access_list(&result.access_list))) }; Box::new(Box::pin(fut).compat()) } From 81750930bcf2bd411bc9fafe1ec38f927855e441 Mon Sep 17 00:00:00 2001 From: dimxy Date: Fri, 13 Sep 2024 17:33:33 +0500 Subject: [PATCH 18/19] fix recreate taker swap started event (correct maker version) --- mm2src/mm2_main/src/lp_swap/recreate_swap_data.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm2src/mm2_main/src/lp_swap/recreate_swap_data.rs b/mm2src/mm2_main/src/lp_swap/recreate_swap_data.rs index 820bbb2ef2..1e70d44061 100644 --- a/mm2src/mm2_main/src/lp_swap/recreate_swap_data.rs +++ b/mm2src/mm2_main/src/lp_swap/recreate_swap_data.rs @@ -321,7 +321,7 @@ async fn recreate_taker_swap(ctx: MmArc, maker_swap: MakerSavedSwap) -> Recreate let mut maker_p2p_pubkey = [0; 32]; maker_p2p_pubkey.copy_from_slice(&started_event.my_persistent_pub.0[1..33]); let taker_started_event = TakerSwapEvent::Started(TakerSwapData { - maker_version: Some(SWAP_PROTOCOL_VERSION), + maker_version: None, taker_coin: started_event.taker_coin, maker_coin: started_event.maker_coin.clone(), maker: H256Json::from(maker_p2p_pubkey), From 92fc0f3c5e3e6a2d0e7e66727f51bd3b19a482bb Mon Sep 17 00:00:00 2001 From: dimxy Date: Fri, 13 Sep 2024 17:34:22 +0500 Subject: [PATCH 19/19] fix maker/taker version in test data (for recreate swap tests to work) --- .../src/for_tests/recreate_maker_swap_maker_expected.json | 1 + ...er_swap_maker_payment_wait_confirm_failed_maker_expected.json | 1 + .../src/for_tests/recreate_taker_swap_taker_expected.json | 1 + ...er_swap_taker_payment_wait_confirm_failed_taker_expected.json | 1 + 4 files changed, 4 insertions(+) diff --git a/mm2src/mm2_main/src/for_tests/recreate_maker_swap_maker_expected.json b/mm2src/mm2_main/src/for_tests/recreate_maker_swap_maker_expected.json index 180cade8d2..4c4e83f43f 100644 --- a/mm2src/mm2_main/src/for_tests/recreate_maker_swap_maker_expected.json +++ b/mm2src/mm2_main/src/for_tests/recreate_maker_swap_maker_expected.json @@ -26,6 +26,7 @@ "taker_payment_spend_trade_fee":null}}}, {"timestamp":1638984456603,"event":{"type":"Negotiated", "data":{ + "taker_version": 1, "taker_payment_locktime":1638992240, "taker_pubkey":"03b1e544ce2d860219bc91314b5483421a553a7b33044659eff0be9214ed58addd", "maker_coin_swap_contract_addr":null, diff --git a/mm2src/mm2_main/src/for_tests/recreate_maker_swap_maker_payment_wait_confirm_failed_maker_expected.json b/mm2src/mm2_main/src/for_tests/recreate_maker_swap_maker_payment_wait_confirm_failed_maker_expected.json index 4e95aa537a..e329b26758 100644 --- a/mm2src/mm2_main/src/for_tests/recreate_maker_swap_maker_payment_wait_confirm_failed_maker_expected.json +++ b/mm2src/mm2_main/src/for_tests/recreate_maker_swap_maker_payment_wait_confirm_failed_maker_expected.json @@ -26,6 +26,7 @@ "taker_payment_spend_trade_fee":null}}}, {"timestamp":1638984456603,"event":{"type":"Negotiated", "data":{ + "taker_version": 1, "taker_payment_locktime":1638992240, "taker_pubkey":"03b1e544ce2d860219bc91314b5483421a553a7b33044659eff0be9214ed58addd", "maker_coin_swap_contract_addr":null, diff --git a/mm2src/mm2_main/src/for_tests/recreate_taker_swap_taker_expected.json b/mm2src/mm2_main/src/for_tests/recreate_taker_swap_taker_expected.json index cfb9ab66f8..2ce62fa260 100644 --- a/mm2src/mm2_main/src/for_tests/recreate_taker_swap_taker_expected.json +++ b/mm2src/mm2_main/src/for_tests/recreate_taker_swap_taker_expected.json @@ -26,6 +26,7 @@ "maker_payment_spend_trade_fee":null}}}, {"timestamp":1638984456204,"event":{"type":"Negotiated", "data":{ + "maker_version": 1, "maker_payment_locktime":1639000040, "maker_pubkey":"0315d9c51c657ab1be4ae9d3ab6e76a619d3bccfe830d5363fa168424c0d044732", "secret_hash":"4da9e7080175e8e10842e0e161b33cd298cab30b", diff --git a/mm2src/mm2_main/src/for_tests/recreate_taker_swap_taker_payment_wait_confirm_failed_taker_expected.json b/mm2src/mm2_main/src/for_tests/recreate_taker_swap_taker_payment_wait_confirm_failed_taker_expected.json index cb3146b2a3..638758c280 100644 --- a/mm2src/mm2_main/src/for_tests/recreate_taker_swap_taker_payment_wait_confirm_failed_taker_expected.json +++ b/mm2src/mm2_main/src/for_tests/recreate_taker_swap_taker_payment_wait_confirm_failed_taker_expected.json @@ -26,6 +26,7 @@ "maker_payment_spend_trade_fee":null}}}, {"timestamp":1638984456204,"event":{"type":"Negotiated", "data":{ + "maker_version": 1, "maker_payment_locktime":1639000040, "maker_pubkey":"0315d9c51c657ab1be4ae9d3ab6e76a619d3bccfe830d5363fa168424c0d044732", "secret_hash":"4da9e7080175e8e10842e0e161b33cd298cab30b",