diff --git a/mm2src/coins/tendermint/tendermint_coin.rs b/mm2src/coins/tendermint/tendermint_coin.rs index 42382c06e6..f3854ba4ee 100644 --- a/mm2src/coins/tendermint/tendermint_coin.rs +++ b/mm2src/coins/tendermint/tendermint_coin.rs @@ -68,6 +68,7 @@ use mm2_git::{FileMetadata, GitController, GithubClient, RepositoryOperations, G use mm2_number::MmNumber; use parking_lot::Mutex as PaMutex; use primitives::hash::H256; +use regex::Regex; use rpc::v1::types::Bytes as BytesJson; use serde_json::{self as json, Value as Json}; use std::collections::HashMap; @@ -105,7 +106,11 @@ pub(crate) const TX_DEFAULT_MEMO: &str = ""; const MAX_TIME_LOCK: i64 = 34560; const MIN_TIME_LOCK: i64 = 50; -const ACCOUNT_SEQUENCE_ERR: &str = "incorrect account sequence"; +const ACCOUNT_SEQUENCE_ERR: &str = "account sequence mismatch"; + +lazy_static! { + static ref SEQUENCE_PARSER_REGEX: Regex = Regex::new(r"expected (\d+)").unwrap(); +} pub struct SerializedUnsignedTx { tx_json: Json, @@ -748,7 +753,7 @@ impl TendermintCoin { // Therefore, we can call SimulateRequest or CheckTx(doesn't work with using Abci interface) to get used gas or fee itself. pub(super) fn gen_simulated_tx( &self, - account_info: BaseAccount, + account_info: &BaseAccount, priv_key: &Secp256k1Secret, tx_payload: Any, timeout_height: u64, @@ -835,10 +840,11 @@ impl TendermintCoin { timeout_height: u64, memo: String, ) -> Result<(String, Raw), TransactionErr> { + let mut account_info = try_tx_s!(self.account_info(&self.account_id).await); let (tx_id, tx_raw) = loop { let tx_raw = try_tx_s!(self.any_to_signed_raw_tx( try_tx_s!(self.activation_policy.activated_key_or_err()), - try_tx_s!(self.account_info(&self.account_id).await), + &account_info, tx_payload.clone(), fee.clone(), timeout_height, @@ -849,6 +855,7 @@ impl TendermintCoin { Ok(tx_id) => break (tx_id, tx_raw), Err(e) => { if e.contains(ACCOUNT_SEQUENCE_ERR) { + account_info.sequence = try_tx_s!(parse_expected_sequence_number(&e)); debug!("Got wrong account sequence, trying again."); continue; } @@ -878,9 +885,9 @@ impl TendermintCoin { let account_info = try_tx_s!(self.account_info(&self.account_id).await); let SerializedUnsignedTx { tx_json, body_bytes } = if self.is_keplr_from_ledger { - try_tx_s!(self.any_to_legacy_amino_json(account_info, tx_payload, fee, timeout_height, memo)) + try_tx_s!(self.any_to_legacy_amino_json(&account_info, tx_payload, fee, timeout_height, memo)) } else { - try_tx_s!(self.any_to_serialized_sign_doc(account_info, tx_payload, fee, timeout_height, memo)) + try_tx_s!(self.any_to_serialized_sign_doc(&account_info, tx_payload, fee, timeout_height, memo)) }; let data: TxHashData = try_tx_s!(ctx @@ -925,11 +932,11 @@ impl TendermintCoin { return Ok(Fee::from_amount_and_gas(fee_amount, gas_limit)); }; + let mut account_info = self.account_info(&self.account_id).await?; let (response, raw_response) = loop { - let account_info = self.account_info(&self.account_id).await?; let tx_bytes = self .gen_simulated_tx( - account_info, + &account_info, activated_priv_key, msg.clone(), timeout_height, @@ -946,7 +953,9 @@ impl TendermintCoin { let raw_response = self.rpc_client().await?.perform(request).await?; - if raw_response.response.log.to_string().contains(ACCOUNT_SEQUENCE_ERR) { + let log = raw_response.response.log.to_string(); + if log.contains(ACCOUNT_SEQUENCE_ERR) { + account_info.sequence = parse_expected_sequence_number(&log)?; debug!("Got wrong account sequence, trying again."); continue; } @@ -1001,10 +1010,10 @@ impl TendermintCoin { return Ok(((GAS_WANTED_BASE_VALUE * 1.5) * gas_price).ceil() as u64); }; + let mut account_info = self.account_info(account_id).await?; let (response, raw_response) = loop { - let account_info = self.account_info(account_id).await?; let tx_bytes = self - .gen_simulated_tx(account_info, &priv_key, msg.clone(), timeout_height, memo.clone()) + .gen_simulated_tx(&account_info, &priv_key, msg.clone(), timeout_height, memo.clone()) .map_to_mm(|e| TendermintCoinRpcError::InternalError(format!("{}", e)))?; let request = AbciRequest::new( @@ -1016,7 +1025,9 @@ impl TendermintCoin { let raw_response = self.rpc_client().await?.perform(request).await?; - if raw_response.response.log.to_string().contains(ACCOUNT_SEQUENCE_ERR) { + let log = raw_response.response.log.to_string(); + if log.contains(ACCOUNT_SEQUENCE_ERR) { + account_info.sequence = parse_expected_sequence_number(&log)?; debug!("Got wrong account sequence, trying again."); continue; } @@ -1155,7 +1166,7 @@ impl TendermintCoin { &self, maybe_pk: Option, message: Any, - account_info: BaseAccount, + account_info: &BaseAccount, fee: Fee, timeout_height: u64, memo: String, @@ -1239,7 +1250,7 @@ impl TendermintCoin { pub(super) fn any_to_signed_raw_tx( &self, priv_key: &Secp256k1Secret, - account_info: BaseAccount, + account_info: &BaseAccount, tx_payload: Any, fee: Fee, timeout_height: u64, @@ -1254,7 +1265,7 @@ impl TendermintCoin { pub(super) fn any_to_serialized_sign_doc( &self, - account_info: BaseAccount, + account_info: &BaseAccount, tx_payload: Any, fee: Fee, timeout_height: u64, @@ -1286,7 +1297,7 @@ impl TendermintCoin { /// Visit https://docs.cosmos.network/main/build/architecture/adr-050-sign-mode-textual#context for more context. pub(super) fn any_to_legacy_amino_json( &self, - account_info: BaseAccount, + account_info: &BaseAccount, tx_payload: Any, fee: Fee, timeout_height: u64, @@ -2264,7 +2275,7 @@ impl MmCoin for TendermintCoin { let account_info = coin.account_info(&account_id).await?; let tx = coin - .any_to_transaction_data(maybe_pk, msg_payload, account_info, fee, timeout_height, memo.clone()) + .any_to_transaction_data(maybe_pk, msg_payload, &account_info, fee, timeout_height, memo.clone()) .map_to_mm(|e| WithdrawError::InternalError(e.to_string()))?; let internal_id = { @@ -3256,6 +3267,20 @@ pub async fn get_ibc_transfer_channels( }) } +fn parse_expected_sequence_number(e: &str) -> MmResult { + if let Some(sequence) = SEQUENCE_PARSER_REGEX.captures(e).and_then(|c| c.get(1)) { + let account_sequence = + u64::from_str(sequence.as_str()).map_to_mm(|e| TendermintCoinRpcError::InternalError(e.to_string()))?; + + return Ok(account_sequence); + } + + MmError::err(TendermintCoinRpcError::InternalError(format!( + "Could not parse the expected sequence number from this error message: '{}'", + e + ))) +} + #[cfg(test)] pub mod tendermint_coin_tests { use super::*; @@ -4165,4 +4190,19 @@ pub mod tendermint_coin_tests { // Public and private keys are from the same keypair, account ids must be equal. assert_eq!(pk_account_id, pb_account_id); } + + #[test] + fn test_parse_expected_sequence_number() { + assert_eq!( + 13, + parse_expected_sequence_number("check_tx log: account sequence mismatch, expected 13").unwrap() + ); + assert_eq!( + 5, + parse_expected_sequence_number("check_tx log: account sequence mismatch, expected 5, got...").unwrap() + ); + assert_eq!(17, parse_expected_sequence_number("account sequence mismatch, expected. check_tx log: account sequence mismatch, expected 17, got 16: incorrect account sequence, deliver_tx log...").unwrap()); + assert!(parse_expected_sequence_number("").is_err()); + assert!(parse_expected_sequence_number("check_tx log: account sequence mismatch, expected").is_err()); + } } diff --git a/mm2src/coins/tendermint/tendermint_token.rs b/mm2src/coins/tendermint/tendermint_token.rs index 90407fa8ae..3e63589926 100644 --- a/mm2src/coins/tendermint/tendermint_token.rs +++ b/mm2src/coins/tendermint/tendermint_token.rs @@ -599,7 +599,7 @@ impl MmCoin for TendermintToken { let account_info = platform.account_info(&account_id).await?; let tx = platform - .any_to_transaction_data(maybe_pk, msg_payload, account_info, fee, timeout_height, memo.clone()) + .any_to_transaction_data(maybe_pk, msg_payload, &account_info, fee, timeout_height, memo.clone()) .map_to_mm(|e| WithdrawError::InternalError(e.to_string()))?; let internal_id = { diff --git a/mm2src/mm2_main/tests/docker_tests/tendermint_tests.rs b/mm2src/mm2_main/tests/docker_tests/tendermint_tests.rs index a11b2f0383..80e4470d70 100644 --- a/mm2src/mm2_main/tests/docker_tests/tendermint_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/tendermint_tests.rs @@ -667,21 +667,13 @@ mod swap { wait_check_stats_swap_status, DOC_ELECTRUM_ADDRS}; use std::convert::TryFrom; use std::str::FromStr; - use std::sync::Mutex; use std::{env, thread}; const BOB_PASSPHRASE: &str = "iris test seed"; const ALICE_PASSPHRASE: &str = "iris test2 seed"; - lazy_static! { - // Simple lock used for running the swap tests sequentially. - static ref SWAP_LOCK: Mutex<()> = Mutex::new(()); - } - #[test] fn swap_nucleus_with_doc() { - let _lock = SWAP_LOCK.lock().unwrap(); - let bob_passphrase = String::from(BOB_PASSPHRASE); let alice_passphrase = String::from(ALICE_PASSPHRASE); @@ -760,8 +752,6 @@ mod swap { #[test] fn swap_nucleus_with_eth() { - let _lock = SWAP_LOCK.lock().unwrap(); - let bob_passphrase = String::from(BOB_PASSPHRASE); let alice_passphrase = String::from(ALICE_PASSPHRASE); const BOB_ETH_ADDRESS: &str = "0x7b338250f990954E3Ab034ccD32a917c2F607C2d"; @@ -868,8 +858,6 @@ mod swap { #[test] fn swap_doc_with_iris_ibc_nucleus() { - let _lock = SWAP_LOCK.lock().unwrap(); - let bob_passphrase = String::from(BOB_PASSPHRASE); let alice_passphrase = String::from(ALICE_PASSPHRASE);