From 954d8571df26b5cc0d342c6c05d414bd6912f1cd Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Wed, 19 Mar 2025 17:20:25 +0100 Subject: [PATCH] `fatxpool`: report_invalid: do not ban Future/Stale txs from re-entering the view (#7777) #### Description Avoid banning future/stale transactions reported as invalid by the authorship module. #### Note for reviewers When re-org is handled by transaction pool, the view for new fork (`Bn'`) is cloned from the tip of the other existing fork (`Bn`). The new view is not entirely re-validated during the maintain process (it will be revalidated in the background), so it may happen that it contains transactions that are ready on (`Bn`) but actually are not ready on (`Bn'`). All required (which are expected to be in retracted set) transactions are submitted to the new view, but order of txs in ready iterator is not updated. The proper fix would require to re-build the the iterator - which is not trivial as we do not have tags for transactions for block `Bn'` yet. We could force retracted txs to be before ready transactions but it also does not feel to be a good solution - it still would be best effort trial. For now allowing future transactions to re-enter the view immediately is in my opinion a good compromise. This PR is a quick fix and actually brings back behavior of txpool from before merging #6008. The bad thing is that incorrect transactions are detected during block authorship, but this situation to happen requires some specific pre-conditions which should be rare. If this PR is not merged, some transaction will get included into blocks, only after [`DEFAULT_BAN_TIME_SECS`](https://github.com/paritytech/polkadot-sdk/blob/4b39ff00b887039bc3e02a763a29deb09df56833/substrate/client/transaction-pool/src/graph/rotator.rs#L37), which is pretty bad. --------- Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- prdoc/pr_7777.prdoc | 10 ++++++++++ .../transaction-pool/src/common/tracing_log_xt.rs | 13 +++++++++++++ .../src/fork_aware_txpool/fork_aware_txpool.rs | 4 ++-- .../transaction-pool/src/fork_aware_txpool/view.rs | 5 ++++- .../src/fork_aware_txpool/view_store.rs | 9 ++++----- .../transaction-pool/src/graph/validated_pool.rs | 13 ++++++++----- 6 files changed, 41 insertions(+), 13 deletions(-) create mode 100644 prdoc/pr_7777.prdoc diff --git a/prdoc/pr_7777.prdoc b/prdoc/pr_7777.prdoc new file mode 100644 index 00000000000..e62abf1bf6f --- /dev/null +++ b/prdoc/pr_7777.prdoc @@ -0,0 +1,10 @@ +title: '`fatxpool`: report_invalid: do not ban Future/Stale txs from re-entering the + view' +doc: +- audience: Node Dev + description: |- + Avoid banning future/stale transactions reported as invalid by the authorship module. + +crates: +- name: sc-transaction-pool + bump: minor diff --git a/substrate/client/transaction-pool/src/common/tracing_log_xt.rs b/substrate/client/transaction-pool/src/common/tracing_log_xt.rs index 9be9cddd978..ef13642bbb9 100644 --- a/substrate/client/transaction-pool/src/common/tracing_log_xt.rs +++ b/substrate/client/transaction-pool/src/common/tracing_log_xt.rs @@ -53,6 +53,18 @@ macro_rules! log_xt { } }; } +macro_rules! log_xt_debug { + (data: $datatype:ident, target: $target:expr, $($arg:tt)+) => { + $crate::common::tracing_log_xt::log_xt!(data: $datatype, target: $target, tracing::Level::DEBUG, $($arg)+); + }; + (target: $target:expr, $tx_collection:expr, $text_with_format:expr) => { + $crate::common::tracing_log_xt::log_xt!(data: hash, target: $target, tracing::Level::DEBUG, $tx_collection, $text_with_format); + }; + (target: $target:expr, $tx_collection:expr, $text_with_format:expr, $($arg:expr)*) => { + $crate::common::tracing_log_xt::log_xt!(data: hash, target: $target, tracing::Level::DEBUG, $tx_collection, $text_with_format, $($arg)*); + }; +} + macro_rules! log_xt_trace { (data: $datatype:ident, target: $target:expr, $($arg:tt)+) => { $crate::common::tracing_log_xt::log_xt!(data: $datatype, target: $target, tracing::Level::TRACE, $($arg)+); @@ -66,4 +78,5 @@ macro_rules! log_xt_trace { } pub(crate) use log_xt; +pub(crate) use log_xt_debug; pub(crate) use log_xt_trace; diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/fork_aware_txpool.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/fork_aware_txpool.rs index 337da1dd61d..855e9b55e85 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/fork_aware_txpool.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/fork_aware_txpool.rs @@ -29,7 +29,7 @@ use super::{ }; use crate::{ api::FullChainApi, - common::tracing_log_xt::log_xt_trace, + common::tracing_log_xt::{log_xt_debug, log_xt_trace}, enactment_state::{EnactmentAction, EnactmentState}, fork_aware_txpool::{ dropped_watcher::{DroppedReason, DroppedTransaction}, @@ -891,7 +891,7 @@ where invalid_tx_errors: TxInvalidityReportMap<TxHash<Self>>, ) -> Vec<Arc<Self::InPoolTransaction>> { debug!(target: LOG_TARGET, len = ?invalid_tx_errors.len(), "fatp::report_invalid"); - log_xt_trace!(data: tuple, target:LOG_TARGET, invalid_tx_errors.iter(), "fatp::report_invalid {:?}"); + log_xt_debug!(data: tuple, target:LOG_TARGET, invalid_tx_errors.iter(), "fatp::report_invalid {:?}"); self.metrics .report(|metrics| metrics.reported_invalid_txs.inc_by(invalid_tx_errors.len() as _)); diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/view.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/view.rs index d496b24fb4e..5c025b22573 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/view.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/view.rs @@ -656,6 +656,7 @@ where pub fn remove_subtree<F>( &self, hashes: &[ExtrinsicHash<ChainApi>], + ban_transactions: bool, listener_action: F, ) -> Vec<TransactionFor<ChainApi>> where @@ -664,6 +665,8 @@ where ExtrinsicHash<ChainApi>, ), { - self.pool.validated_pool().remove_subtree(hashes, listener_action) + self.pool + .validated_pool() + .remove_subtree(hashes, ban_transactions, listener_action) } } diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/view_store.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/view_store.rs index f61e300f364..3ee81d88b9c 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/view_store.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/view_store.rs @@ -647,8 +647,7 @@ where /// /// Invalid future and stale transaction will be removed only from given `at` view, and will be /// kept in the view_store. Such transaction will not be reported in returned vector. They - /// also will not be banned from re-entering the pool (however can be rejected from re-entring - /// the view). No event will be triggered. + /// also will not be banned from re-entering the pool. No event will be triggered. /// /// For other errors, the transaction will be removed from the view_store, and it will be /// included in the returned vector. Additionally, transactions provided as input will be banned @@ -685,7 +684,7 @@ where // be in the pool. at.map(|at| { self.get_view_at(at, true) - .map(|(view, _)| view.remove_subtree(&remove_from_view, |_, _| {})) + .map(|(view, _)| view.remove_subtree(&remove_from_view, false, |_, _| {})) }); let mut removed = vec![]; @@ -757,7 +756,7 @@ where )); }, PreInsertAction::RemoveSubtree(ref removal) => { - view.remove_subtree(&[removal.xt_hash], &*removal.listener_action); + view.remove_subtree(&[removal.xt_hash], true, &*removal.listener_action); }, } } @@ -862,7 +861,7 @@ where .iter() .chain(self.inactive_views.read().iter()) .filter(|(_, view)| view.is_imported(&xt_hash)) - .flat_map(|(_, view)| view.remove_subtree(&[xt_hash], &listener_action)) + .flat_map(|(_, view)| view.remove_subtree(&[xt_hash], true, &listener_action)) .filter_map(|xt| seen.insert(xt.hash).then(|| xt.clone())) .collect(); diff --git a/substrate/client/transaction-pool/src/graph/validated_pool.rs b/substrate/client/transaction-pool/src/graph/validated_pool.rs index 8eb967421bd..281dc32e57c 100644 --- a/substrate/client/transaction-pool/src/graph/validated_pool.rs +++ b/substrate/client/transaction-pool/src/graph/validated_pool.rs @@ -734,7 +734,7 @@ impl<B: ChainApi, L: EventHandler<B>> ValidatedPool<B, L> { return vec![] } - let invalid = self.remove_subtree(hashes, |listener, removed_tx_hash| { + let invalid = self.remove_subtree(hashes, true, |listener, removed_tx_hash| { listener.invalid(&removed_tx_hash); }); @@ -795,8 +795,8 @@ impl<B: ChainApi, L: EventHandler<B>> ValidatedPool<B, L> { /// This function traverses the dependency graph of transactions and removes the specified /// transaction along with all its descendant transactions from the pool. /// - /// The root transaction will be banned from re-entrering the pool. Descendant transactions may - /// be re-submitted to the pool if required. + /// The root transactions will be banned from re-entrering the pool if `ban_transactions` is + /// true. Descendant transactions may be re-submitted to the pool if required. /// /// A `event_disaptcher_action` callback function is invoked for every transaction that is /// removed, providing a reference to the pool's event dispatcher and the hash of the removed @@ -807,13 +807,16 @@ impl<B: ChainApi, L: EventHandler<B>> ValidatedPool<B, L> { pub fn remove_subtree<F>( &self, hashes: &[ExtrinsicHash<B>], + ban_transactions: bool, event_dispatcher_action: F, ) -> Vec<TransactionFor<B>> where F: Fn(&mut EventDispatcher<B, L>, ExtrinsicHash<B>), { - // temporarily ban removed transactions - self.rotator.ban(&Instant::now(), hashes.iter().cloned()); + // temporarily ban removed transactions if requested + if ban_transactions { + self.rotator.ban(&Instant::now(), hashes.iter().cloned()); + }; let removed = self.pool.write().remove_subtree(hashes); removed -- GitLab