diff --git a/crates/astria-sequencer/src/mempool/mod.rs b/crates/astria-sequencer/src/mempool/mod.rs index c015965ab0..d2ca60b1af 100644 --- a/crates/astria-sequencer/src/mempool/mod.rs +++ b/crates/astria-sequencer/src/mempool/mod.rs @@ -20,7 +20,6 @@ use astria_core::{ use astria_eyre::eyre::Result; pub(crate) use mempool_state::get_account_balances; use tokio::{ - join, sync::{ RwLock, RwLockWriteGuard, @@ -133,6 +132,7 @@ pub(crate) struct Mempool { pending: Arc>, parked: Arc>>, comet_bft_removal_cache: Arc>, + contained_txs: Arc>>, } impl Mempool { @@ -145,6 +145,7 @@ impl Mempool { NonZeroUsize::try_from(REMOVAL_CACHE_SIZE) .expect("Removal cache cannot be zero sized"), ))), + contained_txs: Arc::new(RwLock::new(HashSet::new())), } } @@ -152,12 +153,7 @@ impl Mempool { #[must_use] #[instrument(skip_all)] pub(crate) async fn len(&self) -> usize { - #[rustfmt::skip] - let (pending_len, parked_len) = join!( - async { self.pending.read().await.len() }, - async { self.parked.read().await.len() } - ); - pending_len.saturating_add(parked_len) + self.contained_txs.read().await.len() } /// Inserts a transaction into the mempool and does not allow for transaction replacement. @@ -184,11 +180,18 @@ impl Mempool { // Release the lock asap. drop(pending); // try to add to parked queue - parked.add( - timemarked_tx, + match parked.add( + timemarked_tx.clone(), current_account_nonce, ¤t_account_balances, - ) + ) { + Ok(()) => { + // track in contained txs + self.contained_txs.write().await.insert(timemarked_tx.id()); + Ok(()) + } + Err(err) => Err(err), + } } error @ Err( InsertionError::AlreadyPresent @@ -220,6 +223,10 @@ impl Mempool { ); } } + + // track in contained txs + self.contained_txs.write().await.insert(timemarked_tx.id()); + Ok(()) } } @@ -266,10 +273,12 @@ impl Mempool { // Add all removed to removal cache for cometbft. let mut removal_cache = self.comet_bft_removal_cache.write().await; - // Add the original tx first, since it will also be listed in `removed_txs`. The second + + // Add the original tx first to preserve its reason for removal. The second // attempt to add it inside the loop below will be a no-op. removal_cache.add(tx_hash, reason); for removed_tx in removed_txs { + self.contained_txs.write().await.remove(&removed_tx); removal_cache.add(removed_tx, RemovalReason::LowerNonceInvalidated); } } @@ -281,6 +290,12 @@ impl Mempool { self.comet_bft_removal_cache.write().await.remove(tx_hash) } + /// Returns true if the transaction is tracked as inserted. + #[instrument(skip_all)] + pub(crate) async fn tracked(&self, tx_hash: [u8; 32]) -> bool { + self.contained_txs.read().await.contains(&tx_hash) + } + /// Updates stored transactions to reflect current blockchain state. Will remove transactions /// that have stale nonces or are expired. Will also shift transation between pending and /// parked to relfect changes in account balances. @@ -380,10 +395,12 @@ impl Mempool { drop(parked); drop(pending); - // add to removal cache for cometbft + // add to removal cache for cometbft and remove from the tracked set let mut removal_cache = self.comet_bft_removal_cache.write().await; + let mut tracked_txs = self.contained_txs.write().await; for (tx_hash, reason) in removed_txs { removal_cache.add(tx_hash, reason); + tracked_txs.remove(&tx_hash); } } @@ -947,4 +964,62 @@ mod test { "first removal reason should be presenved" ); } + + #[tokio::test] + async fn tx_tracked_set() { + let mempool = Mempool::new(); + let signing_key = SigningKey::from([1; 32]); + let signing_address = signing_key.verification_key().address_bytes(); + let account_balances = mock_balances(100, 100); + let tx_cost = mock_tx_cost(10, 10, 0); + + let tx0 = mock_tx(0, &signing_key, "test"); + let tx1 = mock_tx(1, &signing_key, "test"); + + // check that the parked transaction is in the tracked set + mempool + .insert(tx1.clone(), 0, account_balances.clone(), tx_cost.clone()) + .await + .unwrap(); + assert!(mempool.tracked(tx1.id().get()).await); + + // check that the pending transaction is in the tracked set + mempool + .insert(tx0.clone(), 0, account_balances.clone(), tx_cost.clone()) + .await + .unwrap(); + assert!(mempool.tracked(tx0.id().get()).await); + + // remove the transactions from the mempool + mempool + .remove_tx_invalid(tx0.clone(), RemovalReason::Expired) + .await; + + // check that the transactions are not in the tracked set + assert!(!mempool.tracked(tx0.id().get()).await); + assert!(!mempool.tracked(tx1.id().get()).await); + + // re-insert the transactions into the mempool + mempool + .insert(tx0.clone(), 0, account_balances.clone(), tx_cost.clone()) + .await + .unwrap(); + mempool + .insert(tx1.clone(), 0, account_balances.clone(), tx_cost.clone()) + .await + .unwrap(); + + // check that the transactions are in the tracked set + assert!(mempool.tracked(tx0.id().get()).await); + assert!(mempool.tracked(tx1.id().get()).await); + + // remove the transacitons from the mempool via maintenance + let mut mock_state = mock_state_getter().await; + mock_state_put_account_nonce(&mut mock_state, signing_address, 2); + mempool.run_maintenance(&mock_state, false).await; + + // check that the transactions are not in the tracked set + assert!(!mempool.tracked(tx0.id().get()).await); + assert!(!mempool.tracked(tx1.id().get()).await); + } } diff --git a/crates/astria-sequencer/src/mempool/transactions_container.rs b/crates/astria-sequencer/src/mempool/transactions_container.rs index a0d1e5ee08..bd84b851f1 100644 --- a/crates/astria-sequencer/src/mempool/transactions_container.rs +++ b/crates/astria-sequencer/src/mempool/transactions_container.rs @@ -110,6 +110,10 @@ impl TimemarkedTransaction { pub(super) fn cost(&self) -> &HashMap { &self.cost } + + pub(super) fn id(&self) -> [u8; 32] { + self.tx_hash + } } impl fmt::Display for TimemarkedTransaction { diff --git a/crates/astria-sequencer/src/metrics.rs b/crates/astria-sequencer/src/metrics.rs index d051c68cfc..413196fe9d 100644 --- a/crates/astria-sequencer/src/metrics.rs +++ b/crates/astria-sequencer/src/metrics.rs @@ -24,10 +24,10 @@ pub struct Metrics { check_tx_removed_expired: Counter, check_tx_removed_failed_execution: Counter, check_tx_removed_failed_stateless: Counter, - check_tx_removed_stale_nonce: Counter, check_tx_duration_seconds_parse_tx: Histogram, check_tx_duration_seconds_check_stateless: Histogram, - check_tx_duration_seconds_check_nonce: Histogram, + check_tx_duration_seconds_fetch_nonce: Histogram, + check_tx_duration_seconds_check_tracked: Histogram, check_tx_duration_seconds_check_chain_id: Histogram, check_tx_duration_seconds_check_removed: Histogram, check_tx_duration_seconds_convert_address: Histogram, @@ -88,10 +88,6 @@ impl Metrics { self.check_tx_removed_failed_stateless.increment(1); } - pub(crate) fn increment_check_tx_removed_stale_nonce(&self) { - self.check_tx_removed_stale_nonce.increment(1); - } - pub(crate) fn record_check_tx_duration_seconds_parse_tx(&self, duration: Duration) { self.check_tx_duration_seconds_parse_tx.record(duration); } @@ -101,8 +97,13 @@ impl Metrics { .record(duration); } - pub(crate) fn record_check_tx_duration_seconds_check_nonce(&self, duration: Duration) { - self.check_tx_duration_seconds_check_nonce.record(duration); + pub(crate) fn record_check_tx_duration_seconds_fetch_nonce(&self, duration: Duration) { + self.check_tx_duration_seconds_fetch_nonce.record(duration); + } + + pub(crate) fn record_check_tx_duration_seconds_check_tracked(&self, duration: Duration) { + self.check_tx_duration_seconds_check_tracked + .record(duration); } pub(crate) fn record_check_tx_duration_seconds_check_chain_id(&self, duration: Duration) { @@ -260,6 +261,21 @@ impl telemetry::Metrics for Metrics { )? .register()?; + let check_tx_duration_seconds_fetch_nonce = builder + .new_histogram_factory( + CHECK_TX_DURATION_SECONDS_FETCH_NONCE, + "The amount of time taken in seconds to fetch an account's nonce", + )? + .register()?; + + let check_tx_duration_seconds_check_tracked = builder + .new_histogram_factory( + CHECK_TX_DURATION_SECONDS_CHECK_TRACKED, + "The amount of time taken in seconds to check if the transaction is already in \ + the mempool", + )? + .register()?; + let check_tx_removed_failed_stateless = builder .new_counter_factory( CHECK_TX_REMOVED_FAILED_STATELESS, @@ -267,15 +283,6 @@ impl telemetry::Metrics for Metrics { failing the stateless check", )? .register()?; - - let check_tx_removed_stale_nonce = builder - .new_counter_factory( - CHECK_TX_REMOVED_STALE_NONCE, - "The number of transactions that have been removed from the mempool due to having \ - a stale nonce", - )? - .register()?; - let mut check_tx_duration_factory = builder.new_histogram_factory( CHECK_TX_DURATION_SECONDS, "The amount of time taken in seconds to successfully complete the various stages of \ @@ -286,8 +293,6 @@ impl telemetry::Metrics for Metrics { )?; let check_tx_duration_seconds_check_stateless = check_tx_duration_factory .register_with_labels(&[(CHECK_TX_STAGE, "stateless check".to_string())])?; - let check_tx_duration_seconds_check_nonce = check_tx_duration_factory - .register_with_labels(&[(CHECK_TX_STAGE, "nonce check".to_string())])?; let check_tx_duration_seconds_check_chain_id = check_tx_duration_factory .register_with_labels(&[(CHECK_TX_STAGE, "chain id check".to_string())])?; let check_tx_duration_seconds_check_removed = check_tx_duration_factory @@ -335,10 +340,10 @@ impl telemetry::Metrics for Metrics { check_tx_removed_expired, check_tx_removed_failed_execution, check_tx_removed_failed_stateless, - check_tx_removed_stale_nonce, check_tx_duration_seconds_parse_tx, check_tx_duration_seconds_check_stateless, - check_tx_duration_seconds_check_nonce, + check_tx_duration_seconds_fetch_nonce, + check_tx_duration_seconds_check_tracked, check_tx_duration_seconds_check_chain_id, check_tx_duration_seconds_check_removed, check_tx_duration_seconds_convert_address, @@ -365,12 +370,13 @@ metric_names!(const METRICS_NAMES: CHECK_TX_REMOVED_EXPIRED, CHECK_TX_REMOVED_FAILED_EXECUTION, CHECK_TX_REMOVED_FAILED_STATELESS, - CHECK_TX_REMOVED_STALE_NONCE, CHECK_TX_REMOVED_ACCOUNT_BALANCE, CHECK_TX_DURATION_SECONDS, CHECK_TX_DURATION_SECONDS_CONVERT_ADDRESS, CHECK_TX_DURATION_SECONDS_FETCH_BALANCES, + CHECK_TX_DURATION_SECONDS_FETCH_NONCE, CHECK_TX_DURATION_SECONDS_FETCH_TX_COST, + CHECK_TX_DURATION_SECONDS_CHECK_TRACKED, ACTIONS_PER_TRANSACTION_IN_MEMPOOL, TRANSACTION_IN_MEMPOOL_SIZE_BYTES, TRANSACTIONS_IN_MEMPOOL_TOTAL, @@ -386,7 +392,6 @@ mod tests { CHECK_TX_REMOVED_EXPIRED, CHECK_TX_REMOVED_FAILED_EXECUTION, CHECK_TX_REMOVED_FAILED_STATELESS, - CHECK_TX_REMOVED_STALE_NONCE, CHECK_TX_REMOVED_TOO_LARGE, MEMPOOL_RECOSTED, PREPARE_PROPOSAL_EXCLUDED_TRANSACTIONS, @@ -442,7 +447,6 @@ mod tests { CHECK_TX_REMOVED_FAILED_STATELESS, "check_tx_removed_failed_stateless", ); - assert_const(CHECK_TX_REMOVED_STALE_NONCE, "check_tx_removed_stale_nonce"); assert_const( CHECK_TX_REMOVED_ACCOUNT_BALANCE, "check_tx_removed_account_balance", diff --git a/crates/astria-sequencer/src/service/mempool.rs b/crates/astria-sequencer/src/service/mempool/mod.rs similarity index 67% rename from crates/astria-sequencer/src/service/mempool.rs rename to crates/astria-sequencer/src/service/mempool/mod.rs index 9dc31e0f02..c8dc9247bc 100644 --- a/crates/astria-sequencer/src/service/mempool.rs +++ b/crates/astria-sequencer/src/service/mempool/mod.rs @@ -1,3 +1,6 @@ +#[cfg(test)] +mod tests; + use std::{ collections::HashMap, pin::Pin, @@ -18,6 +21,7 @@ use astria_core::{ }, }; use astria_eyre::eyre::WrapErr as _; +use bytes::Bytes; use cnidarium::Storage; use futures::{ Future, @@ -106,11 +110,14 @@ impl Service for Mempool { /// Handles a [`request::CheckTx`] request. /// -/// Performs stateless checks (decoding and signature check), -/// as well as stateful checks (nonce and balance checks). +/// This function will error if: +/// - the transaction has been removed from the app's mempool (will throw error once) +/// - the transaction fails stateless checks +/// - the transaction fails insertion into the mempool /// -/// If the tx passes all checks, status code 0 is returned. -#[allow(clippy::too_many_lines)] +/// The function will return a [`response::CheckTx`] with a status code of 0 if the transaction: +/// - Is already in the appside mempool +/// - Passes stateless checks and insertion into the mempool is successful #[instrument(skip_all)] async fn handle_check_tx( req: request::CheckTx, @@ -120,18 +127,149 @@ async fn handle_check_tx response::CheckTx { use sha2::Digest as _; - let start_parsing = Instant::now(); - let request::CheckTx { tx, .. } = req; let tx_hash = sha2::Sha256::digest(&tx).into(); + + // check if the transaction has been removed from the appside mempool + if let Some(rsp) = check_removed_comet_bft(tx_hash, mempool, metrics).await { + return rsp; + } + + // check if the transaction is already in the mempool + if let Some(rsp) = check_tracked(tx_hash, mempool, metrics).await { + return rsp; + } + + // perform stateless checks + let signed_tx = match stateless_checks(tx, &state, metrics).await { + StatelessReturn::Response(rsp) => return rsp, + StatelessReturn::Tx(signed_tx) => signed_tx, + }; + + // attempt to insert the transaction into the mempool + if let Some(rsp) = insert_into_mempool(mempool, &state, signed_tx, metrics).await { + return rsp; + } + + // insertion successful + metrics.set_transactions_in_mempool_total(mempool.len().await); + + response::CheckTx::default() +} + +/// Checks if the transaction is already in the mempool. +/// +/// Returns a [`response::CheckTx`] with a status code of 0 if the transaction is already in the +/// mempool. +async fn check_tracked( + tx_hash: [u8; 32], + mempool: &AppMempool, + metrics: &Metrics, +) -> Option { + let start_tracked_check = Instant::now(); + + // check if the transaction is in the mempool already, return early if it is + if mempool.tracked(tx_hash).await { + return Some(response::CheckTx::default()); + } + + let finished_check_tracked = Instant::now(); + metrics.record_check_tx_duration_seconds_check_tracked( + finished_check_tracked.saturating_duration_since(start_tracked_check), + ); + + None +} + +/// Checks if the transaction has been removed from the appside mempool. +/// +/// Returns a [`response::CheckTx`] with an error code and message if the transaction has been +/// removed from the appside mempool. +async fn check_removed_comet_bft( + tx_hash: [u8; 32], + mempool: &AppMempool, + metrics: &Metrics, +) -> Option { + let start_removal_check = Instant::now(); + + // check if the transaction has been removed from the appside mempool + if let Some(removal_reason) = mempool.check_removed_comet_bft(tx_hash).await { + match removal_reason { + RemovalReason::Expired => { + metrics.increment_check_tx_removed_expired(); + return Some(response::CheckTx { + code: Code::Err(AbciErrorCode::TRANSACTION_EXPIRED.value()), + info: "transaction expired in app's mempool".into(), + log: "Transaction expired in the app's mempool".into(), + ..response::CheckTx::default() + }); + } + RemovalReason::FailedPrepareProposal(err) => { + metrics.increment_check_tx_removed_failed_execution(); + return Some(response::CheckTx { + code: Code::Err(AbciErrorCode::TRANSACTION_FAILED.value()), + info: "transaction failed execution in prepare_proposal()".into(), + log: format!("transaction failed execution because: {err}"), + ..response::CheckTx::default() + }); + } + RemovalReason::NonceStale => { + return Some(response::CheckTx { + code: Code::Err(AbciErrorCode::INVALID_NONCE.value()), + info: "transaction removed from app mempool due to stale nonce".into(), + log: "Transaction from app mempool due to stale nonce".into(), + ..response::CheckTx::default() + }); + } + RemovalReason::LowerNonceInvalidated => { + return Some(response::CheckTx { + code: Code::Err(AbciErrorCode::LOWER_NONCE_INVALIDATED.value()), + info: "transaction removed from app mempool due to lower nonce being \ + invalidated" + .into(), + log: "Transaction removed from app mempool due to lower nonce being \ + invalidated" + .into(), + ..response::CheckTx::default() + }); + } + } + }; + + let finished_removal_check = Instant::now(); + metrics.record_check_tx_duration_seconds_check_removed( + finished_removal_check.saturating_duration_since(start_removal_check), + ); + + None +} + +/// Stateless checks return a [`response::CheckTx`] if the transaction fails any of the checks. +/// Otherwise, it returns the [`SignedTransaction`] to be inserted into the mempool. +enum StatelessReturn { + Response(response::CheckTx), + Tx(SignedTransaction), +} + +/// Performs stateless checks on the transaction. +/// +/// Returns a [`response::CheckTx`] if the transaction fails any of the checks. +/// Otherwise, it returns the [`SignedTransaction`] to be inserted into the mempool. +async fn stateless_checks( + tx: Bytes, + state: &S, + metrics: &'static Metrics, +) -> StatelessReturn { + let start_parsing = Instant::now(); + let tx_len = tx.len(); if tx_len > MAX_TX_SIZE { metrics.increment_check_tx_removed_too_large(); - return response::CheckTx { + return StatelessReturn::Response(response::CheckTx { code: Code::Err(AbciErrorCode::TRANSACTION_TOO_LARGE.value()), log: format!( "transaction size too large; allowed: {MAX_TX_SIZE} bytes, got {}", @@ -139,31 +277,31 @@ async fn handle_check_tx tx, Err(e) => { - return response::CheckTx { + return StatelessReturn::Response(response::CheckTx { code: Code::Err(AbciErrorCode::INVALID_PARAMETER.value()), log: e.to_string(), info: "failed decoding bytes as a protobuf SignedTransaction".into(), ..response::CheckTx::default() - }; + }); } }; let signed_tx = match SignedTransaction::try_from_raw(raw_signed_tx) { Ok(tx) => tx, Err(e) => { - return response::CheckTx { + return StatelessReturn::Response(response::CheckTx { code: Code::Err(AbciErrorCode::INVALID_PARAMETER.value()), info: "the provided bytes was not a valid protobuf-encoded SignedTransaction, or \ the signature was invalid" .into(), log: e.to_string(), ..response::CheckTx::default() - }; + }); } }; @@ -174,12 +312,12 @@ async fn handle_check_tx { - metrics.increment_check_tx_removed_expired(); - return response::CheckTx { - code: Code::Err(AbciErrorCode::TRANSACTION_EXPIRED.value()), - info: "transaction expired in app's mempool".into(), - log: "Transaction expired in the app's mempool".into(), - ..response::CheckTx::default() - }; - } - RemovalReason::FailedPrepareProposal(err) => { - metrics.increment_check_tx_removed_failed_execution(); - return response::CheckTx { - code: Code::Err(AbciErrorCode::TRANSACTION_FAILED.value()), - info: "transaction failed execution in prepare_proposal()".into(), - log: format!("transaction failed execution because: {err}"), - ..response::CheckTx::default() - }; - } - RemovalReason::NonceStale => { - return response::CheckTx { - code: Code::Err(AbciErrorCode::INVALID_NONCE.value()), - info: "transaction removed from app mempool due to stale nonce".into(), - log: "Transaction from app mempool due to stale nonce".into(), - ..response::CheckTx::default() - }; - } - RemovalReason::LowerNonceInvalidated => { - return response::CheckTx { - code: Code::Err(AbciErrorCode::LOWER_NONCE_INVALIDATED.value()), - info: "transaction removed from app mempool due to lower nonce being \ - invalidated" - .into(), - log: "Transaction removed from app mempool due to lower nonce being \ - invalidated" - .into(), - ..response::CheckTx::default() - }; - } - } - }; + // note: decide if worth moving to post-insertion, would have to recalculate cost + metrics.record_transaction_in_mempool_size_bytes(tx_len); - let finished_check_removed = Instant::now(); - metrics.record_check_tx_duration_seconds_check_removed( - finished_check_removed.saturating_duration_since(finished_check_chain_id), - ); + StatelessReturn::Tx(signed_tx) +} + +/// Attempts to insert the transaction into the mempool. +/// +/// Returns a [`response::CheckTx`] with an error code and message if the transaction fails to be +/// inserted into the mempool. +async fn insert_into_mempool( + mempool: &AppMempool, + state: &S, + signed_tx: SignedTransaction, + metrics: &'static Metrics, +) -> Option { + let start_convert_address = Instant::now(); - // tx is valid, push to mempool with current state let address = match state .try_base_prefixed(&signed_tx.verification_key().address_bytes()) .await .context("failed to generate address for signed transaction") { Err(err) => { - return response::CheckTx { + return Some(response::CheckTx { code: Code::Err(AbciErrorCode::INTERNAL_ERROR.value()), info: AbciErrorCode::INTERNAL_ERROR.info(), log: format!("failed to generate address because: {err:#}"), ..response::CheckTx::default() - }; + }); } Ok(address) => address, }; - // fetch current account + let finished_convert_address = Instant::now(); + metrics.record_check_tx_duration_seconds_convert_address( + finished_convert_address.saturating_duration_since(start_convert_address), + ); + + // fetch current account nonce let current_account_nonce = match state .get_account_nonce(address) .await .wrap_err("failed fetching nonce for account") { Err(err) => { - return response::CheckTx { + return Some(response::CheckTx { code: Code::Err(AbciErrorCode::INTERNAL_ERROR.value()), info: AbciErrorCode::INTERNAL_ERROR.info(), log: format!("failed to fetch account nonce because: {err:#}"), ..response::CheckTx::default() - }; + }); } Ok(nonce) => nonce, }; - let finished_convert_address = Instant::now(); - metrics.record_check_tx_duration_seconds_convert_address( - finished_convert_address.saturating_duration_since(finished_check_removed), + let finished_fetch_nonce = Instant::now(); + metrics.record_check_tx_duration_seconds_fetch_nonce( + finished_fetch_nonce.saturating_duration_since(finished_convert_address), ); // grab cost of transaction @@ -309,19 +406,19 @@ async fn handle_check_tx { - return response::CheckTx { + return Some(response::CheckTx { code: Code::Err(AbciErrorCode::INTERNAL_ERROR.value()), info: AbciErrorCode::INTERNAL_ERROR.info(), log: format!("failed to fetch cost of the transaction because: {err:#}"), ..response::CheckTx::default() - }; + }); } Ok(transaction_cost) => transaction_cost, }; let finished_fetch_tx_cost = Instant::now(); metrics.record_check_tx_duration_seconds_fetch_tx_cost( - finished_fetch_tx_cost.saturating_duration_since(finished_convert_address), + finished_fetch_tx_cost.saturating_duration_since(finished_fetch_nonce), ); // grab current account's balances @@ -331,12 +428,12 @@ async fn handle_check_tx { - return response::CheckTx { + return Some(response::CheckTx { code: Code::Err(AbciErrorCode::INTERNAL_ERROR.value()), info: AbciErrorCode::INTERNAL_ERROR.info(), log: format!("failed to fetch account balances because: {err:#}"), ..response::CheckTx::default() - }; + }); } Ok(account_balance) => account_balance, }; @@ -357,21 +454,17 @@ async fn handle_check_tx( - tx: &SignedTransaction, - state: &S, -) -> Result<()> { - let signer_address = state - .try_base_prefixed(&tx.verification_key().address_bytes()) - .await - .wrap_err( - "failed constructing the signer address from signed transaction verification and \ - prefix provided by app state", - )?; - let curr_nonce = state - .get_account_nonce(signer_address) - .await - .wrap_err("failed to get account nonce")?; - ensure!(tx.nonce() >= curr_nonce, "nonce already used by account"); - Ok(()) -} - #[instrument(skip_all)] pub(crate) async fn check_chain_id_mempool( tx: &SignedTransaction, diff --git a/crates/astria-sequencer/src/transaction/mod.rs b/crates/astria-sequencer/src/transaction/mod.rs index f0997f6738..e4bfad76cf 100644 --- a/crates/astria-sequencer/src/transaction/mod.rs +++ b/crates/astria-sequencer/src/transaction/mod.rs @@ -20,7 +20,6 @@ use astria_eyre::{ pub(crate) use checks::{ check_balance_for_total_fees_and_transfers, check_chain_id_mempool, - check_nonce_mempool, get_total_transaction_cost, }; use cnidarium::StateWrite;