Skip to content

Commit

Permalink
feat(tendermint): implement better sequence resolving logic (#2164)
Browse files Browse the repository at this point in the history
* resolve the expected sequence number locally

Signed-off-by: onur-ozkan <work@onurozkan.dev>

* drop SWAP locks for tendermint tests

Signed-off-by: onur-ozkan <work@onurozkan.dev>

* bless clippy

Signed-off-by: onur-ozkan <work@onurozkan.dev>

* chain regex options

Signed-off-by: onur-ozkan <work@onurozkan.dev>

* update account sequence error message

Signed-off-by: onur-ozkan <work@onurozkan.dev>

---------

Signed-off-by: onur-ozkan <work@onurozkan.dev>
  • Loading branch information
onur-ozkan authored Jul 17, 2024
1 parent 7f56e95 commit 4db6c66
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 29 deletions.
72 changes: 56 additions & 16 deletions mm2src/coins/tendermint/tendermint_coin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -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;
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -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;
}
Expand Down Expand Up @@ -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(
Expand All @@ -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;
}
Expand Down Expand Up @@ -1155,7 +1166,7 @@ impl TendermintCoin {
&self,
maybe_pk: Option<H256>,
message: Any,
account_info: BaseAccount,
account_info: &BaseAccount,
fee: Fee,
timeout_height: u64,
memo: String,
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 = {
Expand Down Expand Up @@ -3256,6 +3267,20 @@ pub async fn get_ibc_transfer_channels(
})
}

fn parse_expected_sequence_number(e: &str) -> MmResult<u64, TendermintCoinRpcError> {
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::*;
Expand Down Expand Up @@ -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());
}
}
2 changes: 1 addition & 1 deletion mm2src/coins/tendermint/tendermint_token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down
12 changes: 0 additions & 12 deletions mm2src/mm2_main/tests/docker_tests/tendermint_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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";
Expand Down Expand Up @@ -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);

Expand Down

0 comments on commit 4db6c66

Please sign in to comment.