From 04af09bf9e37a2d2884f24f18c57122539481d31 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Tue, 26 Nov 2024 19:12:58 +0100 Subject: [PATCH 01/60] txpool: API update: remove_invalid -> report_invalid --- .../client/transaction-pool/api/src/lib.rs | 20 ++++++- .../fork_aware_txpool/fork_aware_txpool.rs | 55 +++++++++++++++---- .../src/fork_aware_txpool/metrics.rs | 11 +++- .../src/fork_aware_txpool/view.rs | 11 +++- .../src/fork_aware_txpool/view_store.rs | 49 ++++++++++++++++- .../single_state_txpool.rs | 9 ++- .../src/transaction_pool_wrapper.rs | 8 ++- 7 files changed, 140 insertions(+), 23 deletions(-) diff --git a/substrate/client/transaction-pool/api/src/lib.rs b/substrate/client/transaction-pool/api/src/lib.rs index 6f771e9479bd4..f0edf136c2fce 100644 --- a/substrate/client/transaction-pool/api/src/lib.rs +++ b/substrate/client/transaction-pool/api/src/lib.rs @@ -290,8 +290,24 @@ pub trait TransactionPool: Send + Sync { fn ready(&self) -> Box> + Send>; // *** Block production - /// Remove transactions identified by given hashes (and dependent transactions) from the pool. - fn remove_invalid(&self, hashes: &[TxHash]) -> Vec>; + /// Reports invalid transactions to the transaction pool. + /// + /// This function accepts an array of tuples, each containing a transaction hash and an + /// optional error encountered during the transaction execution at a specific (also optional) + /// block. + /// + /// The transaction pool implementation decides which transactions to remove. Transactions + /// dependent on invalid ones will also be removed. + /// + /// If the tuple's error is None, the transaction will be forcibly removed from the pool. + /// + /// The optional `at` parameter provides additional context regarding the block where the error + /// occurred. + fn report_invalid( + &self, + at: Option<::Hash>, + invalid_tx_errors: &[(TxHash, Option)], + ) -> Vec>; // *** logging /// Get futures transaction list. 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 065d0cb3a274f..9929ef524c6f6 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 @@ -675,18 +675,49 @@ where .inspect_err(|_| mempool.remove(xt_hash)) } - /// Intended to remove transactions identified by the given hashes, and any dependent - /// transactions, from the pool. In current implementation this function only outputs the error. - /// Seems that API change is needed here to make this call reasonable. - // todo [#5491]: api change? we need block hash here (assuming we need it at all - could be - // useful for verification for debugging purposes). - fn remove_invalid(&self, hashes: &[TxHash]) -> Vec> { - if !hashes.is_empty() { - log::debug!(target: LOG_TARGET, "fatp::remove_invalid {}", hashes.len()); - log_xt_trace!(target:LOG_TARGET, hashes, "[{:?}] fatp::remove_invalid"); - self.metrics - .report(|metrics| metrics.removed_invalid_txs.inc_by(hashes.len() as _)); - } + // /// Intended to remove transactions identified by the given hashes, and any dependent + // /// transactions, from the pool. In current implementation this function only outputs the + // error. /// Seems that API change is needed here to make this call reasonable. + // // todo [#5491]: api change? we need block hash here (assuming we need it at all - could be + // // useful for verification for debugging purposes). + // fn remove_invalid(&self, hashes: &[TxHash]) -> Vec> { + // if !hashes.is_empty() { + // log::debug!(target: LOG_TARGET, "fatp::remove_invalid {}", hashes.len()); + // log_xt_trace!(target:LOG_TARGET, hashes, "[{:?}] fatp::remove_invalid"); + // self.metrics + // .report(|metrics| metrics.removed_invalid_txs.inc_by(hashes.len() as _)); + // } + // Default::default() + // } + + /// Reports invalid transactions to the transaction pool. + /// + /// This function takes an array of tuples, each consisting of a transaction hash and the + /// corresponding error that occurred during transaction execution at given block. + /// + /// The transaction pool implementation will determine which transactions should be + /// removed from the pool. Transactions that depend on invalid transactions will also + /// be removed. + fn report_invalid( + &self, + at: Option<::Hash>, + invalid_tx_errors: &[(TxHash, Option)], + ) -> Vec> { + self.metrics + .report(|metrics| metrics.reported_invalid_txs.inc_by(invalid_tx_errors.len() as _)); + + let removed = self.view_store.report_invalid(at, invalid_tx_errors); + + self.metrics + .report(|metrics| metrics.removed_invalid_txs.inc_by(removed.len() as _)); + + // todo (after merging / rebasing) + + // depending on error: + // - handle cloned view with view_store replacements + // - remove resulting hashes from mempool and collect Arc + // - send notification using listener (should this be done in view_store) + Default::default() } diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/metrics.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/metrics.rs index 73d45ac430519..6b8d579c693d1 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/metrics.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/metrics.rs @@ -40,6 +40,8 @@ pub struct Metrics { /// Total number of unwatched transactions in txpool. pub unwatched_txs: Gauge, /// Total number of transactions reported as invalid. + pub reported_invalid_txs: Counter, + /// Total number of transactions removed as invalid. pub removed_invalid_txs: Counter, /// Total number of finalized transactions. pub finalized_txs: Counter, @@ -99,10 +101,17 @@ impl MetricsRegistrant for Metrics { )?, registry, )?, + reported_invalid_txs: register( + Counter::new( + "substrate_sub_txpool_reported_invalid_txs_total", + "Total number of transactions reported as invalid.", + )?, + registry, + )?, removed_invalid_txs: register( Counter::new( "substrate_sub_txpool_removed_invalid_txs_total", - "Total number of transactions reported as invalid.", + "Total number of transactions removed as invalid.", )?, registry, )?, 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 99095d88cb0ac..297a6ebb53197 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/view.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/view.rs @@ -27,8 +27,8 @@ use super::metrics::MetricsLink as PrometheusMetrics; use crate::{ common::log_xt::log_xt_trace, graph::{ - self, watcher::Watcher, ExtrinsicFor, ExtrinsicHash, IsValidator, ValidatedTransaction, - ValidatedTransactionFor, + self, watcher::Watcher, ExtrinsicFor, ExtrinsicHash, IsValidator, TransactionFor, + ValidatedTransaction, ValidatedTransactionFor, }, LOG_TARGET, }; @@ -455,4 +455,11 @@ where ); } } + + pub(crate) fn remove_invalid( + &self, + hashes: &[ExtrinsicHash], + ) -> Vec> { + self.pool.validated_pool().remove_invalid(hashes) + } } 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 f23dcedd5bfd1..5257081dcbac1 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 @@ -32,8 +32,12 @@ use futures::prelude::*; use itertools::Itertools; use parking_lot::RwLock; use sc_transaction_pool_api::{error::Error as PoolError, PoolStatus, TransactionSource}; -use sp_blockchain::TreeRoute; -use sp_runtime::{generic::BlockId, traits::Block as BlockT}; +use sp_blockchain::{ApplyExtrinsicFailed, Error as BlockchainError, TreeRoute}; +use sp_runtime::{ + generic::BlockId, + traits::Block as BlockT, + transaction_validity::{InvalidTransaction, TransactionValidityError}, +}; use std::{collections::HashMap, sync::Arc, time::Instant}; /// The helper structure encapsulates all the views. @@ -484,4 +488,45 @@ where futures::future::join_all(finish_revalidation_futures).await; log::trace!(target:LOG_TARGET,"finish_background_revalidations took {:?}", start.elapsed()); } + + pub(crate) fn report_invalid( + &self, + at: Option, + invalid_tx_errors: &[(ExtrinsicHash, Option)], + ) -> Vec> { + let mut remove_from_view = vec![]; + let mut remove_from_pool = vec![]; + + invalid_tx_errors.iter().for_each(|(hash, e)| match e { + Some(BlockchainError::ApplyExtrinsicFailed(ApplyExtrinsicFailed::Validity( + TransactionValidityError::Invalid( + InvalidTransaction::Future | InvalidTransaction::Stale, + ), + ))) => { + remove_from_view.push(*hash); + }, + _ => { + remove_from_pool.push(*hash); + }, + }); + + at.inspect(|at| { + self.get_view_at(*at, true) + .map(|(view, _)| view.remove_invalid(&remove_from_view[..])) + .unwrap_or_default(); + }); + + //todo: duplicated code - we need to remove subtree from every view + // let active_views = self.active_views.read(); + // let inactive_views = self.inactive_views.read(); + // active_views + // .iter() + // .chain(inactive_views.iter()) + // .filter(|(_, view)| view.is_imported(&xt_hash)) + // .for_each(|(_, view)| { + // view.remove_subtree(xt_hash, replaced_with); + // }); + + remove_from_pool + } } diff --git a/substrate/client/transaction-pool/src/single_state_txpool/single_state_txpool.rs b/substrate/client/transaction-pool/src/single_state_txpool/single_state_txpool.rs index b29630b563bb9..35724a10a6c42 100644 --- a/substrate/client/transaction-pool/src/single_state_txpool/single_state_txpool.rs +++ b/substrate/client/transaction-pool/src/single_state_txpool/single_state_txpool.rs @@ -299,8 +299,13 @@ where Ok(watcher.into_stream().boxed()) } - fn remove_invalid(&self, hashes: &[TxHash]) -> Vec> { - let removed = self.pool.validated_pool().remove_invalid(hashes); + fn report_invalid( + &self, + _at: Option<::Hash>, + invalid_tx_errors: &[(TxHash, Option)], + ) -> Vec> { + let hashes = invalid_tx_errors.iter().map(|(hash, _)| *hash).collect::>(); + let removed = self.pool.validated_pool().remove_invalid(&hashes[..]); self.metrics .report(|metrics| metrics.validations_invalid.inc_by(removed.len() as u64)); removed diff --git a/substrate/client/transaction-pool/src/transaction_pool_wrapper.rs b/substrate/client/transaction-pool/src/transaction_pool_wrapper.rs index e373c0278d804..b10094f7a8b9c 100644 --- a/substrate/client/transaction-pool/src/transaction_pool_wrapper.rs +++ b/substrate/client/transaction-pool/src/transaction_pool_wrapper.rs @@ -107,8 +107,12 @@ where self.0.ready() } - fn remove_invalid(&self, hashes: &[TxHash]) -> Vec> { - self.0.remove_invalid(hashes) + fn report_invalid( + &self, + at: Option<::Hash>, + invalid_tx_errors: &[(TxHash, Option)], + ) -> Vec> { + self.0.report_invalid(at, invalid_tx_errors) } fn futures(&self) -> Vec { From bf2dd1fe6f274e9b7892de59d6127ea6872cc3fb Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Tue, 26 Nov 2024 19:13:36 +0100 Subject: [PATCH 02/60] other modules updated --- substrate/bin/node/bench/Cargo.toml | 1 + substrate/bin/node/bench/src/construct.rs | 6 +++++- .../rpc-spec-v2/src/transaction/tests/middleware_pool.rs | 8 ++++++-- .../rpc-spec-v2/src/transaction/transaction_broadcast.rs | 2 +- substrate/client/rpc/src/author/mod.rs | 6 +++--- 5 files changed, 16 insertions(+), 7 deletions(-) diff --git a/substrate/bin/node/bench/Cargo.toml b/substrate/bin/node/bench/Cargo.toml index 447f947107c1b..f5218397da03c 100644 --- a/substrate/bin/node/bench/Cargo.toml +++ b/substrate/bin/node/bench/Cargo.toml @@ -25,6 +25,7 @@ kitchensink-runtime = { workspace = true } sc-client-api = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-state-machine = { workspace = true, default-features = true } +sp-blockchain = { workspace = true, default-features = true } serde = { workspace = true, default-features = true } serde_json = { workspace = true, default-features = true } derive_more = { features = ["display"], workspace = true } diff --git a/substrate/bin/node/bench/src/construct.rs b/substrate/bin/node/bench/src/construct.rs index 22129c6a1d69d..6e34cb499a8ae 100644 --- a/substrate/bin/node/bench/src/construct.rs +++ b/substrate/bin/node/bench/src/construct.rs @@ -271,7 +271,11 @@ impl sc_transaction_pool_api::TransactionPool for Transactions { unimplemented!() } - fn remove_invalid(&self, _hashes: &[TxHash]) -> Vec> { + fn report_invalid( + &self, + _at: Option, + _invalid_tx_errors: &[(TxHash, Option)], + ) -> Vec> { Default::default() } diff --git a/substrate/client/rpc-spec-v2/src/transaction/tests/middleware_pool.rs b/substrate/client/rpc-spec-v2/src/transaction/tests/middleware_pool.rs index a543969a89b83..124029121253c 100644 --- a/substrate/client/rpc-spec-v2/src/transaction/tests/middleware_pool.rs +++ b/substrate/client/rpc-spec-v2/src/transaction/tests/middleware_pool.rs @@ -137,8 +137,12 @@ impl TransactionPool for MiddlewarePool { Ok(watcher.boxed()) } - fn remove_invalid(&self, hashes: &[TxHash]) -> Vec> { - self.inner_pool.remove_invalid(hashes) + fn report_invalid( + &self, + at: Option<::Hash>, + invalid_tx_errors: &[(TxHash, Option)], + ) -> Vec> { + self.inner_pool.report_invalid(at, invalid_tx_errors) } fn status(&self) -> PoolStatus { diff --git a/substrate/client/rpc-spec-v2/src/transaction/transaction_broadcast.rs b/substrate/client/rpc-spec-v2/src/transaction/transaction_broadcast.rs index 2fd4ce2454565..2e077ef4bfac3 100644 --- a/substrate/client/rpc-spec-v2/src/transaction/transaction_broadcast.rs +++ b/substrate/client/rpc-spec-v2/src/transaction/transaction_broadcast.rs @@ -228,7 +228,7 @@ where } // Best effort pool removal (tx can already be finalized). - pool.remove_invalid(&[broadcast_state.tx_hash]); + pool.report_invalid(None, &[(broadcast_state.tx_hash, None)]); }); // Keep track of this entry and the abortable handle. diff --git a/substrate/client/rpc/src/author/mod.rs b/substrate/client/rpc/src/author/mod.rs index 6afc871e565af..d6bb8863b8e93 100644 --- a/substrate/client/rpc/src/author/mod.rs +++ b/substrate/client/rpc/src/author/mod.rs @@ -164,17 +164,17 @@ where let hashes = bytes_or_hash .into_iter() .map(|x| match x { - hash::ExtrinsicOrHash::Hash(h) => Ok(h), + hash::ExtrinsicOrHash::Hash(h) => Ok((h, None)), hash::ExtrinsicOrHash::Extrinsic(bytes) => { let xt = Decode::decode(&mut &bytes[..])?; - Ok(self.pool.hash_of(&xt)) + Ok((self.pool.hash_of(&xt), None)) }, }) .collect::>>()?; Ok(self .pool - .remove_invalid(&hashes) + .report_invalid(None, &hashes) .into_iter() .map(|tx| tx.hash().clone()) .collect()) From 8c8e0336a2697c24d54cff8675c3de7e0ad5a733 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Tue, 26 Nov 2024 19:14:02 +0100 Subject: [PATCH 03/60] basic-authorship: updated --- substrate/client/basic-authorship/src/basic_authorship.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/substrate/client/basic-authorship/src/basic_authorship.rs b/substrate/client/basic-authorship/src/basic_authorship.rs index 79e6fddae99fc..8e0d21538959b 100644 --- a/substrate/client/basic-authorship/src/basic_authorship.rs +++ b/substrate/client/basic-authorship/src/basic_authorship.rs @@ -512,7 +512,7 @@ where target: LOG_TARGET, "[{:?}] Invalid transaction: {} at: {}", pending_tx_hash, e, self.parent_hash ); - unqueue_invalid.push(pending_tx_hash); + unqueue_invalid.push((pending_tx_hash, Some(e))); }, } }; @@ -524,7 +524,7 @@ where ); } - self.transaction_pool.remove_invalid(&unqueue_invalid); + self.transaction_pool.report_invalid(Some(self.parent_hash), &unqueue_invalid); Ok(end_reason) } From 18ceac8129509505341711e8d672ecb15875fff5 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Tue, 26 Nov 2024 19:28:46 +0100 Subject: [PATCH 04/60] Cargo.lock updated --- Cargo.lock | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.lock b/Cargo.lock index c2d2eb3e96446..d20143ac837c2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11107,6 +11107,7 @@ dependencies = [ "sc-transaction-pool-api", "serde", "serde_json", + "sp-blockchain", "sp-consensus", "sp-core 28.0.0", "sp-inherents 26.0.0", From deb5c9879c2f051706583d997e558fd90bd784b8 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Thu, 28 Nov 2024 09:21:30 +0100 Subject: [PATCH 05/60] sketch --- .../src/fork_aware_txpool/view_store.rs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) 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 5257081dcbac1..521e55ffc4e7b 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 @@ -516,16 +516,13 @@ where .unwrap_or_default(); }); - //todo: duplicated code - we need to remove subtree from every view - // let active_views = self.active_views.read(); - // let inactive_views = self.inactive_views.read(); - // active_views - // .iter() - // .chain(inactive_views.iter()) - // .filter(|(_, view)| view.is_imported(&xt_hash)) - // .for_each(|(_, view)| { - // view.remove_subtree(xt_hash, replaced_with); - // }); + //todo - merge with priorites: + // let removed from_pool = self.view_store.remove_transaction_subtree( + // worst_tx_hash, + // |listener, removed_tx_hash| { &mut Listener<::Hash, …>, ::Hash + // listener.invalid(&removed_tx_hash, &tx_hash); + // }, + // ); remove_from_pool } From 018f9d47531023aba6ec5e57dae32d084ba6d13b Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Tue, 26 Nov 2024 12:41:59 +0100 Subject: [PATCH 06/60] dropped_watcher: new fns are public --- .../src/fork_aware_txpool/dropped_watcher.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/dropped_watcher.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/dropped_watcher.rs index 7679e3b169d2e..05c2d049a62fb 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/dropped_watcher.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/dropped_watcher.rs @@ -53,11 +53,13 @@ pub struct DroppedTransaction { } impl DroppedTransaction { - fn new_usurped(tx_hash: Hash, by: Hash) -> Self { + /// Creates an new instnance with reason set to `DroppedReason::Usurped(by)`. + pub fn new_usurped(tx_hash: Hash, by: Hash) -> Self { Self { reason: DroppedReason::Usurped(by), tx_hash } } - fn new_enforced_by_limts(tx_hash: Hash) -> Self { + /// Creates an new instnance with reason set to `DroppedReason::LimitsEnforced`. + pub fn new_enforced_by_limts(tx_hash: Hash) -> Self { Self { reason: DroppedReason::LimitsEnforced, tx_hash } } } From 4aa24d324f7f1cb79611d02e99eee9d224d2b6c3 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Tue, 26 Nov 2024 12:42:25 +0100 Subject: [PATCH 07/60] dropped_watcher: improvement --- .../transaction-pool/src/fork_aware_txpool/dropped_watcher.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/dropped_watcher.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/dropped_watcher.rs index 05c2d049a62fb..48767ad616050 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/dropped_watcher.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/dropped_watcher.rs @@ -206,6 +206,10 @@ where views ); views.remove(&key); + //todo: merge heads up warning! + if views.is_empty() { + ctx.pending_dropped_transactions.push(*tx_hash); + } }); self.future_transaction_views.iter_mut().for_each(|(tx_hash, views)| { From 9fe88a661c5419bdfce578e3f0e6b97a194236a6 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Tue, 26 Nov 2024 12:53:12 +0100 Subject: [PATCH 08/60] fatp: handling higher prio with full mempool --- .../fork_aware_txpool/fork_aware_txpool.rs | 100 +++++++++++++++--- .../src/fork_aware_txpool/tx_mem_pool.rs | 83 ++++++++++----- .../src/fork_aware_txpool/view.rs | 12 +++ .../src/fork_aware_txpool/view_store.rs | 20 ++++ .../src/graph/validated_pool.rs | 50 +++++++++ 5 files changed, 223 insertions(+), 42 deletions(-) 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 4ec87f1fefa40..fbe960ac0da3c 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 @@ -31,7 +31,10 @@ use crate::{ api::FullChainApi, common::log_xt::log_xt_trace, enactment_state::{EnactmentAction, EnactmentState}, - fork_aware_txpool::{dropped_watcher::DroppedReason, revalidation_worker}, + fork_aware_txpool::{ + dropped_watcher::{DroppedReason, DroppedTransaction}, + revalidation_worker, + }, graph::{ self, base_pool::{TimedTransactionSource, Transaction}, @@ -49,8 +52,9 @@ use futures::{ use parking_lot::Mutex; use prometheus_endpoint::Registry as PrometheusRegistry; use sc_transaction_pool_api::{ - ChainEvent, ImportNotificationStream, MaintainedTransactionPool, PoolStatus, TransactionFor, - TransactionPool, TransactionSource, TransactionStatusStreamFor, TxHash, + error::Error as TxPoolApiError, ChainEvent, ImportNotificationStream, + MaintainedTransactionPool, PoolStatus, TransactionFor, TransactionPool, TransactionSource, + TransactionStatusStreamFor, TxHash, }; use sp_blockchain::{HashAndNumber, TreeRoute}; use sp_core::traits::SpawnEssentialNamed; @@ -287,7 +291,7 @@ where DroppedReason::LimitsEnforced => {}, }; - mempool.remove_dropped_transaction(&dropped_tx_hash).await; + mempool.remove_transaction(&dropped_tx_hash); view_store.listener.transaction_dropped(dropped); import_notification_sink.clean_notified_items(&[dropped_tx_hash]); } @@ -675,9 +679,9 @@ where submission_results .next() .expect("The number of Ok results in mempool is exactly the same as the size of to-views-submission result. qed.") - .inspect_err(|_| - mempool.remove(insertion.hash) - ) + .inspect_err(|_|{ + mempool.remove_transaction(&insertion.hash); + }) }) }) .collect::>()) @@ -712,18 +716,20 @@ where ) -> Result>>, Self::Error> { log::trace!(target: LOG_TARGET, "[{:?}] fatp::submit_and_watch views:{}", self.tx_hash(&xt), self.active_views_count()); let xt = Arc::from(xt); + let InsertionInfo { hash: xt_hash, source: timed_source } = match self.mempool.push_watched(source, xt.clone()) { Ok(result) => result, - Err(e) => return Err(e), + Err(TxPoolApiError::ImmediatelyDropped) => + self.attempt_transaction_replacement(at, source, true, xt.clone()).await?, + Err(e) => return Err(e.into()), }; self.metrics.report(|metrics| metrics.submitted_transactions.inc()); - self.view_store - .submit_and_watch(at, timed_source, xt) - .await - .inspect_err(|_| self.mempool.remove(xt_hash)) + self.view_store.submit_and_watch(at, timed_source, xt).await.inspect_err(|_| { + self.mempool.remove_transaction(&xt_hash); + }) } /// Intended to remove transactions identified by the given hashes, and any dependent @@ -1131,7 +1137,7 @@ where for result in watched_results { if let Err(tx_hash) = result { self.view_store.listener.invalidate_transactions(&[tx_hash]); - self.mempool.remove(tx_hash); + self.mempool.remove_transaction(&tx_hash); } } } @@ -1263,6 +1269,74 @@ where fn tx_hash(&self, xt: &TransactionFor) -> TxHash { self.api.hash_and_length(xt).0 } + + /// Attempts to replace a lower-priority transaction in the transaction pool with a new one. + /// + /// This asynchronous function verifies the new transaction against the most recent view. If a + /// transaction with a lower priority exists in the transaction pool, it is replaced with the + /// new transaction. + /// + /// If no lower-priority transaction is found, the function returns an error indicating the + /// transaction was dropped immediately. + async fn attempt_transaction_replacement( + &self, + _: Block::Hash, + source: TransactionSource, + watched: bool, + xt: ExtrinsicFor, + ) -> Result>, TxPoolApiError> { + // 1. we should validate at most_recent, and reuse result to submit there. + let at = self + .view_store + .most_recent_view + .read() + .ok_or(TxPoolApiError::ImmediatelyDropped)?; + + // 2. validate transaction at `at` + let (best_view, _) = self + .view_store + .get_view_at(at, false) + .ok_or(TxPoolApiError::ImmediatelyDropped)?; + + let (tx_hash, validated_tx) = best_view + .pool + .verify_one( + best_view.at.hash, + best_view.at.number, + TimedTransactionSource::from_transaction_source(source, false), + xt.clone(), + crate::graph::CheckBannedBeforeVerify::Yes, + ) + .await; + + // 3. check if most_recent_view contains a transaction with lower priority (actually worse - + // do we want to check timestamp too - no: see #4609?) + // Would be perfect to choose transaction with lowest the number of dependant txs in its + // subtree. + if let Some(worst_tx_hash) = + best_view.pool.validated_pool().find_transaction_with_lower_prio(validated_tx) + { + // 4. if yes - remove worse transaction from mempool and add new one. + log::trace!(target: LOG_TARGET, "found candidate for removal: {worst_tx_hash:?}"); + let insertion_info = + self.mempool.try_replace_transaction(xt, source, watched, worst_tx_hash); + + // 5. notify listner + self.view_store + .listener + .transaction_dropped(DroppedTransaction::new_usurped(worst_tx_hash, tx_hash)); + + // 7. remove transaction from the view_store + self.view_store.remove_transaction_subtree(worst_tx_hash, tx_hash); + + // 8. add to pending_replacements - make sure it will not sneak back via cloned view + + // 9. subemit new one to the view, this will be done upon in the caller + return insertion_info + } + + Err(TxPoolApiError::ImmediatelyDropped) + } } #[async_trait] diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs index 7b824d4653c2b..4a7c3a22b18fa 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs @@ -101,19 +101,23 @@ where /// Creates a new instance of wrapper for unwatched transaction. fn new_unwatched(source: TransactionSource, tx: ExtrinsicFor, bytes: usize) -> Self { - Self { - watched: false, - tx, - source: TimedTransactionSource::from_transaction_source(source, true), - validated_at: AtomicU64::new(0), - bytes, - } + Self::new(false, source, tx, bytes) } /// Creates a new instance of wrapper for watched transaction. fn new_watched(source: TransactionSource, tx: ExtrinsicFor, bytes: usize) -> Self { + Self::new(true, source, tx, bytes) + } + + /// Creates a new instance of wrapper for a transaction. + fn new( + watched: bool, + source: TransactionSource, + tx: ExtrinsicFor, + bytes: usize, + ) -> Self { Self { - watched: true, + watched, tx, source: TimedTransactionSource::from_transaction_source(source, true), validated_at: AtomicU64::new(0), @@ -279,27 +283,52 @@ where &self, hash: ExtrinsicHash, tx: TxInMemPool, - ) -> Result>, ChainApi::Error> { + tx_to_be_removed: Option>, + ) -> Result>, sc_transaction_pool_api::error::Error> { let bytes = self.transactions.bytes(); let mut transactions = self.transactions.write(); + + tx_to_be_removed.inspect(|hash| { + transactions.remove(hash); + }); + let result = match ( - !self.is_limit_exceeded(transactions.len() + 1, bytes + tx.bytes), + self.is_limit_exceeded(transactions.len() + 1, bytes + tx.bytes), transactions.contains_key(&hash), ) { - (true, false) => { + (false, false) => { let source = tx.source(); transactions.insert(hash, Arc::from(tx)); Ok(InsertionInfo::new(hash, source)) }, (_, true) => - Err(sc_transaction_pool_api::error::Error::AlreadyImported(Box::new(hash)).into()), - (false, _) => Err(sc_transaction_pool_api::error::Error::ImmediatelyDropped.into()), + Err(sc_transaction_pool_api::error::Error::AlreadyImported(Box::new(hash))), + (true, _) => Err(sc_transaction_pool_api::error::Error::ImmediatelyDropped), }; log::trace!(target: LOG_TARGET, "[{:?}] mempool::try_insert: {:?}", hash, result.as_ref().map(|r| r.hash)); result } + /// Attempts to replace an existing transaction in the memory pool with a new one. + /// + /// Returns a `Result` containing `InsertionInfo` if the new transaction is successfully + /// inserted; otherwise, returns an appropriate error indicating the failure. + pub(super) fn try_replace_transaction( + &self, + new_xt: ExtrinsicFor, + source: TransactionSource, + watched: bool, + replaced_tx_hash: ExtrinsicHash, + ) -> Result>, sc_transaction_pool_api::error::Error> { + let (hash, length) = self.api.hash_and_length(&new_xt); + self.try_insert( + hash, + TxInMemPool::new(watched, source, new_xt, length), + Some(replaced_tx_hash), + ) + } + /// Adds a new unwatched transactions to the internal buffer not exceeding the limit. /// /// Returns the vector of results for each transaction, the order corresponds to the input @@ -308,12 +337,13 @@ where &self, source: TransactionSource, xts: &[ExtrinsicFor], - ) -> Vec>, ChainApi::Error>> { + ) -> Vec>, sc_transaction_pool_api::error::Error>> + { let result = xts .iter() .map(|xt| { let (hash, length) = self.api.hash_and_length(&xt); - self.try_insert(hash, TxInMemPool::new_unwatched(source, xt.clone(), length)) + self.try_insert(hash, TxInMemPool::new_unwatched(source, xt.clone(), length), None) }) .collect::>(); result @@ -325,18 +355,9 @@ where &self, source: TransactionSource, xt: ExtrinsicFor, - ) -> Result>, ChainApi::Error> { + ) -> Result>, sc_transaction_pool_api::error::Error> { let (hash, length) = self.api.hash_and_length(&xt); - self.try_insert(hash, TxInMemPool::new_watched(source, xt.clone(), length)) - } - - /// Removes transaction from the memory pool which are specified by the given list of hashes. - pub(super) async fn remove_dropped_transaction( - &self, - dropped: &ExtrinsicHash, - ) -> Option>> { - log::debug!(target: LOG_TARGET, "[{:?}] mempool::remove_dropped_transaction", dropped); - self.transactions.write().remove(dropped) + self.try_insert(hash, TxInMemPool::new_watched(source, xt.clone(), length), None) } /// Clones and returns a `HashMap` of references to all unwatched transactions in the memory @@ -362,9 +383,13 @@ where .collect::>() } - /// Removes a transaction from the memory pool based on a given hash. - pub(super) fn remove(&self, hash: ExtrinsicHash) { - let _ = self.transactions.write().remove(&hash); + /// Removes a transaction with given hash from the memory pool. + pub(super) fn remove_transaction( + &self, + hash: &ExtrinsicHash, + ) -> Option>> { + log::debug!(target: LOG_TARGET, "[{hash:?}] mempool::remove_transaction"); + self.transactions.write().remove(hash) } /// Revalidates a batch of transactions against the provided finalized block. 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 3cbb8fa4871d0..9adb879cb2c7e 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/view.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/view.rs @@ -460,4 +460,16 @@ where const IGNORE_BANNED: bool = false; self.pool.validated_pool().check_is_known(tx_hash, IGNORE_BANNED).is_err() } + + /// Removes the whole transaction subtree from the inner pool. + /// + /// Intended to be called when removal is a result of replacement. Provided `replaced_with` + /// transaction hash is used in emitted _usurped_ event. + pub(super) fn remove_subtree( + &self, + tx_hash: ExtrinsicHash, + replaced_with: ExtrinsicHash, + ) -> Vec> { + self.pool.validated_pool().remove_subtree(tx_hash, replaced_with) + } } 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 a06c051f0a7eb..7687a5699747d 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 @@ -690,4 +690,24 @@ where }; let _results = futures::future::join_all(submit_futures).await; } + + /// Removes the whole transaction subtree from every view within the view store. + /// + /// Intended to be called when removal is a result of replacement. Provided `replaced_with` + /// transaction hash is used in emitted _usurped_ event. + pub(super) fn remove_transaction_subtree( + &self, + xt_hash: ExtrinsicHash, + replaced_with: ExtrinsicHash, + ) { + let active_views = self.active_views.read(); + let inactive_views = self.inactive_views.read(); + active_views + .iter() + .chain(inactive_views.iter()) + .filter(|(_, view)| view.is_imported(&xt_hash)) + .for_each(|(_, view)| { + view.remove_subtree(xt_hash, replaced_with); + }); + } } diff --git a/substrate/client/transaction-pool/src/graph/validated_pool.rs b/substrate/client/transaction-pool/src/graph/validated_pool.rs index 14df63d9673e3..d283ee665c672 100644 --- a/substrate/client/transaction-pool/src/graph/validated_pool.rs +++ b/substrate/client/transaction-pool/src/graph/validated_pool.rs @@ -126,6 +126,8 @@ impl Clone for ValidatedPool { } } +type BaseTransactionFor = base::Transaction, ExtrinsicFor>; + impl ValidatedPool { /// Create a new transaction pool. pub fn new(options: Options, is_validator: IsValidator, api: Arc) -> Self { @@ -686,6 +688,54 @@ impl ValidatedPool { listener.future(&f.hash); }); } + + /// Searches the base pool for a transaction with a lower priority than the given one. + /// + /// Returns the hash of a lower-priority transaction if found, otherwise `None`. + pub fn find_transaction_with_lower_prio( + &self, + tx: ValidatedTransactionFor, + ) -> Option> { + match tx { + ValidatedTransaction::Valid(base::Transaction { priority, .. }) => { + let pool = self.pool.read(); + pool.fold_ready( + Some((priority, Option::>>::None)), + |acc, current| { + let (priority, _) = acc.as_ref().expect("acc is initialized. qed"); + if current.priority < *priority { + return Some((current.priority, Some(current.clone()))) + } else { + return acc + } + }, + ) + .map(|r| r.1.map(|tx| tx.hash))? + }, + _ => None, + } + } + + /// Removes the whole transaction subtree the pool. + /// + /// Intended to be called when removal is a result of replacement. Provided `replaced_with` + /// transaction hash is used in emitted _usurped_ event. + pub fn remove_subtree( + &self, + tx_hash: ExtrinsicHash, + replaced_with: ExtrinsicHash, + ) -> Vec> { + self.pool + .write() + .remove_subtree(&[tx_hash]) + .into_iter() + .map(|tx| tx.hash) + .inspect(|tx_hash| { + let mut listener = self.listener.write(); + listener.usurped(&tx_hash, &replaced_with) + }) + .collect::>() + } } fn fire_events(listener: &mut Listener, imported: &base::Imported) From 54ae11c3c79a9aec52bafe2b39e2fd7abdf6df59 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Tue, 26 Nov 2024 12:53:46 +0100 Subject: [PATCH 09/60] graph: fold_ready improved --- .../transaction-pool/src/graph/base_pool.rs | 54 +++++++++++-------- .../transaction-pool/src/graph/ready.rs | 10 ++-- 2 files changed, 37 insertions(+), 27 deletions(-) diff --git a/substrate/client/transaction-pool/src/graph/base_pool.rs b/substrate/client/transaction-pool/src/graph/base_pool.rs index 04eaa998f42e6..8dce507be4e38 100644 --- a/substrate/client/transaction-pool/src/graph/base_pool.rs +++ b/substrate/client/transaction-pool/src/graph/base_pool.rs @@ -438,6 +438,16 @@ impl BasePool(&self, init: R, mut f: F) -> R + where + F: FnMut(R, &Arc>) -> R, + { + self.ready + .fold::(init, |acc, current| f(acc, ¤t.transaction.transaction)) + } + /// Makes sure that the transactions in the queues stay within provided limits. /// /// Removes and returns worst transactions from the queues and all transactions that depend on @@ -453,27 +463,29 @@ impl BasePool, _>(|worst, current| { - let transaction = ¤t.transaction; - worst - .map(|worst| { - // Here we don't use `TransactionRef`'s ordering implementation because - // while it prefers priority like need here, it also prefers older - // transactions for inclusion purposes and limit enforcement needs to prefer - // newer transactions instead and drop the older ones. - match worst.transaction.priority.cmp(&transaction.transaction.priority) { - Ordering::Less => worst, - Ordering::Equal => - if worst.insertion_id > transaction.insertion_id { - transaction.clone() - } else { - worst - }, - Ordering::Greater => transaction.clone(), - } - }) - .or_else(|| Some(transaction.clone())) - }); + let worst = + self.ready.fold::>, _>(None, |worst, current| { + let transaction = ¤t.transaction; + worst + .map(|worst| { + // Here we don't use `TransactionRef`'s ordering implementation because + // while it prefers priority like need here, it also prefers older + // transactions for inclusion purposes and limit enforcement needs to + // prefer newer transactions instead and drop the older ones. + match worst.transaction.priority.cmp(&transaction.transaction.priority) + { + Ordering::Less => worst, + Ordering::Equal => + if worst.insertion_id > transaction.insertion_id { + transaction.clone() + } else { + worst + }, + Ordering::Greater => transaction.clone(), + } + }) + .or_else(|| Some(transaction.clone())) + }); if let Some(worst) = worst { removed.append(&mut self.remove_subtree(&[worst.transaction.hash.clone()])) diff --git a/substrate/client/transaction-pool/src/graph/ready.rs b/substrate/client/transaction-pool/src/graph/ready.rs index 9061d0e255811..b8aef99e638dc 100644 --- a/substrate/client/transaction-pool/src/graph/ready.rs +++ b/substrate/client/transaction-pool/src/graph/ready.rs @@ -232,12 +232,10 @@ impl ReadyTransactions { Ok(replaced) } - /// Fold a list of ready transactions to compute a single value. - pub fn fold, &ReadyTx) -> Option>( - &mut self, - f: F, - ) -> Option { - self.ready.read().values().fold(None, f) + /// Fold a list of ready transactions to compute a single value using initial value of + /// accumulator. + pub fn fold) -> R>(&self, init: R, f: F) -> R { + self.ready.read().values().fold(init, f) } /// Returns true if given transaction is part of the queue. From a6882eb5b46529228741772a644cb4f01287f19f Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Tue, 26 Nov 2024 12:59:49 +0100 Subject: [PATCH 10/60] graph: make some staff public --- substrate/client/transaction-pool/src/graph/mod.rs | 1 + substrate/client/transaction-pool/src/graph/pool.rs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/substrate/client/transaction-pool/src/graph/mod.rs b/substrate/client/transaction-pool/src/graph/mod.rs index d93898b1b22ab..43f93dc262557 100644 --- a/substrate/client/transaction-pool/src/graph/mod.rs +++ b/substrate/client/transaction-pool/src/graph/mod.rs @@ -43,4 +43,5 @@ pub use self::pool::{ }; pub use validated_pool::{IsValidator, ValidatedTransaction}; +pub(crate) use self::pool::CheckBannedBeforeVerify; pub(crate) use listener::DroppedByLimitsEvent; diff --git a/substrate/client/transaction-pool/src/graph/pool.rs b/substrate/client/transaction-pool/src/graph/pool.rs index 23b71ce437b3f..b30e9babc877e 100644 --- a/substrate/client/transaction-pool/src/graph/pool.rs +++ b/substrate/client/transaction-pool/src/graph/pool.rs @@ -161,7 +161,7 @@ impl Default for Options { /// Should we check that the transaction is banned /// in the pool, before we verify it? #[derive(Copy, Clone)] -enum CheckBannedBeforeVerify { +pub(crate) enum CheckBannedBeforeVerify { Yes, No, } @@ -409,7 +409,7 @@ impl Pool { } /// Returns future that validates single transaction at given block. - async fn verify_one( + pub(crate) async fn verify_one( &self, block_hash: ::Hash, block_number: NumberFor, From 8d3dffe08485b8b7d00176d178c6de6e9ecc61fa Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Tue, 26 Nov 2024 13:49:09 +0100 Subject: [PATCH 11/60] tests added --- .../transaction-pool/tests/fatp_common/mod.rs | 19 +- .../transaction-pool/tests/fatp_prios.rs | 287 +++++++++++++++++- 2 files changed, 298 insertions(+), 8 deletions(-) diff --git a/substrate/client/transaction-pool/tests/fatp_common/mod.rs b/substrate/client/transaction-pool/tests/fatp_common/mod.rs index aecd83360f1e9..d9bc7d30b13bd 100644 --- a/substrate/client/transaction-pool/tests/fatp_common/mod.rs +++ b/substrate/client/transaction-pool/tests/fatp_common/mod.rs @@ -192,12 +192,9 @@ macro_rules! assert_ready_iterator { let output: Vec<_> = ready_iterator.collect(); log::debug!(target:LOG_TARGET, "expected: {:#?}", expected); log::debug!(target:LOG_TARGET, "output: {:#?}", output); + let output = output.into_iter().map(|t|t.hash).collect::>(); assert_eq!(expected.len(), output.len()); - assert!( - output.iter().zip(expected.iter()).all(|(o,e)| { - o.hash == *e - }) - ); + assert_eq!(output,expected); }}; } @@ -215,6 +212,18 @@ macro_rules! assert_future_iterator { }}; } +#[macro_export] +macro_rules! assert_watcher_stream { + ($stream:ident, [$( $event:expr ),*]) => {{ + let expected = vec![ $($event),*]; + log::debug!(target:LOG_TARGET, "expected: {:#?} {}, block now:", expected, expected.len()); + let output = futures::executor::block_on_stream($stream).take(expected.len()).collect::>(); + log::debug!(target:LOG_TARGET, "output: {:#?}", output); + assert_eq!(expected.len(), output.len()); + assert_eq!(output, expected); + }}; +} + pub const SOURCE: TransactionSource = TransactionSource::External; #[cfg(test)] diff --git a/substrate/client/transaction-pool/tests/fatp_prios.rs b/substrate/client/transaction-pool/tests/fatp_prios.rs index 41bc374b38f46..08e83e92b1878 100644 --- a/substrate/client/transaction-pool/tests/fatp_prios.rs +++ b/substrate/client/transaction-pool/tests/fatp_prios.rs @@ -20,13 +20,14 @@ pub mod fatp_common; -use fatp_common::{new_best_block_event, TestPoolBuilder, LOG_TARGET, SOURCE}; +use fatp_common::{invalid_hash, new_best_block_event, TestPoolBuilder, LOG_TARGET, SOURCE}; use futures::{executor::block_on, FutureExt}; use sc_transaction_pool::ChainApi; -use sc_transaction_pool_api::{MaintainedTransactionPool, TransactionPool, TransactionStatus}; +use sc_transaction_pool_api::{ + error::Error as TxPoolError, MaintainedTransactionPool, TransactionPool, TransactionStatus, +}; use substrate_test_runtime_client::AccountKeyring::*; use substrate_test_runtime_transaction_pool::uxt; - #[test] fn fatp_prio_ready_higher_evicts_lower() { sp_tracing::try_init_simple(); @@ -247,3 +248,283 @@ fn fatp_prio_watcher_future_lower_prio_gets_dropped_from_all_views() { assert_ready_iterator!(header01.hash(), pool, [xt2, xt1]); assert_ready_iterator!(header02.hash(), pool, [xt2, xt1]); } + +#[test] +fn fatp_prios_watcher_full_mempool_higher_prio_is_accepted() { + sp_tracing::try_init_simple(); + + let builder = TestPoolBuilder::new(); + let (pool, api, _) = builder.with_mempool_count_limit(4).with_ready_count(2).build(); + api.set_nonce(api.genesis_hash(), Bob.into(), 300); + api.set_nonce(api.genesis_hash(), Charlie.into(), 400); + api.set_nonce(api.genesis_hash(), Dave.into(), 500); + api.set_nonce(api.genesis_hash(), Eve.into(), 600); + api.set_nonce(api.genesis_hash(), Ferdie.into(), 700); + + let header01 = api.push_block(1, vec![], true); + let event = new_best_block_event(&pool, None, header01.hash()); + block_on(pool.maintain(event)); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Bob, 300); + let xt2 = uxt(Charlie, 400); + + let xt3 = uxt(Dave, 500); + + let xt4 = uxt(Eve, 600); + let xt5 = uxt(Ferdie, 700); + + api.set_priority(&xt0, 1); + api.set_priority(&xt1, 2); + api.set_priority(&xt2, 3); + api.set_priority(&xt3, 4); + + api.set_priority(&xt4, 5); + api.set_priority(&xt5, 6); + + let _xt0_watcher = + block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); + let _xt1_watcher = + block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap(); + + assert_pool_status!(header01.hash(), &pool, 2, 0); + assert_eq!(pool.mempool_len().1, 2); + + let header02 = api.push_block_with_parent(header01.hash(), vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02.hash()))); + + let xt2_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap(); + let xt3_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).unwrap(); + + assert_pool_status!(header02.hash(), &pool, 2, 0); + assert_eq!(pool.mempool_len().1, 4); + + let header03 = api.push_block_with_parent(header02.hash(), vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header02.hash()), header03.hash()))); + + let _xt4_watcher = + block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt4.clone())).unwrap(); + let _xt5_watcher = + block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt5.clone())).unwrap(); + + assert_pool_status!(header03.hash(), &pool, 2, 0); + assert_eq!(pool.mempool_len().1, 4); + + let header04 = api.push_block_with_parent(header03.hash(), vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header03.hash()), header04.hash()))); + + assert_ready_iterator!(header01.hash(), pool, [xt1, xt0]); + assert_ready_iterator!(header02.hash(), pool, []); + assert_ready_iterator!(header03.hash(), pool, [xt5, xt4]); + + let xt2_status = futures::executor::block_on_stream(xt2_watcher).take(2).collect::>(); + assert_eq!( + xt2_status, + vec![TransactionStatus::Ready, TransactionStatus::Usurped(api.hash_and_length(&xt4).0)] + ); + let xt3_status = futures::executor::block_on_stream(xt3_watcher).take(2).collect::>(); + assert_eq!( + xt3_status, + vec![TransactionStatus::Ready, TransactionStatus::Usurped(api.hash_and_length(&xt5).0)] + ); +} + +#[test] +fn fatp_prios_watcher_full_mempool_higher_prio_is_accepted_with_subtree() { + sp_tracing::try_init_simple(); + + let builder = TestPoolBuilder::new(); + let (pool, api, _) = builder.with_mempool_count_limit(4).with_ready_count(4).build(); + api.set_nonce(api.genesis_hash(), Bob.into(), 300); + api.set_nonce(api.genesis_hash(), Charlie.into(), 400); + + let header01 = api.push_block(1, vec![], true); + let event = new_best_block_event(&pool, None, header01.hash()); + block_on(pool.maintain(event)); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Alice, 201); + let xt2 = uxt(Alice, 202); + let xt3 = uxt(Bob, 300); + let xt4 = uxt(Charlie, 400); + + api.set_priority(&xt0, 1); + api.set_priority(&xt1, 3); + api.set_priority(&xt2, 3); + api.set_priority(&xt3, 2); + api.set_priority(&xt4, 2); + + let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); + let xt1_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap(); + let xt2_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap(); + let xt3_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).unwrap(); + + assert_ready_iterator!(header01.hash(), pool, [xt3, xt0, xt1, xt2]); + assert_pool_status!(header01.hash(), &pool, 4, 0); + assert_eq!(pool.mempool_len().1, 4); + + // let header02 = api.push_block_with_parent(header01.hash(), vec![], true); + // block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02.hash()))); + + let xt4_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt4.clone())).unwrap(); + assert_pool_status!(header01.hash(), &pool, 2, 0); + assert_ready_iterator!(header01.hash(), pool, [xt3, xt4]); + + assert_watcher_stream!( + xt0_watcher, + [TransactionStatus::Ready, TransactionStatus::Usurped(api.hash_and_length(&xt4).0)] + ); + assert_watcher_stream!( + xt1_watcher, + [TransactionStatus::Ready, TransactionStatus::Usurped(api.hash_and_length(&xt4).0)] + ); + assert_watcher_stream!( + xt2_watcher, + [TransactionStatus::Ready, TransactionStatus::Usurped(api.hash_and_length(&xt4).0)] + ); + assert_watcher_stream!(xt3_watcher, [TransactionStatus::Ready]); + assert_watcher_stream!(xt4_watcher, [TransactionStatus::Ready]); +} + +#[test] +fn fatp_prios_watcher_full_mempool_higher_prio_is_accepted_with_subtree2() { + sp_tracing::try_init_simple(); + + let builder = TestPoolBuilder::new(); + let (pool, api, _) = builder.with_mempool_count_limit(4).with_ready_count(4).build(); + api.set_nonce(api.genesis_hash(), Bob.into(), 300); + api.set_nonce(api.genesis_hash(), Charlie.into(), 400); + + let header01 = api.push_block(1, vec![], true); + let event = new_best_block_event(&pool, None, header01.hash()); + block_on(pool.maintain(event)); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Alice, 201); + let xt2 = uxt(Alice, 202); + let xt3 = uxt(Bob, 300); + let xt4 = uxt(Charlie, 400); + + api.set_priority(&xt0, 1); + api.set_priority(&xt1, 3); + api.set_priority(&xt2, 3); + api.set_priority(&xt3, 2); + api.set_priority(&xt4, 2); + + let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); + let xt1_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap(); + let xt2_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap(); + let xt3_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).unwrap(); + + assert_ready_iterator!(header01.hash(), pool, [xt3, xt0, xt1, xt2]); + assert_pool_status!(header01.hash(), &pool, 4, 0); + assert_eq!(pool.mempool_len().1, 4); + + let header02 = api.push_block_with_parent(header01.hash(), vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02.hash()))); + + let xt4_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt4.clone())).unwrap(); + assert_ready_iterator!(header01.hash(), pool, [xt3]); + assert_pool_status!(header02.hash(), &pool, 2, 0); + assert_ready_iterator!(header02.hash(), pool, [xt3, xt4]); + + assert_watcher_stream!( + xt0_watcher, + [TransactionStatus::Ready, TransactionStatus::Usurped(api.hash_and_length(&xt4).0)] + ); + assert_watcher_stream!( + xt1_watcher, + [TransactionStatus::Ready, TransactionStatus::Usurped(api.hash_and_length(&xt4).0)] + ); + assert_watcher_stream!( + xt2_watcher, + [TransactionStatus::Ready, TransactionStatus::Usurped(api.hash_and_length(&xt4).0)] + ); + assert_watcher_stream!(xt3_watcher, [TransactionStatus::Ready]); + assert_watcher_stream!(xt4_watcher, [TransactionStatus::Ready]); +} + +#[test] +fn fatp_prios_watcher_full_mempool_lower_prio_gets_rejected() { + sp_tracing::try_init_simple(); + + let builder = TestPoolBuilder::new(); + let (pool, api, _) = builder.with_mempool_count_limit(2).with_ready_count(2).build(); + api.set_nonce(api.genesis_hash(), Bob.into(), 300); + api.set_nonce(api.genesis_hash(), Charlie.into(), 400); + api.set_nonce(api.genesis_hash(), Dave.into(), 500); + + let header01 = api.push_block(1, vec![], true); + let event = new_best_block_event(&pool, None, header01.hash()); + block_on(pool.maintain(event)); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Bob, 300); + let xt2 = uxt(Charlie, 400); + let xt3 = uxt(Dave, 500); + + api.set_priority(&xt0, 2); + api.set_priority(&xt1, 2); + api.set_priority(&xt2, 2); + api.set_priority(&xt3, 1); + + let _xt0_watcher = + block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); + let _xt1_watcher = + block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap(); + + assert_pool_status!(header01.hash(), &pool, 2, 0); + assert_eq!(pool.mempool_len().1, 2); + + let header02 = api.push_block_with_parent(header01.hash(), vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02.hash()))); + + assert_pool_status!(header02.hash(), &pool, 2, 0); + assert_eq!(pool.mempool_len().1, 2); + + assert_ready_iterator!(header01.hash(), pool, [xt0, xt1]); + assert_ready_iterator!(header02.hash(), pool, [xt0, xt1]); + + let result2 = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).map(|_| ()); + assert!(matches!(result2.as_ref().unwrap_err().0, TxPoolError::ImmediatelyDropped)); + let result3 = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).map(|_| ()); + assert!(matches!(result3.as_ref().unwrap_err().0, TxPoolError::ImmediatelyDropped)); +} + +#[test] +fn fatp_prios_watcher_full_mempool_does_not_keep_dropped_transaction() { + sp_tracing::try_init_simple(); + + let builder = TestPoolBuilder::new(); + let (pool, api, _) = builder.with_mempool_count_limit(4).with_ready_count(2).build(); + api.set_nonce(api.genesis_hash(), Bob.into(), 300); + api.set_nonce(api.genesis_hash(), Charlie.into(), 400); + api.set_nonce(api.genesis_hash(), Dave.into(), 500); + + let header01 = api.push_block(1, vec![], true); + let event = new_best_block_event(&pool, None, header01.hash()); + block_on(pool.maintain(event)); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Bob, 300); + let xt2 = uxt(Charlie, 400); + let xt3 = uxt(Dave, 500); + + api.set_priority(&xt0, 2); + api.set_priority(&xt1, 2); + api.set_priority(&xt2, 2); + api.set_priority(&xt3, 2); + + let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); + let xt1_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap(); + let xt2_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap(); + let xt3_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).unwrap(); + + assert_pool_status!(header01.hash(), &pool, 2, 0); + assert_ready_iterator!(header01.hash(), pool, [xt2, xt3]); + + assert_watcher_stream!(xt0_watcher, [TransactionStatus::Ready, TransactionStatus::Dropped]); + assert_watcher_stream!(xt1_watcher, [TransactionStatus::Ready, TransactionStatus::Dropped]); + assert_watcher_stream!(xt2_watcher, [TransactionStatus::Ready]); + assert_watcher_stream!(xt3_watcher, [TransactionStatus::Ready]); +} From 70fd186c2a586e1165d012a13bb865078afdf74c Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Tue, 26 Nov 2024 13:52:08 +0100 Subject: [PATCH 12/60] type removed From d3a1a7bdc8d436cad9983969d718aefbe74d2d0f Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Thu, 28 Nov 2024 10:34:51 +0100 Subject: [PATCH 13/60] improvements --- .../fork_aware_txpool/fork_aware_txpool.rs | 21 +++++----- .../src/fork_aware_txpool/view.rs | 11 +++-- .../src/fork_aware_txpool/view_store.rs | 34 ++++++++++----- .../client/transaction-pool/src/graph/mod.rs | 2 +- .../src/graph/validated_pool.rs | 41 +++++++++++-------- 5 files changed, 67 insertions(+), 42 deletions(-) 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 fbe960ac0da3c..564fccc6fbb38 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 @@ -31,10 +31,7 @@ use crate::{ api::FullChainApi, common::log_xt::log_xt_trace, enactment_state::{EnactmentAction, EnactmentState}, - fork_aware_txpool::{ - dropped_watcher::{DroppedReason, DroppedTransaction}, - revalidation_worker, - }, + fork_aware_txpool::{dropped_watcher::DroppedReason, revalidation_worker}, graph::{ self, base_pool::{TimedTransactionSource, Transaction}, @@ -1270,7 +1267,8 @@ where self.api.hash_and_length(xt).0 } - /// Attempts to replace a lower-priority transaction in the transaction pool with a new one. + /// Attempts to find and replace a lower-priority transaction in the transaction pool with a new + /// one. /// /// This asynchronous function verifies the new transaction against the most recent view. If a /// transaction with a lower priority exists in the transaction pool, it is replaced with the @@ -1322,12 +1320,13 @@ where self.mempool.try_replace_transaction(xt, source, watched, worst_tx_hash); // 5. notify listner - self.view_store - .listener - .transaction_dropped(DroppedTransaction::new_usurped(worst_tx_hash, tx_hash)); - - // 7. remove transaction from the view_store - self.view_store.remove_transaction_subtree(worst_tx_hash, tx_hash); + // 6. remove transaction from the view_store + self.view_store.remove_transaction_subtree( + worst_tx_hash, + |listener, removed_tx_hash| { + listener.usurped(&removed_tx_hash, &tx_hash); + }, + ); // 8. add to pending_replacements - make sure it will not sneak back via cloned view 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 9adb879cb2c7e..f900da9e29f53 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/view.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/view.rs @@ -465,11 +465,14 @@ where /// /// Intended to be called when removal is a result of replacement. Provided `replaced_with` /// transaction hash is used in emitted _usurped_ event. - pub(super) fn remove_subtree( + pub fn remove_subtree( &self, tx_hash: ExtrinsicHash, - replaced_with: ExtrinsicHash, - ) -> Vec> { - self.pool.validated_pool().remove_subtree(tx_hash, replaced_with) + listener_action: F, + ) -> Vec> + where + F: Fn(&mut crate::graph::Listener, ExtrinsicHash), + { + self.pool.validated_pool().remove_subtree(tx_hash, 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 7687a5699747d..3c095ae2a599d 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 @@ -38,7 +38,7 @@ use sc_transaction_pool_api::{error::Error as PoolError, PoolStatus}; use sp_blockchain::TreeRoute; use sp_runtime::{generic::BlockId, traits::Block as BlockT}; use std::{ - collections::{hash_map::Entry, HashMap}, + collections::{hash_map::Entry, HashMap, HashSet}, sync::Arc, time::Instant, }; @@ -691,23 +691,37 @@ where let _results = futures::future::join_all(submit_futures).await; } - /// Removes the whole transaction subtree from every view within the view store. + /// Removes a transaction subtree from the every view in the view_store, starting from the given + /// transaction hash. /// - /// Intended to be called when removal is a result of replacement. Provided `replaced_with` - /// transaction hash is used in emitted _usurped_ event. - pub(super) fn remove_transaction_subtree( + /// This function traverses the dependency graph of transactions and removes the specified + /// transaction along with all its descendant transactions from every view. + /// + /// A `listener_action` callback function is invoked for every transaction that is removed, + /// providing a reference to the pool's listener and the hash of the removed transaction. This + /// allows to trigger the required events. Note listener may be called multiple times for the + /// same hash. + /// + /// Returns a vector containing the hashes of all removed transactions, including the root + /// transaction specified by `tx_hash`. Vector contains only unique hashes. + pub(super) fn remove_transaction_subtree( &self, xt_hash: ExtrinsicHash, - replaced_with: ExtrinsicHash, - ) { + listener_action: F, + ) -> Vec> + where + F: Fn(&mut crate::graph::Listener, ExtrinsicHash), + { + let mut seen = HashSet::new(); let active_views = self.active_views.read(); let inactive_views = self.inactive_views.read(); active_views .iter() .chain(inactive_views.iter()) .filter(|(_, view)| view.is_imported(&xt_hash)) - .for_each(|(_, view)| { - view.remove_subtree(xt_hash, replaced_with); - }); + .map(|(_, view)| view.remove_subtree(xt_hash, &listener_action)) + .flatten() + .filter(|xt_hash| seen.insert(*xt_hash)) + .collect() } } diff --git a/substrate/client/transaction-pool/src/graph/mod.rs b/substrate/client/transaction-pool/src/graph/mod.rs index 43f93dc262557..0810b532273aa 100644 --- a/substrate/client/transaction-pool/src/graph/mod.rs +++ b/substrate/client/transaction-pool/src/graph/mod.rs @@ -41,7 +41,7 @@ pub use self::pool::{ BlockHash, ChainApi, ExtrinsicFor, ExtrinsicHash, NumberFor, Options, Pool, RawExtrinsicFor, TransactionFor, ValidatedTransactionFor, }; -pub use validated_pool::{IsValidator, ValidatedTransaction}; +pub use validated_pool::{IsValidator, Listener, ValidatedTransaction}; pub(crate) use self::pool::CheckBannedBeforeVerify; pub(crate) use listener::DroppedByLimitsEvent; diff --git a/substrate/client/transaction-pool/src/graph/validated_pool.rs b/substrate/client/transaction-pool/src/graph/validated_pool.rs index d283ee665c672..822d594e91ef2 100644 --- a/substrate/client/transaction-pool/src/graph/validated_pool.rs +++ b/substrate/client/transaction-pool/src/graph/validated_pool.rs @@ -18,7 +18,6 @@ use std::{ collections::{HashMap, HashSet}, - hash, sync::Arc, }; @@ -26,17 +25,15 @@ use crate::{common::log_xt::log_xt_trace, LOG_TARGET}; use futures::channel::mpsc::{channel, Sender}; use parking_lot::{Mutex, RwLock}; use sc_transaction_pool_api::{error, PoolStatus, ReadyTransactions}; -use serde::Serialize; use sp_blockchain::HashAndNumber; use sp_runtime::{ - traits::{self, SaturatedConversion}, + traits::SaturatedConversion, transaction_validity::{TransactionTag as Tag, ValidTransaction}, }; use std::time::Instant; use super::{ base_pool::{self as base, PruneStatus}, - listener::Listener, pool::{ BlockHash, ChainApi, EventStream, ExtrinsicFor, ExtrinsicHash, Options, TransactionFor, }, @@ -85,6 +82,8 @@ impl ValidatedTransaction { pub type ValidatedTransactionFor = ValidatedTransaction, ExtrinsicFor, ::Error>; +pub type Listener = super::listener::Listener, B>; + /// A closure that returns true if the local node is a validator that can author blocks. #[derive(Clone)] pub struct IsValidator(Arc bool + Send + Sync>>); @@ -106,7 +105,7 @@ pub struct ValidatedPool { api: Arc, is_validator: IsValidator, options: Options, - listener: RwLock, B>>, + listener: RwLock>, pub(crate) pool: RwLock, ExtrinsicFor>>, import_notification_sinks: Mutex>>>, rotator: PoolRotator>, @@ -716,31 +715,41 @@ impl ValidatedPool { } } - /// Removes the whole transaction subtree the pool. + /// Removes a transaction subtree from the pool, starting from the given transaction hash. + /// + /// This function traverses the dependency graph of transactions and removes the specified + /// transaction along with all its descendant transactions from the pool. + /// + /// A `listener_action` callback function is invoked for every transaction that is removed, + /// providing a reference to the pool's listener and the hash of the removed transaction. This + /// allows to trigger the required events. /// - /// Intended to be called when removal is a result of replacement. Provided `replaced_with` - /// transaction hash is used in emitted _usurped_ event. - pub fn remove_subtree( + /// Returns a vector containing the hashes of all removed transactions, including the root + /// transaction specified by `tx_hash`. + pub fn remove_subtree( &self, tx_hash: ExtrinsicHash, - replaced_with: ExtrinsicHash, - ) -> Vec> { + listener_action: F, + ) -> Vec> + where + F: Fn(&mut Listener, ExtrinsicHash), + { self.pool .write() .remove_subtree(&[tx_hash]) .into_iter() - .map(|tx| tx.hash) - .inspect(|tx_hash| { + .map(|tx| { + let removed_tx_hash = tx.hash; let mut listener = self.listener.write(); - listener.usurped(&tx_hash, &replaced_with) + listener_action(&mut *listener, removed_tx_hash); + removed_tx_hash }) .collect::>() } } -fn fire_events(listener: &mut Listener, imported: &base::Imported) +fn fire_events(listener: &mut Listener, imported: &base::Imported, Ex>) where - H: hash::Hash + Eq + traits::Member + Serialize, B: ChainApi, { match *imported { From 4246dac3f857230167e614736d26a39a36459d2f Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Thu, 28 Nov 2024 14:48:08 +0100 Subject: [PATCH 14/60] make use of your brain --- .../fork_aware_txpool/fork_aware_txpool.rs | 8 +-- .../transaction-pool/src/graph/listener.rs | 4 +- .../src/graph/validated_pool.rs | 2 +- .../transaction-pool/tests/fatp_prios.rs | 50 +++++-------------- 4 files changed, 20 insertions(+), 44 deletions(-) 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 564fccc6fbb38..1a75ef9d861d1 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 @@ -1315,23 +1315,23 @@ where best_view.pool.validated_pool().find_transaction_with_lower_prio(validated_tx) { // 4. if yes - remove worse transaction from mempool and add new one. - log::trace!(target: LOG_TARGET, "found candidate for removal: {worst_tx_hash:?}"); + log::trace!(target: LOG_TARGET, "found candidate for removal: {worst_tx_hash:?} replaced by {tx_hash:?}"); let insertion_info = - self.mempool.try_replace_transaction(xt, source, watched, worst_tx_hash); + self.mempool.try_replace_transaction(xt, source, watched, worst_tx_hash)?; // 5. notify listner // 6. remove transaction from the view_store self.view_store.remove_transaction_subtree( worst_tx_hash, |listener, removed_tx_hash| { - listener.usurped(&removed_tx_hash, &tx_hash); + listener.limits_enforced(&removed_tx_hash); }, ); // 8. add to pending_replacements - make sure it will not sneak back via cloned view // 9. subemit new one to the view, this will be done upon in the caller - return insertion_info + return Ok(insertion_info) } Err(TxPoolApiError::ImmediatelyDropped) diff --git a/substrate/client/transaction-pool/src/graph/listener.rs b/substrate/client/transaction-pool/src/graph/listener.rs index 41daf5491f709..7b09ee4c64095 100644 --- a/substrate/client/transaction-pool/src/graph/listener.rs +++ b/substrate/client/transaction-pool/src/graph/listener.rs @@ -126,8 +126,8 @@ impl Listener ValidatedPool { // run notifications let mut listener = self.listener.write(); for h in &removed { - listener.limit_enforced(h); + listener.limits_enforced(h); } removed diff --git a/substrate/client/transaction-pool/tests/fatp_prios.rs b/substrate/client/transaction-pool/tests/fatp_prios.rs index 08e83e92b1878..3c7db35323d8f 100644 --- a/substrate/client/transaction-pool/tests/fatp_prios.rs +++ b/substrate/client/transaction-pool/tests/fatp_prios.rs @@ -310,23 +310,17 @@ fn fatp_prios_watcher_full_mempool_higher_prio_is_accepted() { assert_pool_status!(header03.hash(), &pool, 2, 0); assert_eq!(pool.mempool_len().1, 4); - let header04 = api.push_block_with_parent(header03.hash(), vec![], true); - block_on(pool.maintain(new_best_block_event(&pool, Some(header03.hash()), header04.hash()))); + // let header04 = api.push_block_with_parent(header03.hash(), vec![], true); + // block_on(pool.maintain(new_best_block_event(&pool, Some(header03.hash()), header04.hash()))); + + let xt2_status = futures::executor::block_on_stream(xt2_watcher).take(2).collect::>(); + assert_eq!(xt2_status, vec![TransactionStatus::Ready, TransactionStatus::Dropped]); + let xt3_status = futures::executor::block_on_stream(xt3_watcher).take(2).collect::>(); + assert_eq!(xt3_status, vec![TransactionStatus::Ready, TransactionStatus::Dropped]); assert_ready_iterator!(header01.hash(), pool, [xt1, xt0]); assert_ready_iterator!(header02.hash(), pool, []); assert_ready_iterator!(header03.hash(), pool, [xt5, xt4]); - - let xt2_status = futures::executor::block_on_stream(xt2_watcher).take(2).collect::>(); - assert_eq!( - xt2_status, - vec![TransactionStatus::Ready, TransactionStatus::Usurped(api.hash_and_length(&xt4).0)] - ); - let xt3_status = futures::executor::block_on_stream(xt3_watcher).take(2).collect::>(); - assert_eq!( - xt3_status, - vec![TransactionStatus::Ready, TransactionStatus::Usurped(api.hash_and_length(&xt5).0)] - ); } #[test] @@ -370,18 +364,9 @@ fn fatp_prios_watcher_full_mempool_higher_prio_is_accepted_with_subtree() { assert_pool_status!(header01.hash(), &pool, 2, 0); assert_ready_iterator!(header01.hash(), pool, [xt3, xt4]); - assert_watcher_stream!( - xt0_watcher, - [TransactionStatus::Ready, TransactionStatus::Usurped(api.hash_and_length(&xt4).0)] - ); - assert_watcher_stream!( - xt1_watcher, - [TransactionStatus::Ready, TransactionStatus::Usurped(api.hash_and_length(&xt4).0)] - ); - assert_watcher_stream!( - xt2_watcher, - [TransactionStatus::Ready, TransactionStatus::Usurped(api.hash_and_length(&xt4).0)] - ); + assert_watcher_stream!(xt0_watcher, [TransactionStatus::Ready, TransactionStatus::Dropped]); + assert_watcher_stream!(xt1_watcher, [TransactionStatus::Ready, TransactionStatus::Dropped]); + assert_watcher_stream!(xt2_watcher, [TransactionStatus::Ready, TransactionStatus::Dropped]); assert_watcher_stream!(xt3_watcher, [TransactionStatus::Ready]); assert_watcher_stream!(xt4_watcher, [TransactionStatus::Ready]); } @@ -428,18 +413,9 @@ fn fatp_prios_watcher_full_mempool_higher_prio_is_accepted_with_subtree2() { assert_pool_status!(header02.hash(), &pool, 2, 0); assert_ready_iterator!(header02.hash(), pool, [xt3, xt4]); - assert_watcher_stream!( - xt0_watcher, - [TransactionStatus::Ready, TransactionStatus::Usurped(api.hash_and_length(&xt4).0)] - ); - assert_watcher_stream!( - xt1_watcher, - [TransactionStatus::Ready, TransactionStatus::Usurped(api.hash_and_length(&xt4).0)] - ); - assert_watcher_stream!( - xt2_watcher, - [TransactionStatus::Ready, TransactionStatus::Usurped(api.hash_and_length(&xt4).0)] - ); + assert_watcher_stream!(xt0_watcher, [TransactionStatus::Ready, TransactionStatus::Dropped]); + assert_watcher_stream!(xt1_watcher, [TransactionStatus::Ready, TransactionStatus::Dropped]); + assert_watcher_stream!(xt2_watcher, [TransactionStatus::Ready, TransactionStatus::Dropped]); assert_watcher_stream!(xt3_watcher, [TransactionStatus::Ready]); assert_watcher_stream!(xt4_watcher, [TransactionStatus::Ready]); } From c203d72dd199155c14cb9c929666e4233f50faa7 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Thu, 28 Nov 2024 15:40:08 +0100 Subject: [PATCH 15/60] fatp: pending actions now support removals --- .../fork_aware_txpool/fork_aware_txpool.rs | 9 +- .../src/fork_aware_txpool/view_store.rs | 166 ++++++++++++++---- 2 files changed, 141 insertions(+), 34 deletions(-) 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 1a75ef9d861d1..0ad6ffd755223 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 @@ -31,7 +31,10 @@ use crate::{ api::FullChainApi, common::log_xt::log_xt_trace, enactment_state::{EnactmentAction, EnactmentState}, - fork_aware_txpool::{dropped_watcher::DroppedReason, revalidation_worker}, + fork_aware_txpool::{ + dropped_watcher::{DroppedReason, DroppedTransaction}, + revalidation_worker, + }, graph::{ self, base_pool::{TimedTransactionSource, Transaction}, @@ -1320,6 +1323,10 @@ where self.mempool.try_replace_transaction(xt, source, watched, worst_tx_hash)?; // 5. notify listner + self.view_store + .listener + .transaction_dropped(DroppedTransaction::new_enforced_by_limts(worst_tx_hash)); + // 6. remove transaction from the view_store self.view_store.remove_transaction_subtree( worst_tx_hash, 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 3c095ae2a599d..9de3e74229816 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 @@ -43,15 +43,13 @@ use std::{ time::Instant, }; -/// Helper struct to keep the context for transaction replacements. +/// Helper struct to maintain the context for pending transaction submission, executed for +/// newly inserted views. #[derive(Clone)] -struct PendingTxReplacement +struct PendingTxSubmission where ChainApi: graph::ChainApi, { - /// Indicates if the new transaction was already submitted to all the views in the view_store. - /// If true, it can be removed after inserting any new view. - processed: bool, /// New transaction replacing the old one. xt: ExtrinsicFor, /// Source of the transaction. @@ -60,13 +58,84 @@ where watched: bool, } -impl PendingTxReplacement +/// Helper type representing the callback allowing to trigger per-transaction events on +/// `ValidatedPool`'s listener. +type RemovalListener = + Arc, ExtrinsicHash) + Send + Sync>; + +/// Helper struct to maintain the context for pending transaction removal, executed for +/// newly inserted views. +struct PendingTxRemoval +where + ChainApi: graph::ChainApi, +{ + /// Hash of the transaction that will be removed, + xt_hash: ExtrinsicHash, + /// Action that shall be executed on underlying `ValidatedPool`'s listener. + listener_action: RemovalListener, +} + +/// This enum represents an action that should be executed on the newly built +/// view before this view is inserted into the view store. +enum PreInsertAction +where + ChainApi: graph::ChainApi, +{ + /// Represents the action of submitting a new transaction. Intended to use to handle usurped + /// transactions. + SubmitTx(PendingTxSubmission), + + /// Represents the action of removing a subtree of transactions. + RemoveSubtree(PendingTxRemoval), +} + +/// Represents a task awaiting execution, to be performed immediately prior to the view insertion +/// into the view store. +struct PendingPreInsertTask where ChainApi: graph::ChainApi, { - /// Creates new unprocessed instance of pending transaction replacement. - fn new(xt: ExtrinsicFor, source: TimedTransactionSource, watched: bool) -> Self { - Self { processed: false, xt, source, watched } + /// The action to be applied when inserting a new view. + action: PreInsertAction, + /// Indicates if the action was already applied to all the views in the view_store. + /// If true, it can be removed after inserting any new view. + processed: bool, +} + +impl PendingPreInsertTask +where + ChainApi: graph::ChainApi, +{ + /// Creates new unprocessed instance of pending transaction submission. + fn new_submission_action( + xt: ExtrinsicFor, + source: TimedTransactionSource, + watched: bool, + ) -> Self { + Self { + processed: false, + action: PreInsertAction::SubmitTx(PendingTxSubmission { xt, source, watched }), + } + } + + /// Creates new processed instance of pending transaction removal. + fn new_removal_action( + xt_hash: ExtrinsicHash, + listener: RemovalListener, + ) -> Self { + Self { + processed: false, + action: PreInsertAction::RemoveSubtree(PendingTxRemoval { + xt_hash, + listener_action: listener, + }), + } + } + + /// Marks a task as done for every view present in view store. Basically means that can be + /// removed on new view insertion. + fn mark_processed(&mut self) { + self.processed = true; } } @@ -100,9 +169,8 @@ where /// notifcication threads. It is meant to assure that replaced transaction is also removed from /// newly built views in maintain process. /// - /// The map's key is hash of replaced extrinsic. - pending_txs_replacements: - RwLock, PendingTxReplacement>>, + /// The map's key is hash of actionable extrinsic (to avoid duplicated entries). + pending_txs_tasks: RwLock, PendingPreInsertTask>>, } impl ViewStore @@ -124,7 +192,7 @@ where listener, most_recent_view: RwLock::from(None), dropped_stream_controller, - pending_txs_replacements: Default::default(), + pending_txs_tasks: Default::default(), } } @@ -575,8 +643,12 @@ where replaced: ExtrinsicHash, watched: bool, ) { - if let Entry::Vacant(entry) = self.pending_txs_replacements.write().entry(replaced) { - entry.insert(PendingTxReplacement::new(xt.clone(), source.clone(), watched)); + if let Entry::Vacant(entry) = self.pending_txs_tasks.write().entry(replaced) { + entry.insert(PendingPreInsertTask::new_submission_action( + xt.clone(), + source.clone(), + watched, + )); } else { return }; @@ -586,8 +658,8 @@ where self.replace_transaction_in_views(source, xt, xt_hash, replaced, watched).await; - if let Some(replacement) = self.pending_txs_replacements.write().get_mut(&replaced) { - replacement.processed = true; + if let Some(replacement) = self.pending_txs_tasks.write().get_mut(&replaced) { + replacement.mark_processed(); } } @@ -596,18 +668,25 @@ where /// After application, all already processed replacements are removed. async fn apply_pending_tx_replacements(&self, view: Arc>) { let mut futures = vec![]; - for replacement in self.pending_txs_replacements.read().values() { - let xt_hash = self.api.hash_and_length(&replacement.xt).0; - futures.push(self.replace_transaction_in_view( - view.clone(), - replacement.source.clone(), - replacement.xt.clone(), - xt_hash, - replacement.watched, - )); + for replacement in self.pending_txs_tasks.read().values() { + match replacement.action { + PreInsertAction::SubmitTx(ref submission) => { + let xt_hash = self.api.hash_and_length(&submission.xt).0; + futures.push(self.replace_transaction_in_view( + view.clone(), + submission.source.clone(), + submission.xt.clone(), + xt_hash, + submission.watched, + )); + }, + PreInsertAction::RemoveSubtree(ref removal) => { + view.remove_subtree(removal.xt_hash, &*removal.listener_action); + }, + } } let _results = futures::future::join_all(futures).await; - self.pending_txs_replacements.write().retain(|_, r| r.processed); + self.pending_txs_tasks.write().retain(|_, r| r.processed); } /// Submits `xt` to the given view. @@ -702,6 +781,9 @@ where /// allows to trigger the required events. Note listener may be called multiple times for the /// same hash. /// + /// Function will also schedule view pre-insertion actions to ensure that transactions will be + /// removed from newly created view. + /// /// Returns a vector containing the hashes of all removed transactions, including the root /// transaction specified by `tx_hash`. Vector contains only unique hashes. pub(super) fn remove_transaction_subtree( @@ -710,18 +792,36 @@ where listener_action: F, ) -> Vec> where - F: Fn(&mut crate::graph::Listener, ExtrinsicHash), + F: Fn(&mut crate::graph::Listener, ExtrinsicHash) + + Clone + + Send + + Sync + + 'static, { + if let Entry::Vacant(entry) = self.pending_txs_tasks.write().entry(xt_hash) { + entry.insert(PendingPreInsertTask::new_removal_action( + xt_hash, + Arc::from(listener_action.clone()), + )); + }; + let mut seen = HashSet::new(); - let active_views = self.active_views.read(); - let inactive_views = self.inactive_views.read(); - active_views + + let removed = self + .active_views + .read() .iter() - .chain(inactive_views.iter()) + .chain(self.inactive_views.read().iter()) .filter(|(_, view)| view.is_imported(&xt_hash)) .map(|(_, view)| view.remove_subtree(xt_hash, &listener_action)) .flatten() .filter(|xt_hash| seen.insert(*xt_hash)) - .collect() + .collect(); + + if let Some(removal_action) = self.pending_txs_tasks.write().get_mut(&xt_hash) { + removal_action.mark_processed(); + } + + removed } } From edb1257068161c5350210802ec486a9ec3a36ff9 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Fri, 29 Nov 2024 17:43:41 +0100 Subject: [PATCH 16/60] validated_pool: SubmitOutcome --- .../client/transaction-pool/src/graph/mod.rs | 4 +- .../client/transaction-pool/src/graph/pool.rs | 10 +-- .../src/graph/validated_pool.rs | 69 ++++++++++++++++--- 3 files changed, 69 insertions(+), 14 deletions(-) diff --git a/substrate/client/transaction-pool/src/graph/mod.rs b/substrate/client/transaction-pool/src/graph/mod.rs index 0810b532273aa..008503fb4cdfb 100644 --- a/substrate/client/transaction-pool/src/graph/mod.rs +++ b/substrate/client/transaction-pool/src/graph/mod.rs @@ -41,7 +41,9 @@ pub use self::pool::{ BlockHash, ChainApi, ExtrinsicFor, ExtrinsicHash, NumberFor, Options, Pool, RawExtrinsicFor, TransactionFor, ValidatedTransactionFor, }; -pub use validated_pool::{IsValidator, Listener, ValidatedTransaction}; +pub use validated_pool::{ + BaseSubmitOutcome, IsValidator, Listener, ValidatedPoolSubmitOutcome, ValidatedTransaction, +}; pub(crate) use self::pool::CheckBannedBeforeVerify; pub(crate) use listener::DroppedByLimitsEvent; diff --git a/substrate/client/transaction-pool/src/graph/pool.rs b/substrate/client/transaction-pool/src/graph/pool.rs index b30e9babc877e..dcab98a33ffea 100644 --- a/substrate/client/transaction-pool/src/graph/pool.rs +++ b/substrate/client/transaction-pool/src/graph/pool.rs @@ -37,7 +37,7 @@ use std::{ use super::{ base_pool as base, validated_pool::{IsValidator, ValidatedPool, ValidatedTransaction}, - watcher::Watcher, + ValidatedPoolSubmitOutcome, }; /// Modification notification event stream type; @@ -182,7 +182,7 @@ impl Pool { &self, at: &HashAndNumber, xts: impl IntoIterator)>, - ) -> Vec, B::Error>> { + ) -> Vec, B::Error>> { let validated_transactions = self.verify(at, xts, CheckBannedBeforeVerify::Yes).await; self.validated_pool.submit(validated_transactions.into_values()) } @@ -194,7 +194,7 @@ impl Pool { &self, at: &HashAndNumber, xts: impl IntoIterator)>, - ) -> Vec, B::Error>> { + ) -> Vec, B::Error>> { let validated_transactions = self.verify(at, xts, CheckBannedBeforeVerify::No).await; self.validated_pool.submit(validated_transactions.into_values()) } @@ -205,7 +205,7 @@ impl Pool { at: &HashAndNumber, source: base::TimedTransactionSource, xt: ExtrinsicFor, - ) -> Result, B::Error> { + ) -> Result, B::Error> { let res = self.submit_at(at, std::iter::once((source, xt))).await.pop(); res.expect("One extrinsic passed; one result returned; qed") } @@ -216,7 +216,7 @@ impl Pool { at: &HashAndNumber, source: base::TimedTransactionSource, xt: ExtrinsicFor, - ) -> Result, ExtrinsicHash>, B::Error> { + ) -> Result, B::Error> { let (_, tx) = self .verify_one(at.hash, at.number, source, xt, CheckBannedBeforeVerify::Yes) .await; diff --git a/substrate/client/transaction-pool/src/graph/validated_pool.rs b/substrate/client/transaction-pool/src/graph/validated_pool.rs index 072751c2a569d..720a806985381 100644 --- a/substrate/client/transaction-pool/src/graph/validated_pool.rs +++ b/substrate/client/transaction-pool/src/graph/validated_pool.rs @@ -24,7 +24,7 @@ use std::{ use crate::{common::log_xt::log_xt_trace, LOG_TARGET}; use futures::channel::mpsc::{channel, Sender}; use parking_lot::{Mutex, RwLock}; -use sc_transaction_pool_api::{error, PoolStatus, ReadyTransactions}; +use sc_transaction_pool_api::{error, PoolStatus, ReadyTransactions, TransactionPriority}; use sp_blockchain::HashAndNumber; use sp_runtime::{ traits::SaturatedConversion, @@ -78,10 +78,11 @@ impl ValidatedTransaction { } } -/// A type of validated transaction stored in the pool. +/// A type of validated transaction stored in the validated pool. pub type ValidatedTransactionFor = ValidatedTransaction, ExtrinsicFor, ::Error>; +/// A type alias representing ValidatedPool listener for given ChainApi type. pub type Listener = super::listener::Listener, B>; /// A closure that returns true if the local node is a validator that can author blocks. @@ -100,6 +101,54 @@ impl From bool + Send + Sync>> for IsValidator { } } +/// Represents the result of `submit` or `submit_and_watch` operations. +pub struct BaseSubmitOutcome { + /// The hash of the submitted transaction. + hash: ExtrinsicHash, + /// A transaction watcher. This is `Some` for `submit_and_watch` and `None` for `submit`. + watcher: Option, + /// The priority of the transaction. Defaults to zero if unknown. + priority: TransactionPriority, +} + +/// Type alias to outcome of submission to `ValidatedPool`. +pub type ValidatedPoolSubmitOutcome = + BaseSubmitOutcome, ExtrinsicHash>>; + +impl BaseSubmitOutcome { + /// Creates a new instance with given hash and priority. + pub fn new(hash: ExtrinsicHash, priority: TransactionPriority) -> Self { + Self { hash, priority, watcher: None } + } + + /// Creates a new instance with given hash and zeroed priority. + pub fn new_no_priority(hash: ExtrinsicHash) -> Self { + Self { hash, priority: 0u64, watcher: None } + } + + /// Sets the transaction watcher. + pub fn with_watcher(mut self, watcher: W) -> Self { + self.watcher = Some(watcher); + self + } + + /// Provides priority of submitted transaction. + pub fn priority(&self) -> TransactionPriority { + self.priority + } + + /// Provides hash of submitted transaction. + pub fn hash(&self) -> ExtrinsicHash { + self.hash + } + + /// Provides a watcher. Should only be called on outcomes of `submit_and_watch`. Otherwise will + /// panic (that would mean logical error in program). + pub fn expect_watcher(&mut self) -> W { + self.watcher.take().expect("watcher was set in submit_and_watch. qed") + } +} + /// Pool that deals with validated transactions. pub struct ValidatedPool { api: Arc, @@ -176,7 +225,7 @@ impl ValidatedPool { pub fn submit( &self, txs: impl IntoIterator>, - ) -> Vec, B::Error>> { + ) -> Vec, B::Error>> { let results = txs .into_iter() .map(|validated_tx| self.submit_one(validated_tx)) @@ -192,7 +241,7 @@ impl ValidatedPool { results .into_iter() .map(|res| match res { - Ok(ref hash) if removed.contains(hash) => + Ok(outcome) if removed.contains(&outcome.hash) => Err(error::Error::ImmediatelyDropped.into()), other => other, }) @@ -200,9 +249,13 @@ impl ValidatedPool { } /// Submit single pre-validated transaction to the pool. - fn submit_one(&self, tx: ValidatedTransactionFor) -> Result, B::Error> { + fn submit_one( + &self, + tx: ValidatedTransactionFor, + ) -> Result, B::Error> { match tx { ValidatedTransaction::Valid(tx) => { + let priority = tx.priority; log::trace!(target: LOG_TARGET, "[{:?}] ValidatedPool::submit_one", tx.hash); if !tx.propagate && !(self.is_validator.0)() { return Err(error::Error::Unactionable.into()) @@ -230,7 +283,7 @@ impl ValidatedPool { let mut listener = self.listener.write(); fire_events(&mut *listener, &imported); - Ok(*imported.hash()) + Ok(ValidatedPoolSubmitOutcome::new(*imported.hash(), priority)) }, ValidatedTransaction::Invalid(hash, err) => { log::trace!(target: LOG_TARGET, "[{:?}] ValidatedPool::submit_one invalid: {:?}", hash, err); @@ -294,7 +347,7 @@ impl ValidatedPool { pub fn submit_and_watch( &self, tx: ValidatedTransactionFor, - ) -> Result, ExtrinsicHash>, B::Error> { + ) -> Result, B::Error> { match tx { ValidatedTransaction::Valid(tx) => { let hash = self.api.hash_and_length(&tx.data).0; @@ -302,7 +355,7 @@ impl ValidatedPool { self.submit(std::iter::once(ValidatedTransaction::Valid(tx))) .pop() .expect("One extrinsic passed; one result returned; qed") - .map(|_| watcher) + .map(|outcome| outcome.with_watcher(watcher)) }, ValidatedTransaction::Invalid(hash, err) => { self.rotator.ban(&Instant::now(), std::iter::once(hash)); From f778176dad2f6ed95c5ca5da16c30938e64f92a5 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Fri, 29 Nov 2024 17:47:01 +0100 Subject: [PATCH 17/60] view/view_store: SubmitOutcome --- .../src/fork_aware_txpool/view.rs | 8 +- .../src/fork_aware_txpool/view_store.rs | 73 +++++++++++++------ 2 files changed, 54 insertions(+), 27 deletions(-) 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 f900da9e29f53..291a1f7d7fa3a 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/view.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/view.rs @@ -28,7 +28,7 @@ use crate::{ common::log_xt::log_xt_trace, graph::{ self, base_pool::TimedTransactionSource, watcher::Watcher, ExtrinsicFor, ExtrinsicHash, - IsValidator, ValidatedTransaction, ValidatedTransactionFor, + IsValidator, ValidatedPoolSubmitOutcome, ValidatedTransaction, ValidatedTransactionFor, }, LOG_TARGET, }; @@ -158,7 +158,7 @@ where pub(super) async fn submit_many( &self, xts: impl IntoIterator)>, - ) -> Vec, ChainApi::Error>> { + ) -> Vec, ChainApi::Error>> { if log::log_enabled!(target: LOG_TARGET, log::Level::Trace) { let xts = xts.into_iter().collect::>(); log_xt_trace!(target: LOG_TARGET, xts.iter().map(|(_,xt)| self.pool.validated_pool().api().hash_and_length(xt).0), "[{:?}] view::submit_many at:{}", self.at.hash); @@ -173,7 +173,7 @@ where &self, source: TimedTransactionSource, xt: ExtrinsicFor, - ) -> Result, ExtrinsicHash>, ChainApi::Error> { + ) -> Result, ChainApi::Error> { log::trace!(target: LOG_TARGET, "[{:?}] view::submit_and_watch at:{}", self.pool.validated_pool().api().hash_and_length(&xt).0, self.at.hash); self.pool.submit_and_watch(&self.at, source, xt).await } @@ -182,7 +182,7 @@ where pub(super) fn submit_local( &self, xt: ExtrinsicFor, - ) -> Result, ChainApi::Error> { + ) -> Result, ChainApi::Error> { let (hash, length) = self.pool.validated_pool().api().hash_and_length(&xt); log::trace!(target: LOG_TARGET, "[{:?}] view::submit_local at:{}", hash, self.at.hash); 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 9de3e74229816..4ae54e99264ce 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 @@ -27,7 +27,7 @@ use crate::{ graph::{ self, base_pool::{TimedTransactionSource, Transaction}, - ExtrinsicFor, ExtrinsicHash, TransactionFor, + BaseSubmitOutcome, ExtrinsicFor, ExtrinsicHash, TransactionFor, ValidatedPoolSubmitOutcome, }, ReadyIteratorFor, LOG_TARGET, }; @@ -173,6 +173,18 @@ where pending_txs_tasks: RwLock, PendingPreInsertTask>>, } +/// Type alias to outcome of submission to `ViewStore`. +pub(super) type ViewStoreSubmitOutcome = + BaseSubmitOutcome>; + +impl From> + for ViewStoreSubmitOutcome +{ + fn from(value: ValidatedPoolSubmitOutcome) -> Self { + Self::new(value.hash(), value.priority()) + } +} + impl ViewStore where Block: BlockT, @@ -200,7 +212,7 @@ where pub(super) async fn submit( &self, xts: impl IntoIterator)> + Clone, - ) -> HashMap, ChainApi::Error>>> { + ) -> HashMap, ChainApi::Error>>> { let submit_futures = { let active_views = self.active_views.read(); active_views @@ -208,7 +220,16 @@ where .map(|(_, view)| { let view = view.clone(); let xts = xts.clone(); - async move { (view.at.hash, view.submit_many(xts).await) } + async move { + ( + view.at.hash, + view.submit_many(xts) + .await + .into_iter() + .map(|r| r.map(Into::into)) + .collect::>(), + ) + } }) .collect::>() }; @@ -221,7 +242,7 @@ where pub(super) fn submit_local( &self, xt: ExtrinsicFor, - ) -> Result, ChainApi::Error> { + ) -> Result, ChainApi::Error> { let active_views = self .active_views .read() @@ -236,12 +257,14 @@ where .map(|view| view.submit_local(xt.clone())) .find_or_first(Result::is_ok); - if let Some(Err(err)) = result { - log::trace!(target: LOG_TARGET, "[{:?}] submit_local: err: {}", tx_hash, err); - return Err(err) - }; - - Ok(tx_hash) + match result { + Some(Err(err)) => { + log::trace!(target: LOG_TARGET, "[{:?}] submit_local: err: {}", tx_hash, err); + Err(err) + }, + None => Ok(ViewStoreSubmitOutcome::new_no_priority(tx_hash)), + Some(Ok(r)) => Ok(r.into()), + } } /// Import a single extrinsic and starts to watch its progress in the pool. @@ -256,7 +279,7 @@ where _at: Block::Hash, source: TimedTransactionSource, xt: ExtrinsicFor, - ) -> Result, ChainApi::Error> { + ) -> Result, ChainApi::Error> { let tx_hash = self.api.hash_and_length(&xt).0; let Some(external_watcher) = self.listener.create_external_watcher_for_tx(tx_hash) else { return Err(PoolError::AlreadyImported(Box::new(tx_hash)).into()) @@ -271,13 +294,13 @@ where let source = source.clone(); async move { match view.submit_and_watch(source, xt).await { - Ok(watcher) => { + Ok(mut result) => { self.listener.add_view_watcher_for_tx( tx_hash, view.at.hash, - watcher.into_stream().boxed(), + result.expect_watcher().into_stream().boxed(), ); - Ok(()) + Ok(result) }, Err(e) => Err(e), } @@ -285,17 +308,21 @@ where }) .collect::>() }; - let maybe_error = futures::future::join_all(submit_and_watch_futures) + let result = futures::future::join_all(submit_and_watch_futures) .await .into_iter() .find_or_first(Result::is_ok); - if let Some(Err(err)) = maybe_error { - log::trace!(target: LOG_TARGET, "[{:?}] submit_and_watch: err: {}", tx_hash, err); - return Err(err); - }; - - Ok(external_watcher) + match result { + Some(Err(err)) => { + log::trace!(target: LOG_TARGET, "[{:?}] submit_and_watch: err: {}", tx_hash, err); + return Err(err); + }, + Some(Ok(result)) => + Ok(ViewStoreSubmitOutcome::from(result).with_watcher(external_watcher)), + None => + Ok(ViewStoreSubmitOutcome::new_no_priority(tx_hash).with_watcher(external_watcher)), + } } /// Returns the pool status for every active view. @@ -702,11 +729,11 @@ where ) { if watched { match view.submit_and_watch(source, xt).await { - Ok(watcher) => { + Ok(mut result) => { self.listener.add_view_watcher_for_tx( xt_hash, view.at.hash, - watcher.into_stream().boxed(), + result.expect_watcher().into_stream().boxed(), ); }, Err(e) => { From a72b3f9fc4b20024222a705cdf8674fd809721c1 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Fri, 29 Nov 2024 17:48:02 +0100 Subject: [PATCH 18/60] mempool: update_transaction stub --- .../src/fork_aware_txpool/tx_mem_pool.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs index 4a7c3a22b18fa..5b6c9c56a0441 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs @@ -26,7 +26,10 @@ //! it), while on other forks tx can be valid. Depending on which view is chosen to be cloned, //! such transaction could not be present in the newly created view. -use super::{metrics::MetricsLink as PrometheusMetrics, multi_view_listener::MultiViewListener}; +use super::{ + metrics::MetricsLink as PrometheusMetrics, multi_view_listener::MultiViewListener, + view_store::ViewStoreSubmitOutcome, +}; use crate::{ common::log_xt::log_xt_trace, graph, @@ -487,6 +490,10 @@ where }); self.listener.invalidate_transactions(&invalid_hashes); } + + pub(super) fn update_transaction(&self, outcome: &ViewStoreSubmitOutcome) { + // todo!() + } } #[cfg(test)] From c411bb4e4345b501fce87add581034724e61286c Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Fri, 29 Nov 2024 17:50:01 +0100 Subject: [PATCH 19/60] fatp: SubmitOutcome --- .../fork_aware_txpool/fork_aware_txpool.rs | 35 +++++++++++++++---- 1 file changed, 28 insertions(+), 7 deletions(-) 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 0ad6ffd755223..0ff77f7d52c05 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 @@ -602,7 +602,7 @@ where /// out: /// [ Ok(xth0), Ok(xth1), Err ] /// ``` -fn reduce_multiview_result(input: HashMap>>) -> Vec> { +fn reduce_multiview_result(input: HashMap>>) -> Vec> { let mut values = input.values(); let Some(first) = values.next() else { return Default::default(); @@ -684,6 +684,10 @@ where }) }) }) + .map(|r| r.map(|r| { + mempool.update_transaction(&r); + r.hash() + })) .collect::>()) } @@ -727,9 +731,16 @@ where self.metrics.report(|metrics| metrics.submitted_transactions.inc()); - self.view_store.submit_and_watch(at, timed_source, xt).await.inspect_err(|_| { - self.mempool.remove_transaction(&xt_hash); - }) + self.view_store + .submit_and_watch(at, timed_source, xt) + .await + .inspect_err(|_| { + self.mempool.remove_transaction(&xt_hash); + }) + .map(|mut outcome| { + self.mempool.update_transaction(&outcome); + outcome.expect_watcher() + }) } /// Intended to remove transactions identified by the given hashes, and any dependent @@ -863,7 +874,13 @@ where .extend_unwatched(TransactionSource::Local, &[xt.clone()]) .remove(0)?; - self.view_store.submit_local(xt).or_else(|_| Ok(xt_hash)) + self.view_store + .submit_local(xt) + .map(|outcome| { + self.mempool.update_transaction(&outcome); + outcome.hash() + }) + .or_else(|_| Ok(xt_hash)) } } @@ -1115,7 +1132,11 @@ where .await .into_iter() .zip(hashes) - .map(|(result, tx_hash)| result.or_else(|_| Err(tx_hash))) + .map(|(result, tx_hash)| { + result + .map(|outcome| self.mempool.update_transaction(&outcome.into())) + .or_else(|_| Err(tx_hash)) + }) .collect::>(); let submitted_count = watched_results.len(); @@ -1490,7 +1511,7 @@ mod reduce_multiview_result_tests { fn empty() { sp_tracing::try_init_simple(); let input = HashMap::default(); - let r = reduce_multiview_result::(input); + let r = reduce_multiview_result::(input); assert!(r.is_empty()); } From 7b461bff26c085d9c0fbaac777a41f049386c10a Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Fri, 29 Nov 2024 17:51:02 +0100 Subject: [PATCH 20/60] fatp: todo added --- .../transaction-pool/src/fork_aware_txpool/fork_aware_txpool.rs | 2 ++ 1 file changed, 2 insertions(+) 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 0ff77f7d52c05..f25f837d31ef7 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 @@ -1133,6 +1133,8 @@ where .into_iter() .zip(hashes) .map(|(result, tx_hash)| { + //todo: we may need a bool flag here indicating if we need to update priority + //(premature optimization) result .map(|outcome| self.mempool.update_transaction(&outcome.into())) .or_else(|_| Err(tx_hash)) From 8765d2ce9e9d6393707019c77656fcabec56b452 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Fri, 29 Nov 2024 17:51:53 +0100 Subject: [PATCH 21/60] single-state txpool: SubmitOutcome integration --- .../single_state_txpool.rs | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/substrate/client/transaction-pool/src/single_state_txpool/single_state_txpool.rs b/substrate/client/transaction-pool/src/single_state_txpool/single_state_txpool.rs index e7504012ca67b..0fcdfb2cddc8b 100644 --- a/substrate/client/transaction-pool/src/single_state_txpool/single_state_txpool.rs +++ b/substrate/client/transaction-pool/src/single_state_txpool/single_state_txpool.rs @@ -266,7 +266,12 @@ where let number = self.api.resolve_block_number(at); let at = HashAndNumber { hash: at, number: number? }; - Ok(pool.submit_at(&at, xts).await) + Ok(pool + .submit_at(&at, xts) + .await + .into_iter() + .map(|result| result.map(|outcome| outcome.hash())) + .collect()) } async fn submit_one( @@ -284,6 +289,7 @@ where let at = HashAndNumber { hash: at, number: number? }; pool.submit_one(&at, TimedTransactionSource::from_transaction_source(source, false), xt) .await + .map(|outcome| outcome.hash()) } async fn submit_and_watch( @@ -300,15 +306,13 @@ where let number = self.api.resolve_block_number(at); let at = HashAndNumber { hash: at, number: number? }; - let watcher = pool - .submit_and_watch( - &at, - TimedTransactionSource::from_transaction_source(source, false), - xt, - ) - .await?; - - Ok(watcher.into_stream().boxed()) + pool.submit_and_watch( + &at, + TimedTransactionSource::from_transaction_source(source, false), + xt, + ) + .await + .map(|mut outcome| outcome.expect_watcher().into_stream().boxed()) } fn remove_invalid(&self, hashes: &[TxHash]) -> Vec> { @@ -476,7 +480,11 @@ where validity, ); - self.pool.validated_pool().submit(vec![validated]).remove(0) + self.pool + .validated_pool() + .submit(vec![validated]) + .remove(0) + .map(|outcome| outcome.hash()) } } From e8ccd44c09b767d2a07ff71774cbbf1b09309479 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Fri, 29 Nov 2024 17:52:45 +0100 Subject: [PATCH 22/60] tests: SubmitOutcome fixes --- .../client/transaction-pool/src/graph/pool.rs | 70 +++++++++++++------ .../src/single_state_txpool/revalidation.rs | 5 +- .../client/transaction-pool/tests/pool.rs | 14 ++-- 3 files changed, 61 insertions(+), 28 deletions(-) diff --git a/substrate/client/transaction-pool/src/graph/pool.rs b/substrate/client/transaction-pool/src/graph/pool.rs index dcab98a33ffea..2d049eb411723 100644 --- a/substrate/client/transaction-pool/src/graph/pool.rs +++ b/substrate/client/transaction-pool/src/graph/pool.rs @@ -516,6 +516,7 @@ mod tests { .into(), ), ) + .map(|outcome| outcome.hash()) .unwrap(); // then @@ -544,7 +545,10 @@ mod tests { // when let txs = txs.into_iter().map(|x| (SOURCE, Arc::from(x))).collect::>(); - let hashes = block_on(pool.submit_at(&api.expect_hash_and_number(0), txs)); + let hashes = block_on(pool.submit_at(&api.expect_hash_and_number(0), txs)) + .into_iter() + .map(|r| r.map(|o| o.hash())) + .collect::>(); log::debug!("--> {hashes:#?}"); // then @@ -568,7 +572,8 @@ mod tests { // when pool.validated_pool.ban(&Instant::now(), vec![pool.hash_of(&uxt)]); - let res = block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, uxt.into())); + let res = block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, uxt.into())) + .map(|o| o.hash()); assert_eq!(pool.validated_pool().status().ready, 0); assert_eq!(pool.validated_pool().status().future, 0); @@ -591,7 +596,8 @@ mod tests { let uxt = ExtrinsicBuilder::new_include_data(vec![42]).build(); // when - let res = block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, uxt.into())); + let res = block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, uxt.into())) + .map(|o| o.hash()); // then assert_matches!(res.unwrap_err(), error::Error::Unactionable); @@ -619,7 +625,8 @@ mod tests { .into(), ), ) - .unwrap(); + .unwrap() + .hash(); let hash1 = block_on( pool.submit_one( &han_of_block0, @@ -633,7 +640,8 @@ mod tests { .into(), ), ) - .unwrap(); + .unwrap() + .hash(); // future doesn't count let _hash = block_on( pool.submit_one( @@ -648,7 +656,8 @@ mod tests { .into(), ), ) - .unwrap(); + .unwrap() + .hash(); assert_eq!(pool.validated_pool().status().ready, 2); assert_eq!(pool.validated_pool().status().future, 1); @@ -681,7 +690,8 @@ mod tests { .into(), ), ) - .unwrap(); + .unwrap() + .hash(); let hash2 = block_on( pool.submit_one( &han_of_block0, @@ -695,7 +705,8 @@ mod tests { .into(), ), ) - .unwrap(); + .unwrap() + .hash(); let hash3 = block_on( pool.submit_one( &han_of_block0, @@ -709,7 +720,8 @@ mod tests { .into(), ), ) - .unwrap(); + .unwrap() + .hash(); // when pool.validated_pool.clear_stale(&api.expect_hash_and_number(5)); @@ -741,7 +753,8 @@ mod tests { .into(), ), ) - .unwrap(); + .unwrap() + .hash(); // when block_on(pool.prune_tags(&api.expect_hash_and_number(1), vec![vec![0]], vec![hash1])); @@ -769,8 +782,9 @@ mod tests { let api = Arc::new(TestApi::default()); let pool = Pool::new(options, true.into(), api.clone()); - let hash1 = - block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, xt.into())).unwrap(); + let hash1 = block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, xt.into())) + .unwrap() + .hash(); assert_eq!(pool.validated_pool().status().future, 1); // when @@ -787,7 +801,8 @@ mod tests { .into(), ), ) - .unwrap(); + .unwrap() + .hash(); // then assert_eq!(pool.validated_pool().status().future, 1); @@ -819,6 +834,7 @@ mod tests { .into(), ), ) + .map(|o| o.hash()) .unwrap_err(); // then @@ -845,6 +861,7 @@ mod tests { .into(), ), ) + .map(|o| o.hash()) .unwrap_err(); // then @@ -873,7 +890,8 @@ mod tests { .into(), ), ) - .unwrap(); + .unwrap() + .expect_watcher(); assert_eq!(pool.validated_pool().status().ready, 1); assert_eq!(pool.validated_pool().status().future, 0); @@ -910,7 +928,8 @@ mod tests { .into(), ), ) - .unwrap(); + .unwrap() + .expect_watcher(); assert_eq!(pool.validated_pool().status().ready, 1); assert_eq!(pool.validated_pool().status().future, 0); @@ -949,7 +968,8 @@ mod tests { .into(), ), ) - .unwrap(); + .unwrap() + .expect_watcher(); assert_eq!(pool.validated_pool().status().ready, 0); assert_eq!(pool.validated_pool().status().future, 1); @@ -988,7 +1008,8 @@ mod tests { }); let watcher = block_on(pool.submit_and_watch(&api.expect_hash_and_number(0), SOURCE, uxt.into())) - .unwrap(); + .unwrap() + .expect_watcher(); assert_eq!(pool.validated_pool().status().ready, 1); // when @@ -1013,7 +1034,8 @@ mod tests { }); let watcher = block_on(pool.submit_and_watch(&api.expect_hash_and_number(0), SOURCE, uxt.into())) - .unwrap(); + .unwrap() + .expect_watcher(); assert_eq!(pool.validated_pool().status().ready, 1); // when @@ -1046,7 +1068,8 @@ mod tests { }); let watcher = block_on(pool.submit_and_watch(&api.expect_hash_and_number(0), SOURCE, xt.into())) - .unwrap(); + .unwrap() + .expect_watcher(); assert_eq!(pool.validated_pool().status().ready, 1); // when @@ -1113,7 +1136,9 @@ mod tests { // after validation `IncludeData` will have priority set to 9001 // (validate_transaction mock) let xt = ExtrinsicBuilder::new_include_data(Vec::new()).build(); - block_on(pool.submit_and_watch(&han_of_block0, SOURCE, xt.into())).unwrap(); + block_on(pool.submit_and_watch(&han_of_block0, SOURCE, xt.into())) + .unwrap() + .expect_watcher(); assert_eq!(pool.validated_pool().status().ready, 1); // after validation `Transfer` will have priority set to 4 (validate_transaction @@ -1124,8 +1149,9 @@ mod tests { amount: 5, nonce: 0, }); - let watcher = - block_on(pool.submit_and_watch(&han_of_block0, SOURCE, xt.into())).unwrap(); + let watcher = block_on(pool.submit_and_watch(&han_of_block0, SOURCE, xt.into())) + .unwrap() + .expect_watcher(); assert_eq!(pool.validated_pool().status().ready, 2); // when diff --git a/substrate/client/transaction-pool/src/single_state_txpool/revalidation.rs b/substrate/client/transaction-pool/src/single_state_txpool/revalidation.rs index 74031b1e1c729..eba0277c29a8e 100644 --- a/substrate/client/transaction-pool/src/single_state_txpool/revalidation.rs +++ b/substrate/client/transaction-pool/src/single_state_txpool/revalidation.rs @@ -401,7 +401,8 @@ mod tests { TimedTransactionSource::new_external(false), uxt.clone().into(), )) - .expect("Should be valid"); + .expect("Should be valid") + .hash(); block_on(queue.revalidate_later(han_of_block0.hash, vec![uxt_hash])); @@ -440,7 +441,7 @@ mod tests { vec![(source.clone(), uxt0.into()), (source, uxt1.into())], )) .into_iter() - .map(|r| r.expect("Should be valid")) + .map(|r| r.expect("Should be valid").hash()) .collect::>(); assert_eq!(api.validation_requests().len(), 2); diff --git a/substrate/client/transaction-pool/tests/pool.rs b/substrate/client/transaction-pool/tests/pool.rs index e556ba9875f11..e4fd1b37c1f1c 100644 --- a/substrate/client/transaction-pool/tests/pool.rs +++ b/substrate/client/transaction-pool/tests/pool.rs @@ -158,6 +158,7 @@ fn prune_tags_should_work() { let (pool, api) = pool(); let hash209 = block_on(pool.submit_one(&api.expect_hash_and_number(0), TSOURCE, uxt(Alice, 209).into())) + .map(|o| o.hash()) .unwrap(); block_on(pool.submit_one(&api.expect_hash_and_number(0), TSOURCE, uxt(Alice, 210).into())) .unwrap(); @@ -184,10 +185,13 @@ fn prune_tags_should_work() { fn should_ban_invalid_transactions() { let (pool, api) = pool(); let uxt = Arc::from(uxt(Alice, 209)); - let hash = - block_on(pool.submit_one(&api.expect_hash_and_number(0), TSOURCE, uxt.clone())).unwrap(); + let hash = block_on(pool.submit_one(&api.expect_hash_and_number(0), TSOURCE, uxt.clone())) + .unwrap() + .hash(); pool.validated_pool().remove_invalid(&[hash]); - block_on(pool.submit_one(&api.expect_hash_and_number(0), TSOURCE, uxt.clone())).unwrap_err(); + block_on(pool.submit_one(&api.expect_hash_and_number(0), TSOURCE, uxt.clone())) + .map(|_| ()) + .unwrap_err(); // when let pending: Vec<_> = pool @@ -198,7 +202,9 @@ fn should_ban_invalid_transactions() { assert_eq!(pending, Vec::::new()); // then - block_on(pool.submit_one(&api.expect_hash_and_number(0), TSOURCE, uxt.clone())).unwrap_err(); + block_on(pool.submit_one(&api.expect_hash_and_number(0), TSOURCE, uxt.clone())) + .map(|_| ()) + .unwrap_err(); } #[test] From 6cca27201c5222b68a9ac139a12272bf5435ac80 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Fri, 29 Nov 2024 17:53:11 +0100 Subject: [PATCH 23/60] mempool: sizes fix --- .../transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs index 5b6c9c56a0441..96a1a80fe7612 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs @@ -288,13 +288,14 @@ where tx: TxInMemPool, tx_to_be_removed: Option>, ) -> Result>, sc_transaction_pool_api::error::Error> { - let bytes = self.transactions.bytes(); let mut transactions = self.transactions.write(); tx_to_be_removed.inspect(|hash| { transactions.remove(hash); }); + let bytes = self.transactions.bytes(); + let result = match ( self.is_limit_exceeded(transactions.len() + 1, bytes + tx.bytes), transactions.contains_key(&hash), From 3b17a169800fad6e76cc4290ca7bc3b03ba15e8b Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Tue, 3 Dec 2024 19:17:33 +0100 Subject: [PATCH 24/60] dropping transaction - size limit is properly obeyed now --- .../fork_aware_txpool/fork_aware_txpool.rs | 63 ++++---- .../src/fork_aware_txpool/tx_mem_pool.rs | 139 ++++++++++++++++-- .../src/fork_aware_txpool/view_store.rs | 5 +- .../transaction-pool/src/graph/tracked_map.rs | 10 ++ .../src/graph/validated_pool.rs | 24 +-- .../transaction-pool/tests/fatp_prios.rs | 16 +- 6 files changed, 193 insertions(+), 64 deletions(-) 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 f25f837d31ef7..6b8a6be224584 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 @@ -643,7 +643,7 @@ where /// are reduced to single result. Refer to `reduce_multiview_result` for more details. async fn submit_at( &self, - _: ::Hash, + at: ::Hash, source: TransactionSource, xts: Vec>, ) -> Result, Self::Error>>, Self::Error> { @@ -657,6 +657,21 @@ where return Ok(mempool_results.into_iter().map(|r| r.map(|r| r.hash)).collect::>()) } + //todo: review + test maybe? + let retries = mempool_results + .into_iter() + .zip(xts.clone()) + .map(|(result, xt)| async move { + match result { + Err(TxPoolApiError::ImmediatelyDropped) => + self.attempt_transaction_replacement(at, source, false, xt).await, + result @ _ => result, + } + }) + .collect::>(); + + let mempool_results = futures::future::join_all(retries).await; + let to_be_submitted = mempool_results .iter() .zip(xts) @@ -721,7 +736,7 @@ where log::trace!(target: LOG_TARGET, "[{:?}] fatp::submit_and_watch views:{}", self.tx_hash(&xt), self.active_views_count()); let xt = Arc::from(xt); - let InsertionInfo { hash: xt_hash, source: timed_source } = + let InsertionInfo { hash: xt_hash, source: timed_source, .. } = match self.mempool.push_watched(source, xt.clone()) { Ok(result) => result, Err(TxPoolApiError::ImmediatelyDropped) => @@ -1333,33 +1348,31 @@ where ) .await; - // 3. check if most_recent_view contains a transaction with lower priority (actually worse - - // do we want to check timestamp too - no: see #4609?) - // Would be perfect to choose transaction with lowest the number of dependant txs in its - // subtree. - if let Some(worst_tx_hash) = - best_view.pool.validated_pool().find_transaction_with_lower_prio(validated_tx) - { + if let Some(priority) = validated_tx.priority() { + // 3. check if mempool contains a transaction with lower priority (actually worse - do + // we want to check timestamp too - no: see #4609?) Would be perfect to choose + // transaction with lowest the number of dependant txs in its subtree. // 4. if yes - remove worse transaction from mempool and add new one. - log::trace!(target: LOG_TARGET, "found candidate for removal: {worst_tx_hash:?} replaced by {tx_hash:?}"); let insertion_info = - self.mempool.try_replace_transaction(xt, source, watched, worst_tx_hash)?; - - // 5. notify listner - self.view_store - .listener - .transaction_dropped(DroppedTransaction::new_enforced_by_limts(worst_tx_hash)); - - // 6. remove transaction from the view_store - self.view_store.remove_transaction_subtree( - worst_tx_hash, - |listener, removed_tx_hash| { - listener.limits_enforced(&removed_tx_hash); - }, - ); + self.mempool.try_replace_transaction(xt, priority, source, watched)?; + + for worst_hash in &insertion_info.removed { + log::trace!(target: LOG_TARGET, "removed: {worst_hash:?} replaced by {tx_hash:?}"); + // 5. notify listner + self.view_store + .listener + .transaction_dropped(DroppedTransaction::new_enforced_by_limts(*worst_hash)); + + // 6. remove transaction from the view_store + self.view_store.remove_transaction_subtree( + *worst_hash, + |listener, removed_tx_hash| { + listener.limits_enforced(&removed_tx_hash); + }, + ); + } // 8. add to pending_replacements - make sure it will not sneak back via cloned view - // 9. subemit new one to the view, this will be done upon in the caller return Ok(insertion_info) } diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs index 96a1a80fe7612..c851af111e322 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs @@ -38,15 +38,19 @@ use crate::{ }; use futures::FutureExt; use itertools::Itertools; -use sc_transaction_pool_api::TransactionSource; +use sc_transaction_pool_api::{TransactionPriority, TransactionSource}; use sp_blockchain::HashAndNumber; use sp_runtime::{ traits::Block as BlockT, transaction_validity::{InvalidTransaction, TransactionValidityError}, }; use std::{ + cmp::Ordering, collections::HashMap, - sync::{atomic, atomic::AtomicU64, Arc}, + sync::{ + atomic::{self, AtomicU64}, + Arc, + }, time::Instant, }; @@ -80,6 +84,9 @@ where source: TimedTransactionSource, /// When the transaction was revalidated, used to periodically revalidate the mem pool buffer. validated_at: AtomicU64, + /// Priority of transaction at some block. It is assumed it will not be changed often. + //todo: Option is needed here. This means lock. So maybe +AtomicBool? + priority: AtomicU64, //todo: we need to add future / ready status at finalized block. //If future transactions are stuck in tx_mem_pool (due to limits being hit), we need a means // to replace them somehow with newly coming transactions. @@ -112,12 +119,23 @@ where Self::new(true, source, tx, bytes) } - /// Creates a new instance of wrapper for a transaction. + /// Creates a new instance of wrapper for a transaction with no priority. fn new( watched: bool, source: TransactionSource, tx: ExtrinsicFor, bytes: usize, + ) -> Self { + Self::new_with_priority(watched, source, tx, bytes, 0) + } + + /// Creates a new instance of wrapper for a transaction with given priority. + fn new_with_priority( + watched: bool, + source: TransactionSource, + tx: ExtrinsicFor, + bytes: usize, + priority: TransactionPriority, ) -> Self { Self { watched, @@ -125,6 +143,7 @@ where source: TimedTransactionSource::from_transaction_source(source, true), validated_at: AtomicU64::new(0), bytes, + priority: AtomicU64::new(priority), } } @@ -139,6 +158,11 @@ where pub(crate) fn source(&self) -> TimedTransactionSource { self.source.clone() } + + /// Returns the priority of the transaction. + pub(crate) fn priority(&self) -> TransactionPriority { + self.priority.load(atomic::Ordering::Relaxed) + } } impl Size for Arc> @@ -198,11 +222,15 @@ where pub(super) struct InsertionInfo { pub(super) hash: Hash, pub(super) source: TimedTransactionSource, + pub(super) removed: Vec, } impl InsertionInfo { fn new(hash: Hash, source: TimedTransactionSource) -> Self { - Self { hash, source } + Self::new_with_removed(hash, source, Default::default()) + } + fn new_with_removed(hash: Hash, source: TimedTransactionSource, removed: Vec) -> Self { + Self { hash, source, removed } } } @@ -286,14 +314,9 @@ where &self, hash: ExtrinsicHash, tx: TxInMemPool, - tx_to_be_removed: Option>, ) -> Result>, sc_transaction_pool_api::error::Error> { let mut transactions = self.transactions.write(); - tx_to_be_removed.inspect(|hash| { - transactions.remove(hash); - }); - let bytes = self.transactions.bytes(); let result = match ( @@ -314,6 +337,85 @@ where result } + /// Attempts to insert a new transaction in the memory pool and drop some worse existing + /// transactions. + /// + /// This operation will not overflow the limit of the mempool. It means that cumulative + /// size of removed transactions will be equal (or greated) then size of newly inserted + /// transaction. + /// + /// Returns a `Result` containing `InsertionInfo` if the new transaction is successfully + /// inserted; otherwise, returns an appropriate error indicating the failure. + pub(super) fn try_insert_with_dropping( + &self, + hash: ExtrinsicHash, + new_tx: TxInMemPool, + ) -> Result>, sc_transaction_pool_api::error::Error> { + let mut transactions = self.transactions.write(); + + if transactions.contains_key(&hash) { + return Err(sc_transaction_pool_api::error::Error::AlreadyImported(Box::new(hash))); + } + + let mut sorted = + transactions.iter().map(|(h, v)| (h.clone(), v.clone())).collect::>(); + + // When pushing higher prio transaction, we need to find a number of lower prio txs, such + // that the sum of their bytes is ge then size of new tx. Otherwise we could overflow size + // limits. Naive way to do it - rev-sort by priority and eat the tail. + + // reverse (lowest prio last) + sorted.sort_by(|(_, a), (_, b)| match b.priority().cmp(&a.priority()) { + Ordering::Equal => match (a.source.timestamp, b.source.timestamp) { + (Some(a), Some(b)) => b.cmp(&a), + _ => Ordering::Equal, + }, + ordering @ _ => ordering, + }); + + let required_size = new_tx.bytes; + let mut total_size = 0usize; + let mut to_be_removed = vec![]; + + while total_size < required_size { + let Some((worst_hash, worst_tx)) = sorted.pop() else { + return Err(sc_transaction_pool_api::error::Error::ImmediatelyDropped); + }; + + if worst_tx.priority() >= new_tx.priority() { + return Err(sc_transaction_pool_api::error::Error::ImmediatelyDropped); + } + + total_size += worst_tx.bytes; + to_be_removed.push(worst_hash); + } + + let source = new_tx.source(); + transactions.insert(hash, Arc::from(new_tx)); + for worst_hash in &to_be_removed { + transactions.remove(worst_hash); + } + debug_assert!(!self.is_limit_exceeded(transactions.len(), self.transactions.bytes())); + + Ok(InsertionInfo::new_with_removed(hash, source, to_be_removed)) + + // let result = match ( + // self.is_limit_exceeded(transactions.len() + 1, bytes + tx.bytes), + // transactions.contains_key(&hash), + // ) { + // (false, false) => { + // let source = tx.source(); + // transactions.insert(hash, Arc::from(tx)); + // Ok(InsertionInfo::new(hash, source)) + // }, + // (_, true) => + // Err(sc_transaction_pool_api::error::Error::AlreadyImported(Box::new(hash))), + // (true, _) => Err(sc_transaction_pool_api::error::Error::ImmediatelyDropped), + // }; + // log::trace!(target: LOG_TARGET, "[{:?}] mempool::try_insert: {:?}", hash, + // result.as_ref().map(|r| r.hash)); + } + /// Attempts to replace an existing transaction in the memory pool with a new one. /// /// Returns a `Result` containing `InsertionInfo` if the new transaction is successfully @@ -321,15 +423,14 @@ where pub(super) fn try_replace_transaction( &self, new_xt: ExtrinsicFor, + priority: TransactionPriority, source: TransactionSource, watched: bool, - replaced_tx_hash: ExtrinsicHash, ) -> Result>, sc_transaction_pool_api::error::Error> { let (hash, length) = self.api.hash_and_length(&new_xt); - self.try_insert( + self.try_insert_with_dropping( hash, - TxInMemPool::new(watched, source, new_xt, length), - Some(replaced_tx_hash), + TxInMemPool::new_with_priority(watched, source, new_xt, length, priority), ) } @@ -347,7 +448,7 @@ where .iter() .map(|xt| { let (hash, length) = self.api.hash_and_length(&xt); - self.try_insert(hash, TxInMemPool::new_unwatched(source, xt.clone(), length), None) + self.try_insert(hash, TxInMemPool::new_unwatched(source, xt.clone(), length)) }) .collect::>(); result @@ -361,7 +462,7 @@ where xt: ExtrinsicFor, ) -> Result>, sc_transaction_pool_api::error::Error> { let (hash, length) = self.api.hash_and_length(&xt); - self.try_insert(hash, TxInMemPool::new_watched(source, xt.clone(), length), None) + self.try_insert(hash, TxInMemPool::new_watched(source, xt.clone(), length)) } /// Clones and returns a `HashMap` of references to all unwatched transactions in the memory @@ -493,7 +594,11 @@ where } pub(super) fn update_transaction(&self, outcome: &ViewStoreSubmitOutcome) { - // todo!() + if let Some(priority) = outcome.priority() { + if let Some(tx) = self.transactions.write().get_mut(&outcome.hash()) { + tx.priority.store(priority, atomic::Ordering::Relaxed); + } + } } } @@ -650,4 +755,6 @@ mod tx_mem_pool_tests { sc_transaction_pool_api::error::Error::ImmediatelyDropped )); } + + //add some test for try_insert_with_dropping } 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 4ae54e99264ce..89bf47cc38950 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 @@ -262,7 +262,7 @@ where log::trace!(target: LOG_TARGET, "[{:?}] submit_local: err: {}", tx_hash, err); Err(err) }, - None => Ok(ViewStoreSubmitOutcome::new_no_priority(tx_hash)), + None => Ok(ViewStoreSubmitOutcome::new(tx_hash, None)), Some(Ok(r)) => Ok(r.into()), } } @@ -320,8 +320,7 @@ where }, Some(Ok(result)) => Ok(ViewStoreSubmitOutcome::from(result).with_watcher(external_watcher)), - None => - Ok(ViewStoreSubmitOutcome::new_no_priority(tx_hash).with_watcher(external_watcher)), + None => Ok(ViewStoreSubmitOutcome::new(tx_hash, None).with_watcher(external_watcher)), } } diff --git a/substrate/client/transaction-pool/src/graph/tracked_map.rs b/substrate/client/transaction-pool/src/graph/tracked_map.rs index 6c3bbbf34b553..818c1ebe544ff 100644 --- a/substrate/client/transaction-pool/src/graph/tracked_map.rs +++ b/substrate/client/transaction-pool/src/graph/tracked_map.rs @@ -173,6 +173,16 @@ where pub fn len(&mut self) -> usize { self.inner_guard.len() } + + /// Returns an iterator over all values. + pub fn values(&self) -> std::collections::hash_map::Values { + self.inner_guard.values() + } + + /// Returns an iterator over all key-value pairs. + pub fn iter(&self) -> Iter<'_, K, V> { + self.inner_guard.iter() + } } #[cfg(test)] diff --git a/substrate/client/transaction-pool/src/graph/validated_pool.rs b/substrate/client/transaction-pool/src/graph/validated_pool.rs index 720a806985381..0c0317750f85a 100644 --- a/substrate/client/transaction-pool/src/graph/validated_pool.rs +++ b/substrate/client/transaction-pool/src/graph/validated_pool.rs @@ -76,6 +76,14 @@ impl ValidatedTransaction { valid_till: at.saturated_into::().saturating_add(validity.longevity), }) } + + /// Returns priority for valid transaction, None if transaction is not valid. + pub fn priority(&self) -> Option { + match self { + ValidatedTransaction::Valid(base::Transaction { priority, .. }) => Some(*priority), + _ => None, + } + } } /// A type of validated transaction stored in the validated pool. @@ -107,8 +115,9 @@ pub struct BaseSubmitOutcome { hash: ExtrinsicHash, /// A transaction watcher. This is `Some` for `submit_and_watch` and `None` for `submit`. watcher: Option, - /// The priority of the transaction. Defaults to zero if unknown. - priority: TransactionPriority, + + /// The priority of the transaction. Defaults to None if unknown. + priority: Option, } /// Type alias to outcome of submission to `ValidatedPool`. @@ -117,15 +126,10 @@ pub type ValidatedPoolSubmitOutcome = impl BaseSubmitOutcome { /// Creates a new instance with given hash and priority. - pub fn new(hash: ExtrinsicHash, priority: TransactionPriority) -> Self { + pub fn new(hash: ExtrinsicHash, priority: Option) -> Self { Self { hash, priority, watcher: None } } - /// Creates a new instance with given hash and zeroed priority. - pub fn new_no_priority(hash: ExtrinsicHash) -> Self { - Self { hash, priority: 0u64, watcher: None } - } - /// Sets the transaction watcher. pub fn with_watcher(mut self, watcher: W) -> Self { self.watcher = Some(watcher); @@ -133,7 +137,7 @@ impl BaseSubmitOutcome { } /// Provides priority of submitted transaction. - pub fn priority(&self) -> TransactionPriority { + pub fn priority(&self) -> Option { self.priority } @@ -283,7 +287,7 @@ impl ValidatedPool { let mut listener = self.listener.write(); fire_events(&mut *listener, &imported); - Ok(ValidatedPoolSubmitOutcome::new(*imported.hash(), priority)) + Ok(ValidatedPoolSubmitOutcome::new(*imported.hash(), Some(priority))) }, ValidatedTransaction::Invalid(hash, err) => { log::trace!(target: LOG_TARGET, "[{:?}] ValidatedPool::submit_one invalid: {:?}", hash, err); diff --git a/substrate/client/transaction-pool/tests/fatp_prios.rs b/substrate/client/transaction-pool/tests/fatp_prios.rs index 3c7db35323d8f..abd8ebec22965 100644 --- a/substrate/client/transaction-pool/tests/fatp_prios.rs +++ b/substrate/client/transaction-pool/tests/fatp_prios.rs @@ -282,10 +282,8 @@ fn fatp_prios_watcher_full_mempool_higher_prio_is_accepted() { api.set_priority(&xt4, 5); api.set_priority(&xt5, 6); - let _xt0_watcher = - block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); - let _xt1_watcher = - block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap(); + let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); + let xt1_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap(); assert_pool_status!(header01.hash(), &pool, 2, 0); assert_eq!(pool.mempool_len().1, 2); @@ -313,13 +311,11 @@ fn fatp_prios_watcher_full_mempool_higher_prio_is_accepted() { // let header04 = api.push_block_with_parent(header03.hash(), vec![], true); // block_on(pool.maintain(new_best_block_event(&pool, Some(header03.hash()), header04.hash()))); - let xt2_status = futures::executor::block_on_stream(xt2_watcher).take(2).collect::>(); - assert_eq!(xt2_status, vec![TransactionStatus::Ready, TransactionStatus::Dropped]); - let xt3_status = futures::executor::block_on_stream(xt3_watcher).take(2).collect::>(); - assert_eq!(xt3_status, vec![TransactionStatus::Ready, TransactionStatus::Dropped]); + assert_watcher_stream!(xt0_watcher, [TransactionStatus::Ready, TransactionStatus::Dropped]); + assert_watcher_stream!(xt1_watcher, [TransactionStatus::Ready, TransactionStatus::Dropped]); - assert_ready_iterator!(header01.hash(), pool, [xt1, xt0]); - assert_ready_iterator!(header02.hash(), pool, []); + assert_ready_iterator!(header01.hash(), pool, []); + assert_ready_iterator!(header02.hash(), pool, [xt3, xt2]); assert_ready_iterator!(header03.hash(), pool, [xt5, xt4]); } From 4f767e59a37b759c8588148671fb52c4fc236d2c Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Wed, 4 Dec 2024 10:54:58 +0100 Subject: [PATCH 25/60] merge / rebase fixes --- .../src/fork_aware_txpool/dropped_watcher.rs | 2 +- .../src/fork_aware_txpool/fork_aware_txpool.rs | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/dropped_watcher.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/dropped_watcher.rs index 48767ad616050..93fbb79ce2618 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/dropped_watcher.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/dropped_watcher.rs @@ -208,7 +208,7 @@ where views.remove(&key); //todo: merge heads up warning! if views.is_empty() { - ctx.pending_dropped_transactions.push(*tx_hash); + self.pending_dropped_transactions.push(*tx_hash); } }); 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 6b8a6be224584..3f8d66110432b 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 @@ -654,7 +654,10 @@ where let mempool_results = self.mempool.extend_unwatched(source, &xts); if view_store.is_empty() { - return Ok(mempool_results.into_iter().map(|r| r.map(|r| r.hash)).collect::>()) + return Ok(mempool_results + .into_iter() + .map(|r| r.map(|r| r.hash).map_err(Into::into)) + .collect::>()) } //todo: review + test maybe? @@ -690,7 +693,9 @@ where Ok(mempool_results .into_iter() .map(|result| { - result.and_then(|insertion| { + result + .map_err(Into::into) + .and_then(|insertion| { submission_results .next() .expect("The number of Ok results in mempool is exactly the same as the size of to-views-submission result. qed.") From 6ba133e3bf9872bdf8402e20c99f830132913668 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Wed, 4 Dec 2024 11:44:18 +0100 Subject: [PATCH 26/60] mempool: prio is now locked option --- .../src/fork_aware_txpool/tx_mem_pool.rs | 43 +++++++++++++------ 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs index c851af111e322..84963652ffa68 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs @@ -38,6 +38,7 @@ use crate::{ }; use futures::FutureExt; use itertools::Itertools; +use parking_lot::RwLock; use sc_transaction_pool_api::{TransactionPriority, TransactionSource}; use sp_blockchain::HashAndNumber; use sp_runtime::{ @@ -84,9 +85,9 @@ where source: TimedTransactionSource, /// When the transaction was revalidated, used to periodically revalidate the mem pool buffer. validated_at: AtomicU64, - /// Priority of transaction at some block. It is assumed it will not be changed often. - //todo: Option is needed here. This means lock. So maybe +AtomicBool? - priority: AtomicU64, + /// Priority of transaction at some block. It is assumed it will not be changed often. None if + /// not known. + priority: RwLock>, //todo: we need to add future / ready status at finalized block. //If future transactions are stuck in tx_mem_pool (due to limits being hit), we need a means // to replace them somehow with newly coming transactions. @@ -126,7 +127,7 @@ where tx: ExtrinsicFor, bytes: usize, ) -> Self { - Self::new_with_priority(watched, source, tx, bytes, 0) + Self::new_with_optional_priority(watched, source, tx, bytes, None) } /// Creates a new instance of wrapper for a transaction with given priority. @@ -136,6 +137,17 @@ where tx: ExtrinsicFor, bytes: usize, priority: TransactionPriority, + ) -> Self { + Self::new_with_optional_priority(watched, source, tx, bytes, Some(priority)) + } + + /// Creates a new instance of wrapper for a transaction with given priority. + fn new_with_optional_priority( + watched: bool, + source: TransactionSource, + tx: ExtrinsicFor, + bytes: usize, + priority: Option, ) -> Self { Self { watched, @@ -143,7 +155,7 @@ where source: TimedTransactionSource::from_transaction_source(source, true), validated_at: AtomicU64::new(0), bytes, - priority: AtomicU64::new(priority), + priority: priority.into(), } } @@ -160,8 +172,8 @@ where } /// Returns the priority of the transaction. - pub(crate) fn priority(&self) -> TransactionPriority { - self.priority.load(atomic::Ordering::Relaxed) + pub(crate) fn priority(&self) -> Option { + *self.priority.read() } } @@ -357,8 +369,10 @@ where return Err(sc_transaction_pool_api::error::Error::AlreadyImported(Box::new(hash))); } - let mut sorted = - transactions.iter().map(|(h, v)| (h.clone(), v.clone())).collect::>(); + let mut sorted = transactions + .iter() + .filter_map(|(h, v)| v.priority().map(|_| (h.clone(), v.clone()))) + .collect::>(); // When pushing higher prio transaction, we need to find a number of lower prio txs, such // that the sum of their bytes is ge then size of new tx. Otherwise we could overflow size @@ -594,11 +608,12 @@ where } pub(super) fn update_transaction(&self, outcome: &ViewStoreSubmitOutcome) { - if let Some(priority) = outcome.priority() { - if let Some(tx) = self.transactions.write().get_mut(&outcome.hash()) { - tx.priority.store(priority, atomic::Ordering::Relaxed); - } - } + outcome.priority().map(|priority| { + self.transactions + .write() + .get_mut(&outcome.hash()) + .map(|p| *p.priority.write() = Some(priority)) + }); } } From 46fa1fd6a3da4fcf7e61d862d1b63facb32d4d15 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Wed, 4 Dec 2024 18:04:31 +0100 Subject: [PATCH 27/60] tests added + dead code cleanup --- .../src/fork_aware_txpool/tx_mem_pool.rs | 241 ++++++++++++++++-- .../transaction-pool/src/graph/base_pool.rs | 10 - .../transaction-pool/src/graph/tracked_map.rs | 5 - .../src/graph/validated_pool.rs | 29 --- 4 files changed, 213 insertions(+), 72 deletions(-) diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs index 84963652ffa68..f2a2e6f05b3e1 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs @@ -369,6 +369,10 @@ where return Err(sc_transaction_pool_api::error::Error::AlreadyImported(Box::new(hash))); } + if new_tx.bytes > self.max_transactions_total_bytes { + return Err(sc_transaction_pool_api::error::Error::ImmediatelyDropped); + } + let mut sorted = transactions .iter() .filter_map(|(h, v)| v.priority().map(|_| (h.clone(), v.clone()))) @@ -378,7 +382,7 @@ where // that the sum of their bytes is ge then size of new tx. Otherwise we could overflow size // limits. Naive way to do it - rev-sort by priority and eat the tail. - // reverse (lowest prio last) + // reverse (oldest, lowest prio last) sorted.sort_by(|(_, a), (_, b)| match b.priority().cmp(&a.priority()) { Ordering::Equal => match (a.source.timestamp, b.source.timestamp) { (Some(a), Some(b)) => b.cmp(&a), @@ -387,11 +391,11 @@ where ordering @ _ => ordering, }); - let required_size = new_tx.bytes; - let mut total_size = 0usize; + let mut total_size_removed = 0usize; let mut to_be_removed = vec![]; + let free_bytes = self.max_transactions_total_bytes - self.transactions.bytes(); - while total_size < required_size { + loop { let Some((worst_hash, worst_tx)) = sorted.pop() else { return Err(sc_transaction_pool_api::error::Error::ImmediatelyDropped); }; @@ -400,8 +404,12 @@ where return Err(sc_transaction_pool_api::error::Error::ImmediatelyDropped); } - total_size += worst_tx.bytes; + total_size_removed += worst_tx.bytes; to_be_removed.push(worst_hash); + + if free_bytes + total_size_removed >= new_tx.bytes { + break; + } } let source = new_tx.source(); @@ -412,28 +420,16 @@ where debug_assert!(!self.is_limit_exceeded(transactions.len(), self.transactions.bytes())); Ok(InsertionInfo::new_with_removed(hash, source, to_be_removed)) + } - // let result = match ( - // self.is_limit_exceeded(transactions.len() + 1, bytes + tx.bytes), - // transactions.contains_key(&hash), - // ) { - // (false, false) => { - // let source = tx.source(); - // transactions.insert(hash, Arc::from(tx)); - // Ok(InsertionInfo::new(hash, source)) - // }, - // (_, true) => - // Err(sc_transaction_pool_api::error::Error::AlreadyImported(Box::new(hash))), - // (true, _) => Err(sc_transaction_pool_api::error::Error::ImmediatelyDropped), - // }; - // log::trace!(target: LOG_TARGET, "[{:?}] mempool::try_insert: {:?}", hash, - // result.as_ref().map(|r| r.hash)); - } - - /// Attempts to replace an existing transaction in the memory pool with a new one. + /// Attempts to insert a new transaction in the memory pool and drop some worse existing + /// transactions. /// - /// Returns a `Result` containing `InsertionInfo` if the new transaction is successfully - /// inserted; otherwise, returns an appropriate error indicating the failure. + /// This operation will not overflow the limit of the mempool. It means that cumulative + /// size of removed transactions will be equal (or greated) then size of newly inserted + /// transaction. + /// + /// Refer to [`try_insert_with_dropping`] for more details. pub(super) fn try_replace_transaction( &self, new_xt: ExtrinsicFor, @@ -736,6 +732,9 @@ mod tx_mem_pool_tests { assert_eq!(mempool.unwatched_and_watched_count(), (10, 5)); } + /// size of large extrinsic + const LARGE_XT_SIZE: usize = 1129; + fn large_uxt(x: usize) -> Extrinsic { ExtrinsicBuilder::new_include_data(vec![x as u8; 1024]).build() } @@ -745,8 +744,7 @@ mod tx_mem_pool_tests { sp_tracing::try_init_simple(); let max = 10; let api = Arc::from(TestApi::default()); - //size of large extrinsic is: 1129 - let mempool = TxMemPool::new_test(api.clone(), usize::MAX, max * 1129); + let mempool = TxMemPool::new_test(api.clone(), usize::MAX, max * LARGE_XT_SIZE); let xts = (0..max).map(|x| Arc::from(large_uxt(x))).collect::>(); @@ -771,5 +769,192 @@ mod tx_mem_pool_tests { )); } - //add some test for try_insert_with_dropping + #[test] + fn replacing_works_00() { + sp_tracing::try_init_simple(); + let max = 10; + let api = Arc::from(TestApi::default()); + let mempool = TxMemPool::new_test(api.clone(), usize::MAX, max * LARGE_XT_SIZE); + + let xts = (0..max).map(|x| Arc::from(large_uxt(x))).collect::>(); + + let low_prio = 0u64; + let hi_prio = u64::MAX; + + let total_xts_bytes = xts.iter().fold(0, |r, x| r + api.hash_and_length(&x).1); + let (submit_outcomes, hashes): (Vec<_>, Vec<_>) = xts + .iter() + .map(|t| { + let h = api.hash_and_length(t).0; + (ViewStoreSubmitOutcome::new(h, Some(low_prio)), h) + }) + .unzip(); + + let results = mempool.extend_unwatched(TransactionSource::External, &xts); + assert!(results.iter().all(Result::is_ok)); + assert_eq!(mempool.bytes(), total_xts_bytes); + + submit_outcomes.into_iter().for_each(|o| mempool.update_transaction(&o)); + + let xt = Arc::from(large_uxt(98)); + let hash = api.hash_and_length(&xt).0; + let result = mempool + .try_replace_transaction(xt, hi_prio, TransactionSource::External, false) + .unwrap(); + + assert_eq!(result.hash, hash); + assert_eq!(result.removed, hashes[0..1]); + } + + #[test] + fn replacing_works_01() { + sp_tracing::try_init_simple(); + let max = 10; + let api = Arc::from(TestApi::default()); + let mempool = TxMemPool::new_test(api.clone(), usize::MAX, max * LARGE_XT_SIZE); + + let xts = (0..max).map(|x| Arc::from(large_uxt(x))).collect::>(); + + let low_prio = 0u64; + let hi_prio = u64::MAX; + + let total_xts_bytes = xts.iter().fold(0, |r, x| r + api.hash_and_length(&x).1); + let (submit_outcomes, hashes): (Vec<_>, Vec<_>) = xts + .iter() + .map(|t| { + let h = api.hash_and_length(t).0; + (ViewStoreSubmitOutcome::new(h, Some(low_prio)), h) + }) + .unzip(); + + let results = mempool.extend_unwatched(TransactionSource::External, &xts); + assert!(results.iter().all(Result::is_ok)); + assert_eq!(mempool.bytes(), total_xts_bytes); + assert_eq!(total_xts_bytes, max * LARGE_XT_SIZE); + + submit_outcomes.into_iter().for_each(|o| mempool.update_transaction(&o)); + + //this one should drop 2 xts (size: 1130): + let xt = Arc::from(ExtrinsicBuilder::new_include_data(vec![98 as u8; 1025]).build()); + let (hash, length) = api.hash_and_length(&xt); + assert_eq!(length, 1130); + let result = mempool + .try_replace_transaction(xt, hi_prio, TransactionSource::External, false) + .unwrap(); + + assert_eq!(result.hash, hash); + assert_eq!(result.removed, hashes[0..2]); + } + + #[test] + fn replacing_works_02() { + sp_tracing::try_init_simple(); + const COUNT: usize = 10; + let api = Arc::from(TestApi::default()); + let mempool = TxMemPool::new_test(api.clone(), usize::MAX, COUNT * LARGE_XT_SIZE); + + let xts = (0..COUNT).map(|x| Arc::from(large_uxt(x))).collect::>(); + + let hi_prio = u64::MAX; + + let total_xts_bytes = xts.iter().fold(0, |r, x| r + api.hash_and_length(&x).1); + let (submit_outcomes, hashes): (Vec<_>, Vec<_>) = xts + .iter() + .enumerate() + .map(|(prio, t)| { + let h = api.hash_and_length(t).0; + (ViewStoreSubmitOutcome::new(h, Some((COUNT - prio).try_into().unwrap())), h) + }) + .unzip(); + + let results = mempool.extend_unwatched(TransactionSource::External, &xts); + assert!(results.iter().all(Result::is_ok)); + assert_eq!(mempool.bytes(), total_xts_bytes); + + submit_outcomes.into_iter().for_each(|o| mempool.update_transaction(&o)); + + //this one should drop 3 xts (each of size 1129) + let xt = Arc::from(ExtrinsicBuilder::new_include_data(vec![98 as u8; 2154]).build()); + let (hash, length) = api.hash_and_length(&xt); + // overhead is 105, thus length: 105 + 2154 + assert_eq!(length, 2 * LARGE_XT_SIZE + 1); + let result = mempool + .try_replace_transaction(xt, hi_prio, TransactionSource::External, false) + .unwrap(); + + assert_eq!(result.hash, hash); + assert!(result.removed.iter().eq(hashes[COUNT - 3..COUNT].iter().rev())); + } + + #[test] + fn replacing_skips_lower_prio_tx() { + sp_tracing::try_init_simple(); + const COUNT: usize = 10; + let api = Arc::from(TestApi::default()); + let mempool = TxMemPool::new_test(api.clone(), usize::MAX, COUNT * LARGE_XT_SIZE); + + let xts = (0..COUNT).map(|x| Arc::from(large_uxt(x))).collect::>(); + + let hi_prio = 100u64; + let low_prio = 10u64; + + let total_xts_bytes = xts.iter().fold(0, |r, x| r + api.hash_and_length(&x).1); + let submit_outcomes = xts + .iter() + .map(|t| { + let h = api.hash_and_length(t).0; + ViewStoreSubmitOutcome::new(h, Some(hi_prio)) + }) + .collect::>(); + + let results = mempool.extend_unwatched(TransactionSource::External, &xts); + assert!(results.iter().all(Result::is_ok)); + assert_eq!(mempool.bytes(), total_xts_bytes); + + submit_outcomes.into_iter().for_each(|o| mempool.update_transaction(&o)); + + //this one should drop 3 xts (each of size 1129) + let xt = Arc::from(large_uxt(98)); + let result = + mempool.try_replace_transaction(xt, low_prio, TransactionSource::External, false); + + // we did not update priorities (update_transaction was not called): + assert!(matches!( + result.unwrap_err(), + sc_transaction_pool_api::error::Error::ImmediatelyDropped + )); + } + + #[test] + fn replacing_is_skipped_if_prios_are_not_set() { + sp_tracing::try_init_simple(); + const COUNT: usize = 10; + let api = Arc::from(TestApi::default()); + let mempool = TxMemPool::new_test(api.clone(), usize::MAX, COUNT * LARGE_XT_SIZE); + + let xts = (0..COUNT).map(|x| Arc::from(large_uxt(x))).collect::>(); + + let hi_prio = u64::MAX; + + let total_xts_bytes = xts.iter().fold(0, |r, x| r + api.hash_and_length(&x).1); + + let results = mempool.extend_unwatched(TransactionSource::External, &xts); + assert!(results.iter().all(Result::is_ok)); + assert_eq!(mempool.bytes(), total_xts_bytes); + + //this one could drop 3 xts (each of size 1129) + let xt = Arc::from(ExtrinsicBuilder::new_include_data(vec![98 as u8; 2154]).build()); + let length = api.hash_and_length(&xt).1; + // overhead is 105, thus length: 105 + 2154 + assert_eq!(length, 2 * LARGE_XT_SIZE + 1); + + let result = + mempool.try_replace_transaction(xt, hi_prio, TransactionSource::External, false); + + // we did not update priorities (update_transaction was not called): + assert!(matches!( + result.unwrap_err(), + sc_transaction_pool_api::error::Error::ImmediatelyDropped + )); + } } diff --git a/substrate/client/transaction-pool/src/graph/base_pool.rs b/substrate/client/transaction-pool/src/graph/base_pool.rs index 8dce507be4e38..3b4afc88b7897 100644 --- a/substrate/client/transaction-pool/src/graph/base_pool.rs +++ b/substrate/client/transaction-pool/src/graph/base_pool.rs @@ -438,16 +438,6 @@ impl BasePool(&self, init: R, mut f: F) -> R - where - F: FnMut(R, &Arc>) -> R, - { - self.ready - .fold::(init, |acc, current| f(acc, ¤t.transaction.transaction)) - } - /// Makes sure that the transactions in the queues stay within provided limits. /// /// Removes and returns worst transactions from the queues and all transactions that depend on diff --git a/substrate/client/transaction-pool/src/graph/tracked_map.rs b/substrate/client/transaction-pool/src/graph/tracked_map.rs index 818c1ebe544ff..fe15c6eca3080 100644 --- a/substrate/client/transaction-pool/src/graph/tracked_map.rs +++ b/substrate/client/transaction-pool/src/graph/tracked_map.rs @@ -174,11 +174,6 @@ where self.inner_guard.len() } - /// Returns an iterator over all values. - pub fn values(&self) -> std::collections::hash_map::Values { - self.inner_guard.values() - } - /// Returns an iterator over all key-value pairs. pub fn iter(&self) -> Iter<'_, K, V> { self.inner_guard.iter() diff --git a/substrate/client/transaction-pool/src/graph/validated_pool.rs b/substrate/client/transaction-pool/src/graph/validated_pool.rs index 0c0317750f85a..287c0bd03dbce 100644 --- a/substrate/client/transaction-pool/src/graph/validated_pool.rs +++ b/substrate/client/transaction-pool/src/graph/validated_pool.rs @@ -178,8 +178,6 @@ impl Clone for ValidatedPool { } } -type BaseTransactionFor = base::Transaction, ExtrinsicFor>; - impl ValidatedPool { /// Create a new transaction pool. pub fn new(options: Options, is_validator: IsValidator, api: Arc) -> Self { @@ -745,33 +743,6 @@ impl ValidatedPool { }); } - /// Searches the base pool for a transaction with a lower priority than the given one. - /// - /// Returns the hash of a lower-priority transaction if found, otherwise `None`. - pub fn find_transaction_with_lower_prio( - &self, - tx: ValidatedTransactionFor, - ) -> Option> { - match tx { - ValidatedTransaction::Valid(base::Transaction { priority, .. }) => { - let pool = self.pool.read(); - pool.fold_ready( - Some((priority, Option::>>::None)), - |acc, current| { - let (priority, _) = acc.as_ref().expect("acc is initialized. qed"); - if current.priority < *priority { - return Some((current.priority, Some(current.clone()))) - } else { - return acc - } - }, - ) - .map(|r| r.1.map(|tx| tx.hash))? - }, - _ => None, - } - } - /// Removes a transaction subtree from the pool, starting from the given transaction hash. /// /// This function traverses the dependency graph of transactions and removes the specified From 2221d7a4ae0d897636ba465046d4bf94cd46e862 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Wed, 4 Dec 2024 18:13:32 +0100 Subject: [PATCH 28/60] comments cleanup --- .../src/fork_aware_txpool/fork_aware_txpool.rs | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) 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 3f8d66110432b..5f26e580c3abb 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 @@ -1329,14 +1329,12 @@ where watched: bool, xt: ExtrinsicFor, ) -> Result>, TxPoolApiError> { - // 1. we should validate at most_recent, and reuse result to submit there. let at = self .view_store .most_recent_view .read() .ok_or(TxPoolApiError::ImmediatelyDropped)?; - // 2. validate transaction at `at` let (best_view, _) = self .view_store .get_view_at(at, false) @@ -1354,21 +1352,15 @@ where .await; if let Some(priority) = validated_tx.priority() { - // 3. check if mempool contains a transaction with lower priority (actually worse - do - // we want to check timestamp too - no: see #4609?) Would be perfect to choose - // transaction with lowest the number of dependant txs in its subtree. - // 4. if yes - remove worse transaction from mempool and add new one. let insertion_info = self.mempool.try_replace_transaction(xt, priority, source, watched)?; for worst_hash in &insertion_info.removed { log::trace!(target: LOG_TARGET, "removed: {worst_hash:?} replaced by {tx_hash:?}"); - // 5. notify listner self.view_store .listener .transaction_dropped(DroppedTransaction::new_enforced_by_limts(*worst_hash)); - // 6. remove transaction from the view_store self.view_store.remove_transaction_subtree( *worst_hash, |listener, removed_tx_hash| { @@ -1377,8 +1369,7 @@ where ); } - // 8. add to pending_replacements - make sure it will not sneak back via cloned view - // 9. subemit new one to the view, this will be done upon in the caller + // todo: add to pending_replacements - make sure it will not sneak back via cloned view return Ok(insertion_info) } From 0244ba032f1a63a76ecc2a936d5d1003bb2280da Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Tue, 7 Jan 2025 13:52:11 +0100 Subject: [PATCH 29/60] tweaks --- .../transaction-pool/src/fork_aware_txpool/dropped_watcher.rs | 4 ++-- .../src/fork_aware_txpool/fork_aware_txpool.rs | 1 - .../transaction-pool/src/fork_aware_txpool/view_store.rs | 4 ++-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/dropped_watcher.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/dropped_watcher.rs index 93fbb79ce2618..91909fa5a906a 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/dropped_watcher.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/dropped_watcher.rs @@ -335,14 +335,14 @@ where let stream_map = futures::stream::unfold(ctx, |mut ctx| async move { loop { if let Some(dropped) = ctx.get_pending_dropped_transaction() { - debug!("dropped_watcher: sending out (pending): {dropped:?}"); + trace!("dropped_watcher: sending out (pending): {dropped:?}"); return Some((dropped, ctx)); } tokio::select! { biased; Some(event) = next_event(&mut ctx.stream_map) => { if let Some(dropped) = ctx.handle_event(event.0, event.1) { - debug!("dropped_watcher: sending out: {dropped:?}"); + trace!("dropped_watcher: sending out: {dropped:?}"); return Some((dropped, ctx)); } }, 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 5f26e580c3abb..f4a659eec9b63 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 @@ -1369,7 +1369,6 @@ where ); } - // todo: add to pending_replacements - make sure it will not sneak back via cloned view return Ok(insertion_info) } 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 89bf47cc38950..931a8addb1f3c 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 @@ -804,8 +804,8 @@ where /// /// A `listener_action` callback function is invoked for every transaction that is removed, /// providing a reference to the pool's listener and the hash of the removed transaction. This - /// allows to trigger the required events. Note listener may be called multiple times for the - /// same hash. + /// allows to trigger the required events. Note that listener may be called multiple times for + /// the same hash. /// /// Function will also schedule view pre-insertion actions to ensure that transactions will be /// removed from newly created view. From 5d0283e2d2be74040b371e710028ec326a30ea49 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Wed, 8 Jan 2025 11:51:43 +0100 Subject: [PATCH 30/60] review comments --- .../fork_aware_txpool/fork_aware_txpool.rs | 79 +++++++++++-------- .../src/fork_aware_txpool/tx_mem_pool.rs | 28 ++++--- .../src/fork_aware_txpool/view.rs | 3 +- .../src/fork_aware_txpool/view_store.rs | 2 +- .../client/transaction-pool/src/graph/mod.rs | 3 + .../transaction-pool/tests/fatp_prios.rs | 6 +- 6 files changed, 75 insertions(+), 46 deletions(-) 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 f4a659eec9b63..82e5474a6cf19 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 @@ -660,7 +660,7 @@ where .collect::>()) } - //todo: review + test maybe? + // Submit all the transactions to the mempool let retries = mempool_results .into_iter() .zip(xts.clone()) @@ -675,6 +675,7 @@ where let mempool_results = futures::future::join_all(retries).await; + // Collect transactions that were successfully submitted to the mempool... let to_be_submitted = mempool_results .iter() .zip(xts) @@ -686,26 +687,45 @@ where self.metrics .report(|metrics| metrics.submitted_transactions.inc_by(to_be_submitted.len() as _)); + // ... and submit them to the view_store. Please note that transaction rejected by mempool + // are not sent here. let mempool = self.mempool.clone(); let results_map = view_store.submit(to_be_submitted.into_iter()).await; let mut submission_results = reduce_multiview_result(results_map).into_iter(); + // Note for composing final result: + // + // For each failed insertion into the mempool, the mempool result should be placed into + // the returned vector. + // + // For each successful insertion into the mempool, the corresponding + // view_store submission result needs to be examined: + // - If there is an error during view_store submission, the transaction is removed from + // the mempool, and the final result recorded in the vector for this transaction is the + // view_store submission error. + // + // - If the view_store submission is successful, the transaction priority is updated in the + // mempool. + // + // Finally, it collects the hashes of updated transactions or submission errors (either + // from the mempool or view_store) into a returned vector. Ok(mempool_results .into_iter() .map(|result| { result .map_err(Into::into) .and_then(|insertion| { - submission_results - .next() - .expect("The number of Ok results in mempool is exactly the same as the size of to-views-submission result. qed.") - .inspect_err(|_|{ - mempool.remove_transaction(&insertion.hash); - }) + submission_results + .next() + .expect("The number of Ok results in mempool is exactly the same as the size of view_store submission result. qed.") + .inspect_err(|_|{ + mempool.remove_transaction(&insertion.hash); + }) }) + }) .map(|r| r.map(|r| { - mempool.update_transaction(&r); + mempool.update_transaction_priority(&r); r.hash() })) .collect::>()) @@ -758,7 +778,7 @@ where self.mempool.remove_transaction(&xt_hash); }) .map(|mut outcome| { - self.mempool.update_transaction(&outcome); + self.mempool.update_transaction_priority(&outcome); outcome.expect_watcher() }) } @@ -897,7 +917,7 @@ where self.view_store .submit_local(xt) .map(|outcome| { - self.mempool.update_transaction(&outcome); + self.mempool.update_transaction_priority(&outcome); outcome.hash() }) .or_else(|_| Ok(xt_hash)) @@ -1153,10 +1173,8 @@ where .into_iter() .zip(hashes) .map(|(result, tx_hash)| { - //todo: we may need a bool flag here indicating if we need to update priority - //(premature optimization) result - .map(|outcome| self.mempool.update_transaction(&outcome.into())) + .map(|outcome| self.mempool.update_transaction_priority(&outcome.into())) .or_else(|_| Err(tx_hash)) }) .collect::>(); @@ -1351,28 +1369,25 @@ where ) .await; - if let Some(priority) = validated_tx.priority() { - let insertion_info = - self.mempool.try_replace_transaction(xt, priority, source, watched)?; - - for worst_hash in &insertion_info.removed { - log::trace!(target: LOG_TARGET, "removed: {worst_hash:?} replaced by {tx_hash:?}"); - self.view_store - .listener - .transaction_dropped(DroppedTransaction::new_enforced_by_limts(*worst_hash)); - - self.view_store.remove_transaction_subtree( - *worst_hash, - |listener, removed_tx_hash| { - listener.limits_enforced(&removed_tx_hash); - }, - ); - } + let Some(priority) = validated_tx.priority() else { + return Err(TxPoolApiError::ImmediatelyDropped) + }; + + let insertion_info = self.mempool.try_replace_transaction(xt, priority, source, watched)?; - return Ok(insertion_info) + for worst_hash in &insertion_info.removed { + log::trace!(target: LOG_TARGET, "removed: {worst_hash:?} replaced by {tx_hash:?}"); + self.view_store + .listener + .transaction_dropped(DroppedTransaction::new_enforced_by_limts(*worst_hash)); + + self.view_store + .remove_transaction_subtree(*worst_hash, |listener, removed_tx_hash| { + listener.limits_enforced(&removed_tx_hash); + }); } - Err(TxPoolApiError::ImmediatelyDropped) + return Ok(insertion_info) } } diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs index 95b88e7dde7c0..08eb3f0aa6c88 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs @@ -388,7 +388,7 @@ where (Some(a), Some(b)) => b.cmp(&a), _ => Ordering::Equal, }, - ordering @ _ => ordering, + ordering => ordering, }); let mut total_size_removed = 0usize; @@ -429,7 +429,7 @@ where /// size of removed transactions will be equal (or greated) then size of newly inserted /// transaction. /// - /// Refer to [`try_insert_with_dropping`] for more details. + /// Refer to [`TxMemPool::try_insert_with_dropping`] for more details. pub(super) fn try_replace_transaction( &self, new_xt: ExtrinsicFor, @@ -603,7 +603,9 @@ where self.listener.invalidate_transactions(&invalid_hashes); } - pub(super) fn update_transaction(&self, outcome: &ViewStoreSubmitOutcome) { + /// Updates the priority of transaction stored in mempool using provided view_store submission + /// outcome. + pub(super) fn update_transaction_priority(&self, outcome: &ViewStoreSubmitOutcome) { outcome.priority().map(|priority| { self.transactions .write() @@ -794,7 +796,9 @@ mod tx_mem_pool_tests { assert!(results.iter().all(Result::is_ok)); assert_eq!(mempool.bytes(), total_xts_bytes); - submit_outcomes.into_iter().for_each(|o| mempool.update_transaction(&o)); + submit_outcomes + .into_iter() + .for_each(|o| mempool.update_transaction_priority(&o)); let xt = Arc::from(large_uxt(98)); let hash = api.hash_and_length(&xt).0; @@ -832,7 +836,9 @@ mod tx_mem_pool_tests { assert_eq!(mempool.bytes(), total_xts_bytes); assert_eq!(total_xts_bytes, max * LARGE_XT_SIZE); - submit_outcomes.into_iter().for_each(|o| mempool.update_transaction(&o)); + submit_outcomes + .into_iter() + .for_each(|o| mempool.update_transaction_priority(&o)); //this one should drop 2 xts (size: 1130): let xt = Arc::from(ExtrinsicBuilder::new_include_data(vec![98 as u8; 1025]).build()); @@ -871,7 +877,9 @@ mod tx_mem_pool_tests { assert!(results.iter().all(Result::is_ok)); assert_eq!(mempool.bytes(), total_xts_bytes); - submit_outcomes.into_iter().for_each(|o| mempool.update_transaction(&o)); + submit_outcomes + .into_iter() + .for_each(|o| mempool.update_transaction_priority(&o)); //this one should drop 3 xts (each of size 1129) let xt = Arc::from(ExtrinsicBuilder::new_include_data(vec![98 as u8; 2154]).build()); @@ -911,14 +919,16 @@ mod tx_mem_pool_tests { assert!(results.iter().all(Result::is_ok)); assert_eq!(mempool.bytes(), total_xts_bytes); - submit_outcomes.into_iter().for_each(|o| mempool.update_transaction(&o)); + submit_outcomes + .into_iter() + .for_each(|o| mempool.update_transaction_priority(&o)); //this one should drop 3 xts (each of size 1129) let xt = Arc::from(large_uxt(98)); let result = mempool.try_replace_transaction(xt, low_prio, TransactionSource::External, false); - // we did not update priorities (update_transaction was not called): + // we did not update priorities (update_transaction_priority was not called): assert!(matches!( result.unwrap_err(), sc_transaction_pool_api::error::Error::ImmediatelyDropped @@ -951,7 +961,7 @@ mod tx_mem_pool_tests { let result = mempool.try_replace_transaction(xt, hi_prio, TransactionSource::External, false); - // we did not update priorities (update_transaction was not called): + // we did not update priorities (update_transaction_priority was not called): assert!(matches!( result.unwrap_err(), sc_transaction_pool_api::error::Error::ImmediatelyDropped 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 291a1f7d7fa3a..a35d68120a3ab 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/view.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/view.rs @@ -463,8 +463,7 @@ where /// Removes the whole transaction subtree from the inner pool. /// - /// Intended to be called when removal is a result of replacement. Provided `replaced_with` - /// transaction hash is used in emitted _usurped_ event. + /// Refer to [`crate::graph::ValidatedPool::remove_subtree`] for more details. pub fn remove_subtree( &self, tx_hash: ExtrinsicHash, 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 931a8addb1f3c..8c37eed679d70 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 @@ -796,7 +796,7 @@ where let _results = futures::future::join_all(submit_futures).await; } - /// Removes a transaction subtree from the every view in the view_store, starting from the given + /// Removes a transaction subtree from every view in the view_store, starting from the given /// transaction hash. /// /// This function traverses the dependency graph of transactions and removes the specified diff --git a/substrate/client/transaction-pool/src/graph/mod.rs b/substrate/client/transaction-pool/src/graph/mod.rs index 008503fb4cdfb..2114577f4dee7 100644 --- a/substrate/client/transaction-pool/src/graph/mod.rs +++ b/substrate/client/transaction-pool/src/graph/mod.rs @@ -47,3 +47,6 @@ pub use validated_pool::{ pub(crate) use self::pool::CheckBannedBeforeVerify; pub(crate) use listener::DroppedByLimitsEvent; + +#[cfg(doc)] +pub(crate) use validated_pool::ValidatedPool; diff --git a/substrate/client/transaction-pool/tests/fatp_prios.rs b/substrate/client/transaction-pool/tests/fatp_prios.rs index 86352fa68fee1..94ca75a3fe308 100644 --- a/substrate/client/transaction-pool/tests/fatp_prios.rs +++ b/substrate/client/transaction-pool/tests/fatp_prios.rs @@ -291,8 +291,10 @@ fn fatp_prios_watcher_full_mempool_higher_prio_is_accepted() { let header02 = api.push_block_with_parent(header01.hash(), vec![], true); block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02.hash()))); - let xt2_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap(); - let xt3_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).unwrap(); + let _xt2_watcher = + block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap(); + let _xt3_watcher = + block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).unwrap(); assert_pool_status!(header02.hash(), &pool, 2, 0); assert_eq!(pool.mempool_len().1, 4); From caca2e1e816b69e9fca7ed4a83729509a19e47a6 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Wed, 8 Jan 2025 12:18:11 +0100 Subject: [PATCH 31/60] clippy --- .../src/fork_aware_txpool/fork_aware_txpool.rs | 7 +++---- .../transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs | 2 +- .../transaction-pool/src/fork_aware_txpool/view_store.rs | 3 +-- 3 files changed, 5 insertions(+), 7 deletions(-) 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 82e5474a6cf19..6f0e79cce2dc2 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 @@ -643,7 +643,7 @@ where /// are reduced to single result. Refer to `reduce_multiview_result` for more details. async fn submit_at( &self, - at: ::Hash, + _: ::Hash, source: TransactionSource, xts: Vec>, ) -> Result, Self::Error>>, Self::Error> { @@ -667,7 +667,7 @@ where .map(|(result, xt)| async move { match result { Err(TxPoolApiError::ImmediatelyDropped) => - self.attempt_transaction_replacement(at, source, false, xt).await, + self.attempt_transaction_replacement(source, false, xt).await, result @ _ => result, } }) @@ -765,7 +765,7 @@ where match self.mempool.push_watched(source, xt.clone()) { Ok(result) => result, Err(TxPoolApiError::ImmediatelyDropped) => - self.attempt_transaction_replacement(at, source, true, xt.clone()).await?, + self.attempt_transaction_replacement(source, true, xt.clone()).await?, Err(e) => return Err(e.into()), }; @@ -1342,7 +1342,6 @@ where /// transaction was dropped immediately. async fn attempt_transaction_replacement( &self, - _: Block::Hash, source: TransactionSource, watched: bool, xt: ExtrinsicFor, diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs index 08eb3f0aa6c88..6163ccde91677 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs @@ -375,7 +375,7 @@ where let mut sorted = transactions .iter() - .filter_map(|(h, v)| v.priority().map(|_| (h.clone(), v.clone()))) + .filter_map(|(h, v)| v.priority().map(|_| (*h, v.clone()))) .collect::>(); // When pushing higher prio transaction, we need to find a number of lower prio txs, such 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 8c37eed679d70..e08a0b12854e2 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 @@ -839,8 +839,7 @@ where .iter() .chain(self.inactive_views.read().iter()) .filter(|(_, view)| view.is_imported(&xt_hash)) - .map(|(_, view)| view.remove_subtree(xt_hash, &listener_action)) - .flatten() + .flat_map(|(_, view)| view.remove_subtree(xt_hash, &listener_action)) .filter(|xt_hash| seen.insert(*xt_hash)) .collect(); From b86ef05398ba515932b2f4a4ae978ed23912479f Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Wed, 8 Jan 2025 12:36:07 +0100 Subject: [PATCH 32/60] clean up --- .../src/fork_aware_txpool/dropped_watcher.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/dropped_watcher.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/dropped_watcher.rs index 91909fa5a906a..50ebd15a63e43 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/dropped_watcher.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/dropped_watcher.rs @@ -206,10 +206,6 @@ where views ); views.remove(&key); - //todo: merge heads up warning! - if views.is_empty() { - self.pending_dropped_transactions.push(*tx_hash); - } }); self.future_transaction_views.iter_mut().for_each(|(tx_hash, views)| { @@ -263,10 +259,12 @@ where }, TransactionStatus::Ready | TransactionStatus::InBlock(..) => { // note: if future transaction was once seens as the ready we may want to treat it - // as ready transactions. Unreferenced future transactions are more likely to be - // removed when the last referencing view is removed then ready transactions. - // Transcaction seen as ready is likely quite close to be included in some - // future fork. + // as ready transaction. The rationale behind this is as follows: we want to remove + // unreferenced future transactions when the last referencing view is removed (to + // avoid clogging mempool). For ready transactions we prefer to keep them in mempool + // even if no view is currently referencing them. Future transcaction once seen as + // ready is likely quite close to be included in some future fork (it is close to be + // ready, so we make exception and treat such transaction as ready). if let Some(mut views) = self.future_transaction_views.remove(&tx_hash) { views.insert(block_hash); self.ready_transaction_views.insert(tx_hash, views); From 61d7730539f29685b36e936dd28adcb3bd3c4ce0 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Mon, 13 Jan 2025 17:25:19 +0100 Subject: [PATCH 33/60] revalidate: use IndexMap to preserve order --- .../transaction-pool/src/fork_aware_txpool/view.rs | 7 ++++--- .../client/transaction-pool/src/graph/pool.rs | 3 +-- .../transaction-pool/src/graph/validated_pool.rs | 7 ++++--- .../src/single_state_txpool/revalidation.rs | 14 +++++++------- 4 files changed, 16 insertions(+), 15 deletions(-) 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 076e565199d72..19cad48471348 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/view.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/view.rs @@ -33,6 +33,7 @@ use crate::{ }, LOG_TARGET, }; +use indexmap::IndexMap; use parking_lot::Mutex; use sc_transaction_pool_api::{error::Error as TxPoolError, PoolStatus}; use sp_blockchain::HashAndNumber; @@ -40,10 +41,10 @@ use sp_runtime::{ generic::BlockId, traits::Block as BlockT, transaction_validity::TransactionValidityError, SaturatedConversion, }; -use std::{collections::HashMap, sync::Arc, time::Instant}; +use std::{sync::Arc, time::Instant}; pub(super) struct RevalidationResult { - revalidated: HashMap, ValidatedTransactionFor>, + revalidated: IndexMap, ValidatedTransactionFor>, invalid_hashes: Vec>, } @@ -272,7 +273,7 @@ where //todo: revalidate future, remove if invalid [#5496] let mut invalid_hashes = Vec::new(); - let mut revalidated = HashMap::new(); + let mut revalidated = IndexMap::new(); let mut validation_results = vec![]; let mut batch_iter = batch.into_iter(); diff --git a/substrate/client/transaction-pool/src/graph/pool.rs b/substrate/client/transaction-pool/src/graph/pool.rs index fdde8cb1d94eb..d769003282613 100644 --- a/substrate/client/transaction-pool/src/graph/pool.rs +++ b/substrate/client/transaction-pool/src/graph/pool.rs @@ -29,7 +29,6 @@ use sp_runtime::{ }, }; use std::{ - collections::HashMap, sync::Arc, time::{Duration, Instant}, }; @@ -226,7 +225,7 @@ impl Pool { /// Resubmit some transaction that were validated elsewhere. pub fn resubmit( &self, - revalidated_transactions: HashMap, ValidatedTransactionFor>, + revalidated_transactions: IndexMap, ValidatedTransactionFor>, ) { let now = Instant::now(); self.validated_pool.resubmit(revalidated_transactions); diff --git a/substrate/client/transaction-pool/src/graph/validated_pool.rs b/substrate/client/transaction-pool/src/graph/validated_pool.rs index 287c0bd03dbce..64772746e741f 100644 --- a/substrate/client/transaction-pool/src/graph/validated_pool.rs +++ b/substrate/client/transaction-pool/src/graph/validated_pool.rs @@ -23,6 +23,7 @@ use std::{ use crate::{common::log_xt::log_xt_trace, LOG_TARGET}; use futures::channel::mpsc::{channel, Sender}; +use indexmap::IndexMap; use parking_lot::{Mutex, RwLock}; use sc_transaction_pool_api::{error, PoolStatus, ReadyTransactions, TransactionPriority}; use sp_blockchain::HashAndNumber; @@ -386,7 +387,7 @@ impl ValidatedPool { /// Transactions that are missing from the pool are not submitted. pub fn resubmit( &self, - mut updated_transactions: HashMap, ValidatedTransactionFor>, + mut updated_transactions: IndexMap, ValidatedTransactionFor>, ) { #[derive(Debug, Clone, Copy, PartialEq)] enum Status { @@ -421,7 +422,7 @@ impl ValidatedPool { let removed = pool.remove_subtree(&[hash]); for removed_tx in removed { let removed_hash = removed_tx.hash; - let updated_transaction = updated_transactions.remove(&removed_hash); + let updated_transaction = updated_transactions.shift_remove(&removed_hash); let tx_to_resubmit = if let Some(updated_tx) = updated_transaction { updated_tx } else { @@ -438,7 +439,7 @@ impl ValidatedPool { txs_to_resubmit.push((removed_hash, tx_to_resubmit)); } // make sure to remove the hash even if it's not present in the pool anymore. - updated_transactions.remove(&hash); + updated_transactions.shift_remove(&hash); } // if we're rejecting future transactions, then insertion order matters here: diff --git a/substrate/client/transaction-pool/src/single_state_txpool/revalidation.rs b/substrate/client/transaction-pool/src/single_state_txpool/revalidation.rs index 472aa75609777..74d1992dd1791 100644 --- a/substrate/client/transaction-pool/src/single_state_txpool/revalidation.rs +++ b/substrate/client/transaction-pool/src/single_state_txpool/revalidation.rs @@ -18,17 +18,17 @@ //! Pool periodic revalidation. -use std::{ - collections::{BTreeMap, HashMap, HashSet}, - pin::Pin, - sync::Arc, -}; - use crate::graph::{BlockHash, ChainApi, ExtrinsicHash, Pool, ValidatedTransaction}; +use indexmap::IndexMap; use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender}; use sp_runtime::{ generic::BlockId, traits::SaturatedConversion, transaction_validity::TransactionValidityError, }; +use std::{ + collections::{BTreeMap, HashMap, HashSet}, + pin::Pin, + sync::Arc, +}; use futures::prelude::*; use std::time::Duration; @@ -84,7 +84,7 @@ async fn batch_revalidate( }; let mut invalid_hashes = Vec::new(); - let mut revalidated = HashMap::new(); + let mut revalidated = IndexMap::new(); let validation_results = futures::future::join_all(batch.into_iter().filter_map(|ext_hash| { pool.validated_pool().ready_by_hash(&ext_hash).map(|ext| { From c88d58b6585ec83793544aeb4a93d218525a1714 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Mon, 13 Jan 2025 17:33:17 +0100 Subject: [PATCH 34/60] implementation improvements + doc --- .../client/transaction-pool/api/src/lib.rs | 2 + .../fork_aware_txpool/fork_aware_txpool.rs | 2 +- .../src/fork_aware_txpool/view.rs | 8 +-- .../src/fork_aware_txpool/view_store.rs | 53 +++++++++++++------ .../src/graph/validated_pool.rs | 13 ++++- 5 files changed, 57 insertions(+), 21 deletions(-) diff --git a/substrate/client/transaction-pool/api/src/lib.rs b/substrate/client/transaction-pool/api/src/lib.rs index f0edf136c2fce..725f4646791da 100644 --- a/substrate/client/transaction-pool/api/src/lib.rs +++ b/substrate/client/transaction-pool/api/src/lib.rs @@ -303,6 +303,8 @@ pub trait TransactionPool: Send + Sync { /// /// The optional `at` parameter provides additional context regarding the block where the error /// occurred. + /// + /// Function returns the transactions actually removed from the pool. fn report_invalid( &self, at: Option<::Hash>, 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 fc9deb7fef0ac..e253adf9fea57 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 @@ -826,7 +826,7 @@ where // - remove resulting hashes from mempool and collect Arc // - send notification using listener (should this be done in view_store) - Default::default() + removed } // todo [#5491]: api change? 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 19cad48471348..6ce1cf329359d 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/view.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/view.rs @@ -312,7 +312,7 @@ where batch_len, revalidation_duration ); - log_xt_trace!(data:tuple, target:LOG_TARGET, validation_results.iter().map(|x| (x.1, &x.0)), "[{:?}] view::revalidateresult: {:?}"); + log_xt_trace!(data:tuple, target:LOG_TARGET, validation_results.iter().map(|x| (x.1, &x.0)), "[{:?}] view::revalidate result: {:?}"); for (validation_result, tx_hash, tx) in validation_results { match validation_result { @@ -457,7 +457,9 @@ where } } - /// todo: add doc + /// Remove invalid transactions from the view. + /// + /// Refer to [`crate::graph::ValidatedPool::remove_invalid`] for more details. pub(crate) fn remove_invalid( &self, hashes: &[ExtrinsicHash], @@ -478,7 +480,7 @@ where &self, tx_hash: ExtrinsicHash, listener_action: F, - ) -> Vec> + ) -> Vec> where F: Fn(&mut crate::graph::Listener, ExtrinsicHash), { 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 93d2bfc1ef012..25022b5cb4c4c 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 @@ -654,12 +654,32 @@ where log::trace!(target:LOG_TARGET,"finish_background_revalidations took {:?}", start.elapsed()); } - /// todo: doc + /// Reports invalid transactions to the view store. + /// + /// This function accepts an array of tuples, each containing a transaction hash and an + /// optional error encountered during the transaction execution at a specific (also optional) + /// block. + /// + /// Removal operation applies to provided transactions and their descendants. + /// + /// Invalid future and stale transaction will be removed only from given `at` view, and will be + /// kept in the transaction pool. Such transaction will not be reported in returned vector. They + /// also will not be banned. No event will be triggered. + /// + /// For other errors, the transaction will be removed from the pool, and it will be included in + /// the returned vector. If the tuple's error is None, the transaction will be forcibly removed + /// from the pool. It will be included in the returned vector. Additionally transactions + /// provided as input will be banned from re-entering the pool. + /// + /// For every transaction removed from the pool an Invalid event is triggered. + /// + /// Returns the list of actually removed transactions, which may include transactions dependent + /// on provided set. pub(crate) fn report_invalid( &self, at: Option, invalid_tx_errors: &[(ExtrinsicHash, Option)], - ) -> Vec> { + ) -> Vec> { let mut remove_from_view = vec![]; let mut remove_from_pool = vec![]; @@ -676,21 +696,24 @@ where }, }); - at.inspect(|at| { - self.get_view_at(*at, true) + // transaction removed from view, won't be included into the final result, as they may still + // be in the pool. + at.map(|at| { + self.get_view_at(at, true) .map(|(view, _)| view.remove_invalid(&remove_from_view[..])) - .unwrap_or_default(); }); - //todo - merge with priorites: - // let removed from_pool = self.view_store.remove_transaction_subtree( - // worst_tx_hash, - // |listener, removed_tx_hash| { &mut Listener<::Hash, …>, ::Hash - // listener.invalid(&removed_tx_hash, &tx_hash); - // }, - // ); + let mut removed = vec![]; + for tx in &remove_from_pool { + let removed_from_pool = + self.remove_transaction_subtree(*tx, |listener, removed_tx_hash| { + listener.invalid(&removed_tx_hash); + }); + removed.extend(removed_from_pool); + } + self.listener.invalidate_transactions(&remove_from_pool[..]); - remove_from_pool + removed } /// Replaces an existing transaction in the view_store with a new one. @@ -859,7 +882,7 @@ where &self, xt_hash: ExtrinsicHash, listener_action: F, - ) -> Vec> + ) -> Vec> where F: Fn(&mut crate::graph::Listener, ExtrinsicHash) + Clone @@ -883,7 +906,7 @@ where .chain(self.inactive_views.read().iter()) .filter(|(_, view)| view.is_imported(&xt_hash)) .flat_map(|(_, view)| view.remove_subtree(xt_hash, &listener_action)) - .filter(|xt_hash| seen.insert(*xt_hash)) + .filter_map(|xt| seen.insert(xt.hash).then(|| xt.clone())) .collect(); if let Some(removal_action) = self.pending_txs_tasks.write().get_mut(&xt_hash) { diff --git a/substrate/client/transaction-pool/src/graph/validated_pool.rs b/substrate/client/transaction-pool/src/graph/validated_pool.rs index 64772746e741f..bb097d1d66d1c 100644 --- a/substrate/client/transaction-pool/src/graph/validated_pool.rs +++ b/substrate/client/transaction-pool/src/graph/validated_pool.rs @@ -668,6 +668,11 @@ impl ValidatedPool { /// to prevent them from entering the pool right away. /// Note this is not the case for the dependent transactions - those may /// still be valid so we want to be able to re-import them. + /// + /// For every removed transaction an Invalid event is triggered. + /// + /// Returns the list of actually removed transactions, which may include transactions dependent + /// on provided set. pub fn remove_invalid(&self, hashes: &[ExtrinsicHash]) -> Vec> { // early exit in case there is no invalid transactions. if hashes.is_empty() { @@ -749,6 +754,9 @@ impl ValidatedPool { /// 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. + /// /// A `listener_action` callback function is invoked for every transaction that is removed, /// providing a reference to the pool's listener and the hash of the removed transaction. This /// allows to trigger the required events. @@ -759,10 +767,11 @@ impl ValidatedPool { &self, tx_hash: ExtrinsicHash, listener_action: F, - ) -> Vec> + ) -> Vec> where F: Fn(&mut Listener, ExtrinsicHash), { + self.rotator.ban(&Instant::now(), std::iter::once(tx_hash)); self.pool .write() .remove_subtree(&[tx_hash]) @@ -771,7 +780,7 @@ impl ValidatedPool { let removed_tx_hash = tx.hash; let mut listener = self.listener.write(); listener_action(&mut *listener, removed_tx_hash); - removed_tx_hash + tx.clone() }) .collect::>() } From 8af640f015f8ef13cf551a5755ec0dd1009b6221 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Mon, 13 Jan 2025 17:33:35 +0100 Subject: [PATCH 35/60] tests --- .../transaction-pool/tests/fatp_prios.rs | 234 ++++++++++++++++++ 1 file changed, 234 insertions(+) diff --git a/substrate/client/transaction-pool/tests/fatp_prios.rs b/substrate/client/transaction-pool/tests/fatp_prios.rs index 94ca75a3fe308..75c73fed290e6 100644 --- a/substrate/client/transaction-pool/tests/fatp_prios.rs +++ b/substrate/client/transaction-pool/tests/fatp_prios.rs @@ -26,6 +26,8 @@ use sc_transaction_pool::ChainApi; use sc_transaction_pool_api::{ error::Error as TxPoolError, MaintainedTransactionPool, TransactionPool, TransactionStatus, }; +use sp_blockchain::{ApplyExtrinsicFailed, Error as BlockchainError}; +use sp_runtime::transaction_validity::{InvalidTransaction, TransactionValidityError}; use substrate_test_runtime_client::Sr25519Keyring::*; use substrate_test_runtime_transaction_pool::uxt; #[test] @@ -502,3 +504,235 @@ fn fatp_prios_watcher_full_mempool_does_not_keep_dropped_transaction() { assert_watcher_stream!(xt2_watcher, [TransactionStatus::Ready]); assert_watcher_stream!(xt3_watcher, [TransactionStatus::Ready]); } + +#[test] +fn fatp_invalid_report_stale_or_future_works_as_expected() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = TestPoolBuilder::new().build(); + api.set_nonce(api.genesis_hash(), Bob.into(), 300); + api.set_nonce(api.genesis_hash(), Charlie.into(), 400); + api.set_nonce(api.genesis_hash(), Dave.into(), 500); + + let header01 = api.push_block(1, vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, None, header01.hash()))); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Bob, 300); + let xt2 = uxt(Charlie, 400); + let xt3 = uxt(Dave, 500); + + let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); + let xt1_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap(); + let xt2_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap(); + let xt3_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).unwrap(); + + assert_pool_status!(header01.hash(), &pool, 4, 0); + assert_ready_iterator!(header01.hash(), pool, [xt0, xt1, xt2, xt3]); + + // future/stale are ignored when at is None + let xt0_report = ( + pool.api().hash_and_length(&xt0).0, + Some(BlockchainError::ApplyExtrinsicFailed(ApplyExtrinsicFailed::Validity( + TransactionValidityError::Invalid(InvalidTransaction::Future), + ))), + ); + let invalid_txs = vec![xt0_report]; + let result = pool.report_invalid(None, &invalid_txs[..]); + assert!(result.is_empty()); + assert_ready_iterator!(header01.hash(), pool, [xt0, xt1, xt2, xt3]); + + // future/stale are applied when at is provided + let xt0_report = ( + pool.api().hash_and_length(&xt0).0, + Some(BlockchainError::ApplyExtrinsicFailed(ApplyExtrinsicFailed::Validity( + TransactionValidityError::Invalid(InvalidTransaction::Future), + ))), + ); + let xt1_report = ( + pool.api().hash_and_length(&xt1).0, + Some(BlockchainError::ApplyExtrinsicFailed(ApplyExtrinsicFailed::Validity( + TransactionValidityError::Invalid(InvalidTransaction::Stale), + ))), + ); + let invalid_txs = vec![xt0_report, xt1_report]; + let result = pool.report_invalid(Some(header01.hash()), &invalid_txs[..]); + // stale/future does not cause tx to be removed from the pool + assert!(result.is_empty()); + // assert_eq!(result[0].hash, pool.api().hash_and_length(&xt0).0); + assert_ready_iterator!(header01.hash(), pool, [xt2, xt3]); + + // None error means force removal + // todo + + assert_watcher_stream!(xt0_watcher, [TransactionStatus::Ready]); + assert_watcher_stream!(xt1_watcher, [TransactionStatus::Ready]); + assert_watcher_stream!(xt2_watcher, [TransactionStatus::Ready]); + assert_watcher_stream!(xt3_watcher, [TransactionStatus::Ready]); +} + +#[test] +fn fatp_invalid_report_future_dont_remove_from_pool() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = TestPoolBuilder::new().build(); + api.set_nonce(api.genesis_hash(), Bob.into(), 300); + api.set_nonce(api.genesis_hash(), Charlie.into(), 400); + api.set_nonce(api.genesis_hash(), Dave.into(), 500); + + let header01 = api.push_block(1, vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, None, header01.hash()))); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Bob, 300); + let xt2 = uxt(Charlie, 400); + let xt3 = uxt(Dave, 500); + + let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); + let xt1_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap(); + let xt2_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap(); + let xt3_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).unwrap(); + + assert_pool_status!(header01.hash(), &pool, 4, 0); + assert_ready_iterator!(header01.hash(), pool, [xt0, xt1, xt2, xt3]); + + let xt0_report = ( + pool.api().hash_and_length(&xt0).0, + Some(BlockchainError::ApplyExtrinsicFailed(ApplyExtrinsicFailed::Validity( + TransactionValidityError::Invalid(InvalidTransaction::Stale), + ))), + ); + let xt1_report = ( + pool.api().hash_and_length(&xt1).0, + Some(BlockchainError::ApplyExtrinsicFailed(ApplyExtrinsicFailed::Validity( + TransactionValidityError::Invalid(InvalidTransaction::Future), + ))), + ); + let invalid_txs = vec![xt0_report, xt1_report]; + let result = pool.report_invalid(Some(header01.hash()), &invalid_txs[..]); + // future does not cause tx to be removed from the pool + assert!(result.is_empty()); + assert_ready_iterator!(header01.hash(), pool, [xt2, xt3]); + + let header02 = api.push_block_with_parent(header01.hash(), vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02.hash()))); + assert_pool_status!(header02.hash(), &pool, 4, 0); + assert_ready_iterator!(header02.hash(), pool, [xt2, xt3, xt1, xt0]); + + assert_watcher_stream!(xt0_watcher, [TransactionStatus::Ready]); + assert_watcher_stream!(xt1_watcher, [TransactionStatus::Ready]); + assert_watcher_stream!(xt2_watcher, [TransactionStatus::Ready]); + assert_watcher_stream!(xt3_watcher, [TransactionStatus::Ready]); +} + +#[test] +fn fatp_invalid_tx_is_removed_from_the_pool() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = TestPoolBuilder::new().build(); + api.set_nonce(api.genesis_hash(), Bob.into(), 300); + api.set_nonce(api.genesis_hash(), Charlie.into(), 400); + api.set_nonce(api.genesis_hash(), Dave.into(), 500); + + let header01 = api.push_block(1, vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, None, header01.hash()))); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Bob, 300); + let xt2 = uxt(Charlie, 400); + let xt3 = uxt(Dave, 500); + + let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); + let xt1_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap(); + let xt2_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap(); + let xt3_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).unwrap(); + + assert_pool_status!(header01.hash(), &pool, 4, 0); + assert_ready_iterator!(header01.hash(), pool, [xt0, xt1, xt2, xt3]); + + let xt0_report = ( + pool.api().hash_and_length(&xt0).0, + Some(BlockchainError::ApplyExtrinsicFailed(ApplyExtrinsicFailed::Validity( + TransactionValidityError::Invalid(InvalidTransaction::BadProof), + ))), + ); + let xt1_report = (pool.api().hash_and_length(&xt1).0, None); + let invalid_txs = vec![xt0_report, xt1_report]; + let result = pool.report_invalid(Some(header01.hash()), &invalid_txs[..]); + assert_eq!(result[0].hash, pool.api().hash_and_length(&xt0).0); + assert_pool_status!(header01.hash(), &pool, 2, 0); + assert_ready_iterator!(header01.hash(), pool, [xt2, xt3]); + + let header02 = api.push_block_with_parent(header01.hash(), vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02.hash()))); + assert_pool_status!(header02.hash(), &pool, 2, 0); + assert_ready_iterator!(header02.hash(), pool, [xt2, xt3]); + + assert_watcher_stream!(xt0_watcher, [TransactionStatus::Ready, TransactionStatus::Invalid]); + assert_watcher_stream!(xt1_watcher, [TransactionStatus::Ready, TransactionStatus::Invalid]); + assert_watcher_stream!(xt2_watcher, [TransactionStatus::Ready]); + assert_watcher_stream!(xt3_watcher, [TransactionStatus::Ready]); +} + +#[test] +fn fatp_invalid_tx_is_removed_from_the_pool2() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = TestPoolBuilder::new().build(); + api.set_nonce(api.genesis_hash(), Bob.into(), 300); + api.set_nonce(api.genesis_hash(), Charlie.into(), 400); + api.set_nonce(api.genesis_hash(), Dave.into(), 500); + + let header01 = api.push_block(1, vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, None, header01.hash()))); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Bob, 300); + let xt2 = uxt(Charlie, 400); + let xt3 = uxt(Dave, 500); + + let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); + let xt1_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap(); + let xt2_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap(); + let xt3_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).unwrap(); + + assert_pool_status!(header01.hash(), &pool, 4, 0); + assert_ready_iterator!(header01.hash(), pool, [xt0, xt1, xt2, xt3]); + + let header02a = api.push_block_with_parent(header01.hash(), vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02a.hash()))); + assert_pool_status!(header02a.hash(), &pool, 4, 0); + assert_ready_iterator!(header02a.hash(), pool, [xt0, xt1, xt2, xt3]); + + let header02b = api.push_block_with_parent(header01.hash(), vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header02a.hash()), header02b.hash()))); + + assert_pool_status!(header02b.hash(), &pool, 4, 0); + assert_ready_iterator!(header02b.hash(), pool, [xt0, xt1, xt2, xt3]); + + let xt0_report = ( + pool.api().hash_and_length(&xt0).0, + Some(BlockchainError::ApplyExtrinsicFailed(ApplyExtrinsicFailed::Validity( + TransactionValidityError::Invalid(InvalidTransaction::BadProof), + ))), + ); + let xt1_report = (pool.api().hash_and_length(&xt1).0, None); + let invalid_txs = vec![xt0_report, xt1_report]; + let result = pool.report_invalid(Some(header01.hash()), &invalid_txs[..]); + assert_eq!(result[0].hash, pool.api().hash_and_length(&xt0).0); + assert_ready_iterator!(header01.hash(), pool, [xt2, xt3]); + assert_pool_status!(header02a.hash(), &pool, 2, 0); + assert_ready_iterator!(header02a.hash(), pool, [xt2, xt3]); + assert_pool_status!(header02b.hash(), &pool, 2, 0); + assert_ready_iterator!(header02b.hash(), pool, [xt2, xt3]); + + let header03 = api.push_block_with_parent(header02b.hash(), vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header02b.hash()), header03.hash()))); + assert_pool_status!(header03.hash(), &pool, 2, 0); + assert_ready_iterator!(header03.hash(), pool, [xt2, xt3]); + + assert_watcher_stream!(xt0_watcher, [TransactionStatus::Ready, TransactionStatus::Invalid]); + assert_watcher_stream!(xt1_watcher, [TransactionStatus::Ready, TransactionStatus::Invalid]); + assert_watcher_stream!(xt2_watcher, [TransactionStatus::Ready]); + assert_watcher_stream!(xt3_watcher, [TransactionStatus::Ready]); +} From 0fad26c43a65bfb371d667278981d3c68c3ce9d6 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Mon, 13 Jan 2025 17:43:52 +0100 Subject: [PATCH 36/60] wording --- .../src/fork_aware_txpool/view_store.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) 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 25022b5cb4c4c..c13a4e4226798 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 @@ -667,14 +667,17 @@ where /// also will not be banned. No event will be triggered. /// /// For other errors, the transaction will be removed from the pool, and it will be included in - /// the returned vector. If the tuple's error is None, the transaction will be forcibly removed - /// from the pool. It will be included in the returned vector. Additionally transactions + /// the returned vector. It will be included in the returned vector. Additionally transactions /// provided as input will be banned from re-entering the pool. /// - /// For every transaction removed from the pool an Invalid event is triggered. + /// If the tuple's error is None, the transaction will be forcibly removed from the pool, + /// banned and included into the returned vector. + /// + /// For every transaction removed from the pool (including descendants) an Invalid event is + /// triggered. /// /// Returns the list of actually removed transactions, which may include transactions dependent - /// on provided set. + /// on the provided set. pub(crate) fn report_invalid( &self, at: Option, From ccdb853c25129be14ff1e386e743ec8b42adb76a Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Mon, 13 Jan 2025 17:49:57 +0100 Subject: [PATCH 37/60] cleanup --- .../fork_aware_txpool/fork_aware_txpool.rs | 22 ------------------- 1 file changed, 22 deletions(-) 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 e253adf9fea57..7459c1bcc5971 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 @@ -783,21 +783,6 @@ where }) } - // /// Intended to remove transactions identified by the given hashes, and any dependent - // /// transactions, from the pool. In current implementation this function only outputs the - // error. /// Seems that API change is needed here to make this call reasonable. - // // todo [#5491]: api change? we need block hash here (assuming we need it at all - could be - // // useful for verification for debugging purposes). - // fn remove_invalid(&self, hashes: &[TxHash]) -> Vec> { - // if !hashes.is_empty() { - // log::debug!(target: LOG_TARGET, "fatp::remove_invalid {}", hashes.len()); - // log_xt_trace!(target:LOG_TARGET, hashes, "[{:?}] fatp::remove_invalid"); - // self.metrics - // .report(|metrics| metrics.removed_invalid_txs.inc_by(hashes.len() as _)); - // } - // Default::default() - // } - /// Reports invalid transactions to the transaction pool. /// /// This function takes an array of tuples, each consisting of a transaction hash and the @@ -819,13 +804,6 @@ where self.metrics .report(|metrics| metrics.removed_invalid_txs.inc_by(removed.len() as _)); - // todo (after merging / rebasing) - - // depending on error: - // - handle cloned view with view_store replacements - // - remove resulting hashes from mempool and collect Arc - // - send notification using listener (should this be done in view_store) - removed } From 1b09887d5fa601ee3ad6e562a73f6d81b8bd7862 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Mon, 13 Jan 2025 18:14:23 +0100 Subject: [PATCH 38/60] Cargo.lock updated --- Cargo.lock | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index a7e51d6764231..4e2272bdc9884 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11253,7 +11253,6 @@ dependencies = [ "sc-transaction-pool-api", "serde", "serde_json", - "sp-blockchain", "sp-consensus", "sp-core 28.0.0", "sp-inherents 26.0.0", From f1a1650ac68e387efaf576b539794250e563d53f Mon Sep 17 00:00:00 2001 From: command-bot <> Date: Mon, 13 Jan 2025 17:19:59 +0000 Subject: [PATCH 39/60] Update from michalkucharczyk running command 'prdoc --bump minor --audience node_dev' --- prdoc/pr_6661.prdoc | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 prdoc/pr_6661.prdoc diff --git a/prdoc/pr_6661.prdoc b/prdoc/pr_6661.prdoc new file mode 100644 index 0000000000000..80ec1cb66ac6d --- /dev/null +++ b/prdoc/pr_6661.prdoc @@ -0,0 +1,35 @@ +title: '`txpool api`: `remove_invalid` call improved' +doc: +- audience: Node Dev + description: |- + ### Note + This PR depends on work done in #6647 + + ### Description + Currently the transaction which is reported as invalid by a block builder (or `removed_invalid` by other components) is silently skipped. + + This PR improves this behavior. The transaction pool `report_invalid` function now accepts optional error associated with every reported transaction, and also the optional block hash which provides hints how reported transaction shall be handled. The following API change is proposed: + https://github.com/paritytech/polkadot-sdk/blob/8c8e0336a2697c24d54cff8675c3de7e0ad5a733/substrate/client/transaction-pool/api/src/lib.rs#L293-L310 + + Depending on error, the transaction pool can decide if transaction shall be removed from the view only or entirely from the pool. Invalid event will be dispatched if required. + + + ### Notes for reviewers + + - Actual logic of removing invalid txs is impleneted in [`ViewStore::report_invalid`](https://github.com/paritytech/polkadot-sdk/blob/0fad26c43a65bfb371d667278981d3c68c3ce9d6/substrate/client/transaction-pool/src/fork_aware_txpool/view_store.rs#L657-L680). Method's doc explains the flow. + - This PR changes `HashMap` to `IndexMap` in revalidation logic. This is to preserve the original order of transactions (mainly for purposes of unit tests). + + + + fixes: #6008 +crates: +- name: sc-transaction-pool-api + bump: minor +- name: sc-transaction-pool + bump: minor +- name: sc-rpc-spec-v2 + bump: minor +- name: sc-rpc + bump: minor +- name: sc-basic-authorship + bump: minor From 7ed1c822741779dfcc176e4fbc8f66123eca3503 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Mon, 13 Jan 2025 18:35:17 +0100 Subject: [PATCH 40/60] Update prdoc/pr_6661.prdoc --- prdoc/pr_6661.prdoc | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/prdoc/pr_6661.prdoc b/prdoc/pr_6661.prdoc index 80ec1cb66ac6d..316817686dfd9 100644 --- a/prdoc/pr_6661.prdoc +++ b/prdoc/pr_6661.prdoc @@ -2,22 +2,7 @@ title: '`txpool api`: `remove_invalid` call improved' doc: - audience: Node Dev description: |- - ### Note - This PR depends on work done in #6647 - - ### Description - Currently the transaction which is reported as invalid by a block builder (or `removed_invalid` by other components) is silently skipped. - - This PR improves this behavior. The transaction pool `report_invalid` function now accepts optional error associated with every reported transaction, and also the optional block hash which provides hints how reported transaction shall be handled. The following API change is proposed: - https://github.com/paritytech/polkadot-sdk/blob/8c8e0336a2697c24d54cff8675c3de7e0ad5a733/substrate/client/transaction-pool/api/src/lib.rs#L293-L310 - - Depending on error, the transaction pool can decide if transaction shall be removed from the view only or entirely from the pool. Invalid event will be dispatched if required. - - - ### Notes for reviewers - - - Actual logic of removing invalid txs is impleneted in [`ViewStore::report_invalid`](https://github.com/paritytech/polkadot-sdk/blob/0fad26c43a65bfb371d667278981d3c68c3ce9d6/substrate/client/transaction-pool/src/fork_aware_txpool/view_store.rs#L657-L680). Method's doc explains the flow. - - This PR changes `HashMap` to `IndexMap` in revalidation logic. This is to preserve the original order of transactions (mainly for purposes of unit tests). + Currently the transaction which is reported as invalid by a block builder (or `removed_invalid` by other components) is silently skipped. This PR improves this behavior. The transaction pool `report_invalid` function now accepts optional error associated with every reported transaction, and also the optional block hash which both provide hints how reported invalid transaction shall be handled. Depending on error, the transaction pool can decide if transaction shall be removed from the view only or entirely from the pool. Invalid event will be dispatched if required. From 4aa9bf0a4a14b69da12da27705218b6da74567e1 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Mon, 13 Jan 2025 18:35:45 +0100 Subject: [PATCH 41/60] Update prdoc/pr_6661.prdoc --- prdoc/pr_6661.prdoc | 2 -- 1 file changed, 2 deletions(-) diff --git a/prdoc/pr_6661.prdoc b/prdoc/pr_6661.prdoc index 316817686dfd9..2f35c4b9738c7 100644 --- a/prdoc/pr_6661.prdoc +++ b/prdoc/pr_6661.prdoc @@ -4,8 +4,6 @@ doc: description: |- Currently the transaction which is reported as invalid by a block builder (or `removed_invalid` by other components) is silently skipped. This PR improves this behavior. The transaction pool `report_invalid` function now accepts optional error associated with every reported transaction, and also the optional block hash which both provide hints how reported invalid transaction shall be handled. Depending on error, the transaction pool can decide if transaction shall be removed from the view only or entirely from the pool. Invalid event will be dispatched if required. - - fixes: #6008 crates: - name: sc-transaction-pool-api From 2db11e5519174ccfd37109eec9f3c82467519ee9 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Mon, 13 Jan 2025 18:36:10 +0100 Subject: [PATCH 42/60] Update prdoc/pr_6661.prdoc --- prdoc/pr_6661.prdoc | 1 - 1 file changed, 1 deletion(-) diff --git a/prdoc/pr_6661.prdoc b/prdoc/pr_6661.prdoc index 2f35c4b9738c7..2e39646fededa 100644 --- a/prdoc/pr_6661.prdoc +++ b/prdoc/pr_6661.prdoc @@ -4,7 +4,6 @@ doc: description: |- Currently the transaction which is reported as invalid by a block builder (or `removed_invalid` by other components) is silently skipped. This PR improves this behavior. The transaction pool `report_invalid` function now accepts optional error associated with every reported transaction, and also the optional block hash which both provide hints how reported invalid transaction shall be handled. Depending on error, the transaction pool can decide if transaction shall be removed from the view only or entirely from the pool. Invalid event will be dispatched if required. - fixes: #6008 crates: - name: sc-transaction-pool-api bump: minor From f97ec19725f2ce8a5efdf5bc64d9414754c21c66 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Mon, 13 Jan 2025 19:04:02 +0100 Subject: [PATCH 43/60] deps --- Cargo.lock | 1 + substrate/bin/node/bench/Cargo.toml | 1 + 2 files changed, 2 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 4e2272bdc9884..a7e51d6764231 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11253,6 +11253,7 @@ dependencies = [ "sc-transaction-pool-api", "serde", "serde_json", + "sp-blockchain", "sp-consensus", "sp-core 28.0.0", "sp-inherents 26.0.0", diff --git a/substrate/bin/node/bench/Cargo.toml b/substrate/bin/node/bench/Cargo.toml index 83f7b82cd2b51..b5f94060bb657 100644 --- a/substrate/bin/node/bench/Cargo.toml +++ b/substrate/bin/node/bench/Cargo.toml @@ -36,6 +36,7 @@ sc-transaction-pool = { workspace = true, default-features = true } sc-transaction-pool-api = { workspace = true, default-features = true } serde = { workspace = true, default-features = true } serde_json = { workspace = true, default-features = true } +sp-blockchain = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-inherents = { workspace = true, default-features = true } From bac9d89978cdc76fe36a1d4f53aa69ac22320313 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Tue, 14 Jan 2025 16:48:18 +0100 Subject: [PATCH 44/60] tests: minor improvements --- substrate/client/transaction-pool/tests/fatp.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate/client/transaction-pool/tests/fatp.rs b/substrate/client/transaction-pool/tests/fatp.rs index 8bf08122995c1..1c5cd0654a71c 100644 --- a/substrate/client/transaction-pool/tests/fatp.rs +++ b/substrate/client/transaction-pool/tests/fatp.rs @@ -866,11 +866,11 @@ fn fatp_watcher_invalid_fails_on_submission() { block_on(pool.maintain(event)); let xt0 = uxt(Alice, 150); - api.add_invalid(&xt0); let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())); let xt0_watcher = xt0_watcher.map(|_| ()); assert_pool_status!(header01.hash(), &pool, 0, 0); + // Alice's nonce in state is 200, tx is 150. assert!(matches!( xt0_watcher.unwrap_err().into_pool_error(), Ok(TxPoolError::InvalidTransaction(InvalidTransaction::Stale)) From 7081c59955ddaa027c060c566c96e0372617067b Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Tue, 14 Jan 2025 18:02:01 +0100 Subject: [PATCH 45/60] test fixed --- substrate/client/transaction-pool/tests/fatp_prios.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/substrate/client/transaction-pool/tests/fatp_prios.rs b/substrate/client/transaction-pool/tests/fatp_prios.rs index 06bfb22c5cf29..b61ec14e1d556 100644 --- a/substrate/client/transaction-pool/tests/fatp_prios.rs +++ b/substrate/client/transaction-pool/tests/fatp_prios.rs @@ -652,6 +652,9 @@ fn fatp_invalid_report_future_dont_remove_from_pool() { assert_pool_status!(header01.hash(), &pool, 4, 0); assert_ready_iterator!(header01.hash(), pool, [xt0, xt1, xt2, xt3]); + let header02 = api.push_block_with_parent(header01.hash(), vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02.hash()))); + let xt0_report = ( pool.api().hash_and_length(&xt0).0, Some(BlockchainError::ApplyExtrinsicFailed(ApplyExtrinsicFailed::Validity( @@ -670,10 +673,8 @@ fn fatp_invalid_report_future_dont_remove_from_pool() { assert!(result.is_empty()); assert_ready_iterator!(header01.hash(), pool, [xt2, xt3]); - let header02 = api.push_block_with_parent(header01.hash(), vec![], true); - block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02.hash()))); assert_pool_status!(header02.hash(), &pool, 4, 0); - assert_ready_iterator!(header02.hash(), pool, [xt2, xt3, xt1, xt0]); + assert_ready_iterator!(header02.hash(), pool, [xt0, xt1, xt2, xt3]); assert_watcher_stream!(xt0_watcher, [TransactionStatus::Ready]); assert_watcher_stream!(xt1_watcher, [TransactionStatus::Ready]); From 686a427d923dc1cce3d745820c2dbde68d6a954e Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Wed, 15 Jan 2025 15:11:59 +0100 Subject: [PATCH 46/60] Apply suggestions from code review Co-authored-by: Sebastian Kunert --- .../transaction-pool/src/fork_aware_txpool/view_store.rs | 6 +++--- .../src/single_state_txpool/single_state_txpool.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) 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 c4184b7738b0d..260fa1c1bd9a5 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 @@ -667,7 +667,7 @@ where /// also will not be banned. No event will be triggered. /// /// For other errors, the transaction will be removed from the pool, and it will be included in - /// the returned vector. It will be included in the returned vector. Additionally transactions + /// the returned vector. Additionally, transactions /// provided as input will be banned from re-entering the pool. /// /// If the tuple's error is None, the transaction will be forcibly removed from the pool, @@ -703,7 +703,7 @@ where // be in the pool. at.map(|at| { self.get_view_at(at, true) - .map(|(view, _)| view.remove_invalid(&remove_from_view[..])) + .map(|(view, _)| view.remove_invalid(&remove_from_view)) }); let mut removed = vec![]; @@ -714,7 +714,7 @@ where }); removed.extend(removed_from_pool); } - self.listener.invalidate_transactions(&remove_from_pool[..]); + self.listener.invalidate_transactions(&remove_from_pool); removed } diff --git a/substrate/client/transaction-pool/src/single_state_txpool/single_state_txpool.rs b/substrate/client/transaction-pool/src/single_state_txpool/single_state_txpool.rs index a5166b9bbb596..cd86919f91119 100644 --- a/substrate/client/transaction-pool/src/single_state_txpool/single_state_txpool.rs +++ b/substrate/client/transaction-pool/src/single_state_txpool/single_state_txpool.rs @@ -329,7 +329,7 @@ where invalid_tx_errors: &[(TxHash, Option)], ) -> Vec> { let hashes = invalid_tx_errors.iter().map(|(hash, _)| *hash).collect::>(); - let removed = self.pool.validated_pool().remove_invalid(&hashes[..]); + let removed = self.pool.validated_pool().remove_invalid(&hashes); self.metrics .report(|metrics| metrics.validations_invalid.inc_by(removed.len() as u64)); removed From 4200ccc206c151894e447798c6cb8b2817559dab Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Thu, 16 Jan 2025 12:28:41 +0100 Subject: [PATCH 47/60] Comment reviews applied --- .../src/fork_aware_txpool/view.rs | 4 +-- .../src/fork_aware_txpool/view_store.rs | 7 ++--- .../src/graph/validated_pool.rs | 27 +++++++------------ 3 files changed, 16 insertions(+), 22 deletions(-) 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 6ce1cf329359d..a5755bfcaa37d 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/view.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/view.rs @@ -478,12 +478,12 @@ where /// Refer to [`crate::graph::ValidatedPool::remove_subtree`] for more details. pub fn remove_subtree( &self, - tx_hash: ExtrinsicHash, + hashes: &[ExtrinsicHash], listener_action: F, ) -> Vec> where F: Fn(&mut crate::graph::Listener, ExtrinsicHash), { - self.pool.validated_pool().remove_subtree(tx_hash, listener_action) + self.pool.validated_pool().remove_subtree(hashes, 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 260fa1c1bd9a5..8b8cc58e1fd73 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 @@ -664,7 +664,8 @@ where /// /// Invalid future and stale transaction will be removed only from given `at` view, and will be /// kept in the transaction pool. Such transaction will not be reported in returned vector. They - /// also will not be banned. No event will be triggered. + /// also will not be banned from re-entering the pool (however can be rejected from re-entring + /// the view). No event will be triggered. /// /// For other errors, the transaction will be removed from the pool, and it will be included in /// the returned vector. Additionally, transactions @@ -776,7 +777,7 @@ where )); }, PreInsertAction::RemoveSubtree(ref removal) => { - view.remove_subtree(removal.xt_hash, &*removal.listener_action); + view.remove_subtree(&[removal.xt_hash], &*removal.listener_action); }, } } @@ -908,7 +909,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], &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 72a656ecb21f5..14e1c479f241f 100644 --- a/substrate/client/transaction-pool/src/graph/validated_pool.rs +++ b/substrate/client/transaction-pool/src/graph/validated_pool.rs @@ -704,21 +704,13 @@ impl ValidatedPool { return vec![] } - log::trace!(target: LOG_TARGET, "Removing invalid transactions: {:?}", hashes.len()); - - // temporarily ban invalid transactions - self.rotator.ban(&Instant::now(), hashes.iter().cloned()); - - let invalid = self.pool.write().remove_subtree(hashes); + let invalid = self.remove_subtree(hashes, |listener, removed_tx_hash| { + listener.invalid(&removed_tx_hash); + }); - log::trace!(target: LOG_TARGET, "Removed invalid transactions: {:?}", invalid.len()); + log::trace!(target: LOG_TARGET, "Removed invalid transactions: {:?}/{:?}", hashes.len(), invalid.len()); log_xt_trace!(target: LOG_TARGET, invalid.iter().map(|t| t.hash), "{:?} Removed invalid transaction"); - let mut listener = self.listener.write(); - for tx in &invalid { - listener.invalid(&tx.hash); - } - invalid } @@ -790,16 +782,17 @@ impl ValidatedPool { /// transaction specified by `tx_hash`. pub fn remove_subtree( &self, - tx_hash: ExtrinsicHash, + hashes: &[ExtrinsicHash], listener_action: F, ) -> Vec> where F: Fn(&mut Listener, ExtrinsicHash), { - self.rotator.ban(&Instant::now(), std::iter::once(tx_hash)); - self.pool - .write() - .remove_subtree(&[tx_hash]) + // temporarily ban invalid transactions + self.rotator.ban(&Instant::now(), hashes.iter().cloned()); + let removed = self.pool.write().remove_subtree(hashes); + + removed .into_iter() .map(|tx| { let removed_tx_hash = tx.hash; From 5bb5f8ac7a186a17a86053dea028647f4444bd6d Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Thu, 16 Jan 2025 12:53:25 +0100 Subject: [PATCH 48/60] tests: fatp_invalid added --- .../client/transaction-pool/tests/fatp.rs | 331 +-------- .../transaction-pool/tests/fatp_invalid.rs | 645 ++++++++++++++++++ .../transaction-pool/tests/fatp_prios.rs | 235 ------- 3 files changed, 647 insertions(+), 564 deletions(-) create mode 100644 substrate/client/transaction-pool/tests/fatp_invalid.rs diff --git a/substrate/client/transaction-pool/tests/fatp.rs b/substrate/client/transaction-pool/tests/fatp.rs index c427a2ad149e3..ecdf2b0a0835f 100644 --- a/substrate/client/transaction-pool/tests/fatp.rs +++ b/substrate/client/transaction-pool/tests/fatp.rs @@ -25,8 +25,8 @@ use fatp_common::{ use futures::{executor::block_on, task::Poll, FutureExt, StreamExt}; use sc_transaction_pool::ChainApi; use sc_transaction_pool_api::{ - error::{Error as TxPoolError, IntoPoolError}, - ChainEvent, MaintainedTransactionPool, TransactionPool, TransactionStatus, + error::Error as TxPoolError, ChainEvent, MaintainedTransactionPool, TransactionPool, + TransactionStatus, }; use sp_runtime::transaction_validity::InvalidTransaction; use std::{sync::Arc, time::Duration}; @@ -854,112 +854,6 @@ fn fatp_fork_finalization_removes_stale_views() { assert_eq!(pool.active_views_count(), 1); } -#[test] -fn fatp_watcher_invalid_fails_on_submission() { - sp_tracing::try_init_simple(); - - let (pool, api, _) = pool(); - - let header01 = api.push_block(1, vec![], true); - - let event = new_best_block_event(&pool, None, header01.hash()); - block_on(pool.maintain(event)); - - let xt0 = uxt(Alice, 150); - let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())); - let xt0_watcher = xt0_watcher.map(|_| ()); - - assert_pool_status!(header01.hash(), &pool, 0, 0); - // Alice's nonce in state is 200, tx is 150. - assert!(matches!( - xt0_watcher.unwrap_err().into_pool_error(), - Ok(TxPoolError::InvalidTransaction(InvalidTransaction::Stale)) - )); -} - -#[test] -fn fatp_watcher_invalid_single_revalidation() { - sp_tracing::try_init_simple(); - - let (pool, api, _) = pool(); - - let header01 = api.push_block(1, vec![], true); - let event = new_best_block_event(&pool, Some(api.genesis_hash()), header01.hash()); - block_on(pool.maintain(event)); - - let xt0 = uxt(Alice, 200); - let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); - - api.add_invalid(&xt0); - - let header02 = api.push_block_with_parent(header01.hash(), vec![], true); - let event = finalized_block_event(&pool, header01.hash(), header02.hash()); - block_on(pool.maintain(event)); - - // wait 10 blocks for revalidation - let mut prev_header = header02; - for n in 3..=11 { - let header = api.push_block(n, vec![], true); - let event = finalized_block_event(&pool, prev_header.hash(), header.hash()); - block_on(pool.maintain(event)); - prev_header = header; - } - - let xt0_events = futures::executor::block_on_stream(xt0_watcher).collect::>(); - log::debug!("xt0_events: {:#?}", xt0_events); - assert_eq!(xt0_events, vec![TransactionStatus::Ready, TransactionStatus::Invalid]); -} - -#[test] -fn fatp_watcher_invalid_single_revalidation2() { - sp_tracing::try_init_simple(); - - let (pool, api, _) = pool(); - - let xt0 = uxt(Alice, 200); - let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); - assert_eq!(pool.mempool_len(), (0, 1)); - api.add_invalid(&xt0); - - let header01 = api.push_block(1, vec![], true); - let event = new_best_block_event(&pool, None, header01.hash()); - block_on(pool.maintain(event)); - - let xt0_events = futures::executor::block_on_stream(xt0_watcher).collect::>(); - log::debug!("xt0_events: {:#?}", xt0_events); - assert_eq!(xt0_events, vec![TransactionStatus::Invalid]); - assert_eq!(pool.mempool_len(), (0, 0)); -} - -#[test] -fn fatp_watcher_invalid_single_revalidation3() { - sp_tracing::try_init_simple(); - - let (pool, api, _) = pool(); - - let xt0 = uxt(Alice, 150); - let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); - assert_eq!(pool.mempool_len(), (0, 1)); - - let header01 = api.push_block(1, vec![], true); - let event = finalized_block_event(&pool, api.genesis_hash(), header01.hash()); - block_on(pool.maintain(event)); - - // wait 10 blocks for revalidation - let mut prev_header = header01; - for n in 2..=11 { - let header = api.push_block(n, vec![], true); - let event = finalized_block_event(&pool, prev_header.hash(), header.hash()); - block_on(pool.maintain(event)); - prev_header = header; - } - - let xt0_events = futures::executor::block_on_stream(xt0_watcher).collect::>(); - log::debug!("xt0_events: {:#?}", xt0_events); - assert_eq!(xt0_events, vec![TransactionStatus::Invalid]); - assert_eq!(pool.mempool_len(), (0, 0)); -} - #[test] fn fatp_watcher_future() { sp_tracing::try_init_simple(); @@ -1835,180 +1729,6 @@ fn fatp_watcher_best_block_after_finalization_does_not_retract() { ); } -#[test] -fn fatp_watcher_invalid_many_revalidation() { - sp_tracing::try_init_simple(); - - let (pool, api, _) = pool(); - - let header01 = api.push_block(1, vec![], true); - block_on(pool.maintain(new_best_block_event(&pool, None, header01.hash()))); - - let xt0 = uxt(Alice, 200); - let xt1 = uxt(Alice, 201); - let xt2 = uxt(Alice, 202); - let xt3 = uxt(Alice, 203); - let xt4 = uxt(Alice, 204); - - let submissions = vec![ - pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone()), - pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone()), - pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone()), - pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone()), - pool.submit_and_watch(invalid_hash(), SOURCE, xt4.clone()), - ]; - - let submissions = block_on(futures::future::join_all(submissions)); - assert_eq!(pool.status_all()[&header01.hash()].ready, 5); - - let mut watchers = submissions.into_iter().map(Result::unwrap).collect::>(); - let xt4_watcher = watchers.remove(4); - let xt3_watcher = watchers.remove(3); - let xt2_watcher = watchers.remove(2); - let xt1_watcher = watchers.remove(1); - let xt0_watcher = watchers.remove(0); - - api.add_invalid(&xt3); - api.add_invalid(&xt4); - - let header02 = api.push_block(2, vec![], true); - block_on(pool.maintain(finalized_block_event(&pool, header01.hash(), header02.hash()))); - - //todo: shall revalidation check finalized (fork's tip) view? - assert_eq!(pool.status_all()[&header02.hash()].ready, 5); - - let header03 = api.push_block(3, vec![xt0.clone(), xt1.clone(), xt2.clone()], true); - block_on(pool.maintain(finalized_block_event(&pool, header02.hash(), header03.hash()))); - - // wait 10 blocks for revalidation - let mut prev_header = header03.clone(); - for n in 4..=11 { - let header = api.push_block(n, vec![], true); - let event = finalized_block_event(&pool, prev_header.hash(), header.hash()); - block_on(pool.maintain(event)); - prev_header = header; - } - - let xt0_events = futures::executor::block_on_stream(xt0_watcher).collect::>(); - let xt1_events = futures::executor::block_on_stream(xt1_watcher).collect::>(); - let xt2_events = futures::executor::block_on_stream(xt2_watcher).collect::>(); - let xt3_events = futures::executor::block_on_stream(xt3_watcher).collect::>(); - let xt4_events = futures::executor::block_on_stream(xt4_watcher).collect::>(); - - log::debug!("xt0_events: {:#?}", xt0_events); - log::debug!("xt1_events: {:#?}", xt1_events); - log::debug!("xt2_events: {:#?}", xt2_events); - log::debug!("xt3_events: {:#?}", xt3_events); - log::debug!("xt4_events: {:#?}", xt4_events); - - assert_eq!( - xt0_events, - vec![ - TransactionStatus::Ready, - TransactionStatus::InBlock((header03.hash(), 0)), - TransactionStatus::Finalized((header03.hash(), 0)) - ], - ); - assert_eq!( - xt1_events, - vec![ - TransactionStatus::Ready, - TransactionStatus::InBlock((header03.hash(), 1)), - TransactionStatus::Finalized((header03.hash(), 1)) - ], - ); - assert_eq!( - xt2_events, - vec![ - TransactionStatus::Ready, - TransactionStatus::InBlock((header03.hash(), 2)), - TransactionStatus::Finalized((header03.hash(), 2)) - ], - ); - assert_eq!(xt3_events, vec![TransactionStatus::Ready, TransactionStatus::Invalid],); - assert_eq!(xt4_events, vec![TransactionStatus::Ready, TransactionStatus::Invalid],); -} - -#[test] -fn should_not_retain_invalid_hashes_from_retracted() { - sp_tracing::try_init_simple(); - - let (pool, api, _) = pool(); - let xt = uxt(Alice, 200); - - let header01 = api.push_block(1, vec![], true); - block_on(pool.maintain(new_best_block_event(&pool, None, header01.hash()))); - let watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt.clone())).unwrap(); - - let header02a = api.push_block_with_parent(header01.hash(), vec![xt.clone()], true); - - block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02a.hash()))); - assert_eq!(pool.status_all()[&header02a.hash()].ready, 0); - - api.add_invalid(&xt); - let header02b = api.push_block_with_parent(header01.hash(), vec![], true); - block_on(pool.maintain(finalized_block_event(&pool, api.genesis_hash(), header02b.hash()))); - - // wait 10 blocks for revalidation - let mut prev_header = header02b.clone(); - for _ in 3..=11 { - let header = api.push_block_with_parent(prev_header.hash(), vec![], true); - let event = finalized_block_event(&pool, prev_header.hash(), header.hash()); - block_on(pool.maintain(event)); - prev_header = header; - } - - assert_eq!( - futures::executor::block_on_stream(watcher).collect::>(), - vec![ - TransactionStatus::Ready, - TransactionStatus::InBlock((header02a.hash(), 0)), - TransactionStatus::Invalid - ], - ); - - //todo: shall revalidation check finalized (fork's tip) view? - assert_eq!(pool.status_all()[&prev_header.hash()].ready, 0); -} - -#[test] -fn should_revalidate_during_maintenance() { - sp_tracing::try_init_simple(); - - let (pool, api, _) = pool(); - let xt1 = uxt(Alice, 200); - let xt2 = uxt(Alice, 201); - - let header01 = api.push_block(1, vec![], true); - block_on(pool.maintain(new_best_block_event(&pool, None, header01.hash()))); - - block_on(pool.submit_one(invalid_hash(), SOURCE, xt1.clone())).unwrap(); - let watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap(); - assert_eq!(pool.status_all()[&header01.hash()].ready, 2); - assert_eq!(api.validation_requests().len(), 2); - - let header02 = api.push_block(2, vec![xt1.clone()], true); - api.add_invalid(&xt2); - block_on(pool.maintain(finalized_block_event(&pool, api.genesis_hash(), header02.hash()))); - - //todo: shall revalidation check finalized (fork's tip) view? - assert_eq!(pool.status_all()[&header02.hash()].ready, 1); - - // wait 10 blocks for revalidation - let mut prev_header = header02.clone(); - for _ in 3..=11 { - let header = api.push_block_with_parent(prev_header.hash(), vec![], true); - let event = finalized_block_event(&pool, prev_header.hash(), header.hash()); - block_on(pool.maintain(event)); - prev_header = header; - } - - assert_eq!( - futures::executor::block_on_stream(watcher).collect::>(), - vec![TransactionStatus::Ready, TransactionStatus::Invalid], - ); -} - #[test] fn fatp_transactions_purging_stale_on_finalization_works() { sp_tracing::try_init_simple(); @@ -2057,53 +1777,6 @@ fn fatp_transactions_purging_stale_on_finalization_works() { ); } -#[test] -fn fatp_transactions_purging_invalid_on_finalization_works() { - sp_tracing::try_init_simple(); - - let (pool, api, _) = pool(); - - let xt1 = uxt(Alice, 200); - let xt2 = uxt(Alice, 201); - let xt3 = uxt(Alice, 202); - - let header01 = api.push_block(1, vec![], true); - block_on(pool.maintain(new_best_block_event(&pool, None, header01.hash()))); - - let watcher1 = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap(); - let watcher2 = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap(); - block_on(pool.submit_one(invalid_hash(), SOURCE, xt3.clone())).unwrap(); - - assert_eq!(api.validation_requests().len(), 3); - assert_eq!(pool.status_all()[&header01.hash()].ready, 3); - assert_eq!(pool.mempool_len(), (1, 2)); - - let header02 = api.push_block(2, vec![], true); - api.add_invalid(&xt1); - api.add_invalid(&xt2); - api.add_invalid(&xt3); - block_on(pool.maintain(finalized_block_event(&pool, header01.hash(), header02.hash()))); - - // wait 10 blocks for revalidation - let mut prev_header = header02; - for n in 3..=13 { - let header = api.push_block(n, vec![], true); - let event = finalized_block_event(&pool, prev_header.hash(), header.hash()); - block_on(pool.maintain(event)); - prev_header = header; - } - - //todo: should it work at all? (it requires better revalidation: mempool keeping validated txs) - //additionally it also requires revalidation of finalized view. - // assert_eq!(pool.status_all()[&header02.hash()].ready, 0); - assert_eq!(pool.mempool_len(), (0, 0)); - - let xt1_events = futures::executor::block_on_stream(watcher1).collect::>(); - let xt2_events = futures::executor::block_on_stream(watcher2).collect::>(); - assert_eq!(xt1_events, vec![TransactionStatus::Ready, TransactionStatus::Invalid]); - assert_eq!(xt2_events, vec![TransactionStatus::Ready, TransactionStatus::Invalid]); -} - #[test] fn import_sink_works() { sp_tracing::try_init_simple(); diff --git a/substrate/client/transaction-pool/tests/fatp_invalid.rs b/substrate/client/transaction-pool/tests/fatp_invalid.rs new file mode 100644 index 0000000000000..dec9c4d6032da --- /dev/null +++ b/substrate/client/transaction-pool/tests/fatp_invalid.rs @@ -0,0 +1,645 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Tests of invalid transactions handling for fork-aware transaction pool. + +pub mod fatp_common; + +use fatp_common::{ + finalized_block_event, invalid_hash, new_best_block_event, pool, TestPoolBuilder, LOG_TARGET, + SOURCE, +}; +use futures::{executor::block_on, FutureExt}; +use sc_transaction_pool::ChainApi; +use sc_transaction_pool_api::{ + error::{Error as TxPoolError, IntoPoolError}, + MaintainedTransactionPool, TransactionPool, TransactionStatus, +}; +use sp_blockchain::{ApplyExtrinsicFailed, Error as BlockchainError}; +use sp_runtime::transaction_validity::{InvalidTransaction, TransactionValidityError}; +use substrate_test_runtime_client::Sr25519Keyring::*; +use substrate_test_runtime_transaction_pool::uxt; + +#[test] +fn fatp_invalid_devel_ideas() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + + let xt0 = uxt(Alice, 206); + let xt1 = uxt(Alice, 206); + + let result = block_on(pool.submit_one(invalid_hash(), SOURCE, xt1.clone())); + assert!(result.is_ok()); + + let header01a = api.push_block(1, vec![xt0.clone()], true); + let header01b = api.push_block(1, vec![xt0.clone()], true); + + api.set_nonce(header01a.hash(), Alice.into(), 201); + api.set_nonce(header01b.hash(), Alice.into(), 202); + + let event = new_best_block_event(&pool, None, header01a.hash()); + block_on(pool.maintain(event)); + + let event = new_best_block_event(&pool, None, header01b.hash()); + block_on(pool.maintain(event)); + + let mut xts = (199..204).map(|i| uxt(Alice, i)).collect::>(); + xts.push(xt0); + xts.push(xt1); + + let results = block_on(pool.submit_at(invalid_hash(), SOURCE, xts.clone())).unwrap(); + + log::debug!(target:LOG_TARGET, "res: {:#?}", results); + log::debug!(target:LOG_TARGET, "stats: {:#?}", pool.status_all()); + + (0..2).for_each(|i| { + assert!(matches!( + results[i].as_ref().unwrap_err().0, + TxPoolError::InvalidTransaction(InvalidTransaction::Stale,) + )); + }); + //note: tx at 2 is valid at header01a and invalid at header01b + (2..5).for_each(|i| { + assert_eq!(*results[i].as_ref().unwrap(), api.hash_and_length(&xts[i]).0); + }); + //xt0 at index 5 (transaction from the imported block, gets banned when pruned) + assert!(matches!(results[5].as_ref().unwrap_err().0, TxPoolError::TemporarilyBanned)); + //xt1 at index 6 + assert!(matches!(results[6].as_ref().unwrap_err().0, TxPoolError::AlreadyImported(_))); +} + +#[test] +fn fatp_transactions_purging_invalid_on_finalization_works() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + + let xt1 = uxt(Alice, 200); + let xt2 = uxt(Alice, 201); + let xt3 = uxt(Alice, 202); + + let header01 = api.push_block(1, vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, None, header01.hash()))); + + let watcher1 = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap(); + let watcher2 = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap(); + block_on(pool.submit_one(invalid_hash(), SOURCE, xt3.clone())).unwrap(); + + assert_eq!(api.validation_requests().len(), 3); + assert_eq!(pool.status_all()[&header01.hash()].ready, 3); + assert_eq!(pool.mempool_len(), (1, 2)); + + let header02 = api.push_block(2, vec![], true); + api.add_invalid(&xt1); + api.add_invalid(&xt2); + api.add_invalid(&xt3); + block_on(pool.maintain(finalized_block_event(&pool, header01.hash(), header02.hash()))); + + // wait 10 blocks for revalidation + let mut prev_header = header02; + for n in 3..=13 { + let header = api.push_block(n, vec![], true); + let event = finalized_block_event(&pool, prev_header.hash(), header.hash()); + block_on(pool.maintain(event)); + prev_header = header; + } + + //todo: should it work at all? (it requires better revalidation: mempool keeping validated txs) + //additionally it also requires revalidation of finalized view. + // assert_eq!(pool.status_all()[&header02.hash()].ready, 0); + assert_eq!(pool.mempool_len(), (0, 0)); + + let xt1_events = futures::executor::block_on_stream(watcher1).collect::>(); + let xt2_events = futures::executor::block_on_stream(watcher2).collect::>(); + assert_eq!(xt1_events, vec![TransactionStatus::Ready, TransactionStatus::Invalid]); + assert_eq!(xt2_events, vec![TransactionStatus::Ready, TransactionStatus::Invalid]); +} + +#[test] +fn should_revalidate_during_maintenance() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + let xt1 = uxt(Alice, 200); + let xt2 = uxt(Alice, 201); + + let header01 = api.push_block(1, vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, None, header01.hash()))); + + block_on(pool.submit_one(invalid_hash(), SOURCE, xt1.clone())).unwrap(); + let watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap(); + assert_eq!(pool.status_all()[&header01.hash()].ready, 2); + assert_eq!(api.validation_requests().len(), 2); + + let header02 = api.push_block(2, vec![xt1.clone()], true); + api.add_invalid(&xt2); + block_on(pool.maintain(finalized_block_event(&pool, api.genesis_hash(), header02.hash()))); + + //todo: shall revalidation check finalized (fork's tip) view? + assert_eq!(pool.status_all()[&header02.hash()].ready, 1); + + // wait 10 blocks for revalidation + let mut prev_header = header02.clone(); + for _ in 3..=11 { + let header = api.push_block_with_parent(prev_header.hash(), vec![], true); + let event = finalized_block_event(&pool, prev_header.hash(), header.hash()); + block_on(pool.maintain(event)); + prev_header = header; + } + + assert_eq!( + futures::executor::block_on_stream(watcher).collect::>(), + vec![TransactionStatus::Ready, TransactionStatus::Invalid], + ); +} + +#[test] +fn should_not_retain_invalid_hashes_from_retracted() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + let xt = uxt(Alice, 200); + + let header01 = api.push_block(1, vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, None, header01.hash()))); + let watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt.clone())).unwrap(); + + let header02a = api.push_block_with_parent(header01.hash(), vec![xt.clone()], true); + + block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02a.hash()))); + assert_eq!(pool.status_all()[&header02a.hash()].ready, 0); + + api.add_invalid(&xt); + let header02b = api.push_block_with_parent(header01.hash(), vec![], true); + block_on(pool.maintain(finalized_block_event(&pool, api.genesis_hash(), header02b.hash()))); + + // wait 10 blocks for revalidation + let mut prev_header = header02b.clone(); + for _ in 3..=11 { + let header = api.push_block_with_parent(prev_header.hash(), vec![], true); + let event = finalized_block_event(&pool, prev_header.hash(), header.hash()); + block_on(pool.maintain(event)); + prev_header = header; + } + + assert_eq!( + futures::executor::block_on_stream(watcher).collect::>(), + vec![ + TransactionStatus::Ready, + TransactionStatus::InBlock((header02a.hash(), 0)), + TransactionStatus::Invalid + ], + ); + + //todo: shall revalidation check finalized (fork's tip) view? + assert_eq!(pool.status_all()[&prev_header.hash()].ready, 0); +} + +#[test] +fn fatp_watcher_invalid_many_revalidation() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + + let header01 = api.push_block(1, vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, None, header01.hash()))); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Alice, 201); + let xt2 = uxt(Alice, 202); + let xt3 = uxt(Alice, 203); + let xt4 = uxt(Alice, 204); + + let submissions = vec![ + pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone()), + pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone()), + pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone()), + pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone()), + pool.submit_and_watch(invalid_hash(), SOURCE, xt4.clone()), + ]; + + let submissions = block_on(futures::future::join_all(submissions)); + assert_eq!(pool.status_all()[&header01.hash()].ready, 5); + + let mut watchers = submissions.into_iter().map(Result::unwrap).collect::>(); + let xt4_watcher = watchers.remove(4); + let xt3_watcher = watchers.remove(3); + let xt2_watcher = watchers.remove(2); + let xt1_watcher = watchers.remove(1); + let xt0_watcher = watchers.remove(0); + + api.add_invalid(&xt3); + api.add_invalid(&xt4); + + let header02 = api.push_block(2, vec![], true); + block_on(pool.maintain(finalized_block_event(&pool, header01.hash(), header02.hash()))); + + //todo: shall revalidation check finalized (fork's tip) view? + assert_eq!(pool.status_all()[&header02.hash()].ready, 5); + + let header03 = api.push_block(3, vec![xt0.clone(), xt1.clone(), xt2.clone()], true); + block_on(pool.maintain(finalized_block_event(&pool, header02.hash(), header03.hash()))); + + // wait 10 blocks for revalidation + let mut prev_header = header03.clone(); + for n in 4..=11 { + let header = api.push_block(n, vec![], true); + let event = finalized_block_event(&pool, prev_header.hash(), header.hash()); + block_on(pool.maintain(event)); + prev_header = header; + } + + let xt0_events = futures::executor::block_on_stream(xt0_watcher).collect::>(); + let xt1_events = futures::executor::block_on_stream(xt1_watcher).collect::>(); + let xt2_events = futures::executor::block_on_stream(xt2_watcher).collect::>(); + let xt3_events = futures::executor::block_on_stream(xt3_watcher).collect::>(); + let xt4_events = futures::executor::block_on_stream(xt4_watcher).collect::>(); + + log::debug!("xt0_events: {:#?}", xt0_events); + log::debug!("xt1_events: {:#?}", xt1_events); + log::debug!("xt2_events: {:#?}", xt2_events); + log::debug!("xt3_events: {:#?}", xt3_events); + log::debug!("xt4_events: {:#?}", xt4_events); + + assert_eq!( + xt0_events, + vec![ + TransactionStatus::Ready, + TransactionStatus::InBlock((header03.hash(), 0)), + TransactionStatus::Finalized((header03.hash(), 0)) + ], + ); + assert_eq!( + xt1_events, + vec![ + TransactionStatus::Ready, + TransactionStatus::InBlock((header03.hash(), 1)), + TransactionStatus::Finalized((header03.hash(), 1)) + ], + ); + assert_eq!( + xt2_events, + vec![ + TransactionStatus::Ready, + TransactionStatus::InBlock((header03.hash(), 2)), + TransactionStatus::Finalized((header03.hash(), 2)) + ], + ); + assert_eq!(xt3_events, vec![TransactionStatus::Ready, TransactionStatus::Invalid],); + assert_eq!(xt4_events, vec![TransactionStatus::Ready, TransactionStatus::Invalid],); +} + +#[test] +fn fatp_watcher_invalid_fails_on_submission() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + + let header01 = api.push_block(1, vec![], true); + + let event = new_best_block_event(&pool, None, header01.hash()); + block_on(pool.maintain(event)); + + let xt0 = uxt(Alice, 150); + let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())); + let xt0_watcher = xt0_watcher.map(|_| ()); + + assert_pool_status!(header01.hash(), &pool, 0, 0); + // Alice's nonce in state is 200, tx is 150. + assert!(matches!( + xt0_watcher.unwrap_err().into_pool_error(), + Ok(TxPoolError::InvalidTransaction(InvalidTransaction::Stale)) + )); +} + +#[test] +fn fatp_watcher_invalid_single_revalidation() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + + let header01 = api.push_block(1, vec![], true); + let event = new_best_block_event(&pool, Some(api.genesis_hash()), header01.hash()); + block_on(pool.maintain(event)); + + let xt0 = uxt(Alice, 200); + let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); + + api.add_invalid(&xt0); + + let header02 = api.push_block_with_parent(header01.hash(), vec![], true); + let event = finalized_block_event(&pool, header01.hash(), header02.hash()); + block_on(pool.maintain(event)); + + // wait 10 blocks for revalidation + let mut prev_header = header02; + for n in 3..=11 { + let header = api.push_block(n, vec![], true); + let event = finalized_block_event(&pool, prev_header.hash(), header.hash()); + block_on(pool.maintain(event)); + prev_header = header; + } + + let xt0_events = futures::executor::block_on_stream(xt0_watcher).collect::>(); + log::debug!("xt0_events: {:#?}", xt0_events); + assert_eq!(xt0_events, vec![TransactionStatus::Ready, TransactionStatus::Invalid]); +} + +#[test] +fn fatp_watcher_invalid_single_revalidation2() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + + let xt0 = uxt(Alice, 200); + let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); + assert_eq!(pool.mempool_len(), (0, 1)); + api.add_invalid(&xt0); + + let header01 = api.push_block(1, vec![], true); + let event = new_best_block_event(&pool, None, header01.hash()); + block_on(pool.maintain(event)); + + let xt0_events = futures::executor::block_on_stream(xt0_watcher).collect::>(); + log::debug!("xt0_events: {:#?}", xt0_events); + assert_eq!(xt0_events, vec![TransactionStatus::Invalid]); + assert_eq!(pool.mempool_len(), (0, 0)); +} + +#[test] +fn fatp_watcher_invalid_single_revalidation3() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + + let xt0 = uxt(Alice, 150); + let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); + assert_eq!(pool.mempool_len(), (0, 1)); + + let header01 = api.push_block(1, vec![], true); + let event = finalized_block_event(&pool, api.genesis_hash(), header01.hash()); + block_on(pool.maintain(event)); + + // wait 10 blocks for revalidation + let mut prev_header = header01; + for n in 2..=11 { + let header = api.push_block(n, vec![], true); + let event = finalized_block_event(&pool, prev_header.hash(), header.hash()); + block_on(pool.maintain(event)); + prev_header = header; + } + + let xt0_events = futures::executor::block_on_stream(xt0_watcher).collect::>(); + log::debug!("xt0_events: {:#?}", xt0_events); + assert_eq!(xt0_events, vec![TransactionStatus::Invalid]); + assert_eq!(pool.mempool_len(), (0, 0)); +} + +#[test] +fn fatp_invalid_report_stale_or_future_works_as_expected() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = TestPoolBuilder::new().build(); + api.set_nonce(api.genesis_hash(), Bob.into(), 300); + api.set_nonce(api.genesis_hash(), Charlie.into(), 400); + api.set_nonce(api.genesis_hash(), Dave.into(), 500); + + let header01 = api.push_block(1, vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, None, header01.hash()))); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Bob, 300); + let xt2 = uxt(Charlie, 400); + let xt3 = uxt(Dave, 500); + + let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); + let xt1_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap(); + let xt2_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap(); + let xt3_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).unwrap(); + + assert_pool_status!(header01.hash(), &pool, 4, 0); + assert_ready_iterator!(header01.hash(), pool, [xt0, xt1, xt2, xt3]); + + // future/stale are ignored when at is None + let xt0_report = ( + pool.api().hash_and_length(&xt0).0, + Some(BlockchainError::ApplyExtrinsicFailed(ApplyExtrinsicFailed::Validity( + TransactionValidityError::Invalid(InvalidTransaction::Future), + ))), + ); + let invalid_txs = vec![xt0_report]; + let result = pool.report_invalid(None, &invalid_txs[..]); + assert!(result.is_empty()); + assert_ready_iterator!(header01.hash(), pool, [xt0, xt1, xt2, xt3]); + + // future/stale are applied when at is provided + let xt0_report = ( + pool.api().hash_and_length(&xt0).0, + Some(BlockchainError::ApplyExtrinsicFailed(ApplyExtrinsicFailed::Validity( + TransactionValidityError::Invalid(InvalidTransaction::Future), + ))), + ); + let xt1_report = ( + pool.api().hash_and_length(&xt1).0, + Some(BlockchainError::ApplyExtrinsicFailed(ApplyExtrinsicFailed::Validity( + TransactionValidityError::Invalid(InvalidTransaction::Stale), + ))), + ); + let invalid_txs = vec![xt0_report, xt1_report]; + let result = pool.report_invalid(Some(header01.hash()), &invalid_txs[..]); + // stale/future does not cause tx to be removed from the pool + assert!(result.is_empty()); + // assert_eq!(result[0].hash, pool.api().hash_and_length(&xt0).0); + assert_ready_iterator!(header01.hash(), pool, [xt2, xt3]); + + // None error means force removal + // todo + + assert_watcher_stream!(xt0_watcher, [TransactionStatus::Ready]); + assert_watcher_stream!(xt1_watcher, [TransactionStatus::Ready]); + assert_watcher_stream!(xt2_watcher, [TransactionStatus::Ready]); + assert_watcher_stream!(xt3_watcher, [TransactionStatus::Ready]); +} + +#[test] +fn fatp_invalid_report_future_dont_remove_from_pool() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = TestPoolBuilder::new().build(); + api.set_nonce(api.genesis_hash(), Bob.into(), 300); + api.set_nonce(api.genesis_hash(), Charlie.into(), 400); + api.set_nonce(api.genesis_hash(), Dave.into(), 500); + + let header01 = api.push_block(1, vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, None, header01.hash()))); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Bob, 300); + let xt2 = uxt(Charlie, 400); + let xt3 = uxt(Dave, 500); + + let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); + let xt1_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap(); + let xt2_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap(); + let xt3_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).unwrap(); + + assert_pool_status!(header01.hash(), &pool, 4, 0); + assert_ready_iterator!(header01.hash(), pool, [xt0, xt1, xt2, xt3]); + + let header02 = api.push_block_with_parent(header01.hash(), vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02.hash()))); + + let xt0_report = ( + pool.api().hash_and_length(&xt0).0, + Some(BlockchainError::ApplyExtrinsicFailed(ApplyExtrinsicFailed::Validity( + TransactionValidityError::Invalid(InvalidTransaction::Stale), + ))), + ); + let xt1_report = ( + pool.api().hash_and_length(&xt1).0, + Some(BlockchainError::ApplyExtrinsicFailed(ApplyExtrinsicFailed::Validity( + TransactionValidityError::Invalid(InvalidTransaction::Future), + ))), + ); + let invalid_txs = vec![xt0_report, xt1_report]; + let result = pool.report_invalid(Some(header01.hash()), &invalid_txs[..]); + // future does not cause tx to be removed from the pool + assert!(result.is_empty()); + assert_ready_iterator!(header01.hash(), pool, [xt2, xt3]); + + assert_pool_status!(header02.hash(), &pool, 4, 0); + assert_ready_iterator!(header02.hash(), pool, [xt0, xt1, xt2, xt3]); + + assert_watcher_stream!(xt0_watcher, [TransactionStatus::Ready]); + assert_watcher_stream!(xt1_watcher, [TransactionStatus::Ready]); + assert_watcher_stream!(xt2_watcher, [TransactionStatus::Ready]); + assert_watcher_stream!(xt3_watcher, [TransactionStatus::Ready]); +} + +#[test] +fn fatp_invalid_tx_is_removed_from_the_pool() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = TestPoolBuilder::new().build(); + api.set_nonce(api.genesis_hash(), Bob.into(), 300); + api.set_nonce(api.genesis_hash(), Charlie.into(), 400); + api.set_nonce(api.genesis_hash(), Dave.into(), 500); + + let header01 = api.push_block(1, vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, None, header01.hash()))); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Bob, 300); + let xt2 = uxt(Charlie, 400); + let xt3 = uxt(Dave, 500); + + let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); + let xt1_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap(); + let xt2_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap(); + let xt3_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).unwrap(); + + assert_pool_status!(header01.hash(), &pool, 4, 0); + assert_ready_iterator!(header01.hash(), pool, [xt0, xt1, xt2, xt3]); + + let xt0_report = ( + pool.api().hash_and_length(&xt0).0, + Some(BlockchainError::ApplyExtrinsicFailed(ApplyExtrinsicFailed::Validity( + TransactionValidityError::Invalid(InvalidTransaction::BadProof), + ))), + ); + let xt1_report = (pool.api().hash_and_length(&xt1).0, None); + let invalid_txs = vec![xt0_report, xt1_report]; + let result = pool.report_invalid(Some(header01.hash()), &invalid_txs[..]); + assert_eq!(result[0].hash, pool.api().hash_and_length(&xt0).0); + assert_pool_status!(header01.hash(), &pool, 2, 0); + assert_ready_iterator!(header01.hash(), pool, [xt2, xt3]); + + let header02 = api.push_block_with_parent(header01.hash(), vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02.hash()))); + assert_pool_status!(header02.hash(), &pool, 2, 0); + assert_ready_iterator!(header02.hash(), pool, [xt2, xt3]); + + assert_watcher_stream!(xt0_watcher, [TransactionStatus::Ready, TransactionStatus::Invalid]); + assert_watcher_stream!(xt1_watcher, [TransactionStatus::Ready, TransactionStatus::Invalid]); + assert_watcher_stream!(xt2_watcher, [TransactionStatus::Ready]); + assert_watcher_stream!(xt3_watcher, [TransactionStatus::Ready]); +} + +#[test] +fn fatp_invalid_tx_is_removed_from_the_pool2() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = TestPoolBuilder::new().build(); + api.set_nonce(api.genesis_hash(), Bob.into(), 300); + api.set_nonce(api.genesis_hash(), Charlie.into(), 400); + api.set_nonce(api.genesis_hash(), Dave.into(), 500); + + let header01 = api.push_block(1, vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, None, header01.hash()))); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Bob, 300); + let xt2 = uxt(Charlie, 400); + let xt3 = uxt(Dave, 500); + + let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); + let xt1_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap(); + let xt2_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap(); + let xt3_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).unwrap(); + + assert_pool_status!(header01.hash(), &pool, 4, 0); + assert_ready_iterator!(header01.hash(), pool, [xt0, xt1, xt2, xt3]); + + let header02a = api.push_block_with_parent(header01.hash(), vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02a.hash()))); + assert_pool_status!(header02a.hash(), &pool, 4, 0); + assert_ready_iterator!(header02a.hash(), pool, [xt0, xt1, xt2, xt3]); + + let header02b = api.push_block_with_parent(header01.hash(), vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header02a.hash()), header02b.hash()))); + + assert_pool_status!(header02b.hash(), &pool, 4, 0); + assert_ready_iterator!(header02b.hash(), pool, [xt0, xt1, xt2, xt3]); + + let xt0_report = ( + pool.api().hash_and_length(&xt0).0, + Some(BlockchainError::ApplyExtrinsicFailed(ApplyExtrinsicFailed::Validity( + TransactionValidityError::Invalid(InvalidTransaction::BadProof), + ))), + ); + let xt1_report = (pool.api().hash_and_length(&xt1).0, None); + let invalid_txs = vec![xt0_report, xt1_report]; + let result = pool.report_invalid(Some(header01.hash()), &invalid_txs[..]); + assert_eq!(result[0].hash, pool.api().hash_and_length(&xt0).0); + assert_ready_iterator!(header01.hash(), pool, [xt2, xt3]); + assert_pool_status!(header02a.hash(), &pool, 2, 0); + assert_ready_iterator!(header02a.hash(), pool, [xt2, xt3]); + assert_pool_status!(header02b.hash(), &pool, 2, 0); + assert_ready_iterator!(header02b.hash(), pool, [xt2, xt3]); + + let header03 = api.push_block_with_parent(header02b.hash(), vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header02b.hash()), header03.hash()))); + assert_pool_status!(header03.hash(), &pool, 2, 0); + assert_ready_iterator!(header03.hash(), pool, [xt2, xt3]); + + assert_watcher_stream!(xt0_watcher, [TransactionStatus::Ready, TransactionStatus::Invalid]); + assert_watcher_stream!(xt1_watcher, [TransactionStatus::Ready, TransactionStatus::Invalid]); + assert_watcher_stream!(xt2_watcher, [TransactionStatus::Ready]); + assert_watcher_stream!(xt3_watcher, [TransactionStatus::Ready]); +} diff --git a/substrate/client/transaction-pool/tests/fatp_prios.rs b/substrate/client/transaction-pool/tests/fatp_prios.rs index b61ec14e1d556..af5e7e8c5a6a8 100644 --- a/substrate/client/transaction-pool/tests/fatp_prios.rs +++ b/substrate/client/transaction-pool/tests/fatp_prios.rs @@ -27,8 +27,6 @@ use sc_transaction_pool_api::{ error::Error as TxPoolError, LocalTransactionPool, MaintainedTransactionPool, TransactionPool, TransactionStatus, }; -use sp_blockchain::{ApplyExtrinsicFailed, Error as BlockchainError}; -use sp_runtime::transaction_validity::{InvalidTransaction, TransactionValidityError}; use substrate_test_runtime_client::Sr25519Keyring::*; use substrate_test_runtime_transaction_pool::uxt; #[test] @@ -560,236 +558,3 @@ fn fatp_prios_submit_local_full_mempool_higher_prio_is_accepted() { assert_ready_iterator!(header02.hash(), pool, [xt3, xt2]); assert_ready_iterator!(header03.hash(), pool, [xt5, xt4]); } - -#[test] -fn fatp_invalid_report_stale_or_future_works_as_expected() { - sp_tracing::try_init_simple(); - - let (pool, api, _) = TestPoolBuilder::new().build(); - api.set_nonce(api.genesis_hash(), Bob.into(), 300); - api.set_nonce(api.genesis_hash(), Charlie.into(), 400); - api.set_nonce(api.genesis_hash(), Dave.into(), 500); - - let header01 = api.push_block(1, vec![], true); - block_on(pool.maintain(new_best_block_event(&pool, None, header01.hash()))); - - let xt0 = uxt(Alice, 200); - let xt1 = uxt(Bob, 300); - let xt2 = uxt(Charlie, 400); - let xt3 = uxt(Dave, 500); - - let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); - let xt1_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap(); - let xt2_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap(); - let xt3_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).unwrap(); - - assert_pool_status!(header01.hash(), &pool, 4, 0); - assert_ready_iterator!(header01.hash(), pool, [xt0, xt1, xt2, xt3]); - - // future/stale are ignored when at is None - let xt0_report = ( - pool.api().hash_and_length(&xt0).0, - Some(BlockchainError::ApplyExtrinsicFailed(ApplyExtrinsicFailed::Validity( - TransactionValidityError::Invalid(InvalidTransaction::Future), - ))), - ); - let invalid_txs = vec![xt0_report]; - let result = pool.report_invalid(None, &invalid_txs[..]); - assert!(result.is_empty()); - assert_ready_iterator!(header01.hash(), pool, [xt0, xt1, xt2, xt3]); - - // future/stale are applied when at is provided - let xt0_report = ( - pool.api().hash_and_length(&xt0).0, - Some(BlockchainError::ApplyExtrinsicFailed(ApplyExtrinsicFailed::Validity( - TransactionValidityError::Invalid(InvalidTransaction::Future), - ))), - ); - let xt1_report = ( - pool.api().hash_and_length(&xt1).0, - Some(BlockchainError::ApplyExtrinsicFailed(ApplyExtrinsicFailed::Validity( - TransactionValidityError::Invalid(InvalidTransaction::Stale), - ))), - ); - let invalid_txs = vec![xt0_report, xt1_report]; - let result = pool.report_invalid(Some(header01.hash()), &invalid_txs[..]); - // stale/future does not cause tx to be removed from the pool - assert!(result.is_empty()); - // assert_eq!(result[0].hash, pool.api().hash_and_length(&xt0).0); - assert_ready_iterator!(header01.hash(), pool, [xt2, xt3]); - - // None error means force removal - // todo - - assert_watcher_stream!(xt0_watcher, [TransactionStatus::Ready]); - assert_watcher_stream!(xt1_watcher, [TransactionStatus::Ready]); - assert_watcher_stream!(xt2_watcher, [TransactionStatus::Ready]); - assert_watcher_stream!(xt3_watcher, [TransactionStatus::Ready]); -} - -#[test] -fn fatp_invalid_report_future_dont_remove_from_pool() { - sp_tracing::try_init_simple(); - - let (pool, api, _) = TestPoolBuilder::new().build(); - api.set_nonce(api.genesis_hash(), Bob.into(), 300); - api.set_nonce(api.genesis_hash(), Charlie.into(), 400); - api.set_nonce(api.genesis_hash(), Dave.into(), 500); - - let header01 = api.push_block(1, vec![], true); - block_on(pool.maintain(new_best_block_event(&pool, None, header01.hash()))); - - let xt0 = uxt(Alice, 200); - let xt1 = uxt(Bob, 300); - let xt2 = uxt(Charlie, 400); - let xt3 = uxt(Dave, 500); - - let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); - let xt1_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap(); - let xt2_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap(); - let xt3_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).unwrap(); - - assert_pool_status!(header01.hash(), &pool, 4, 0); - assert_ready_iterator!(header01.hash(), pool, [xt0, xt1, xt2, xt3]); - - let header02 = api.push_block_with_parent(header01.hash(), vec![], true); - block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02.hash()))); - - let xt0_report = ( - pool.api().hash_and_length(&xt0).0, - Some(BlockchainError::ApplyExtrinsicFailed(ApplyExtrinsicFailed::Validity( - TransactionValidityError::Invalid(InvalidTransaction::Stale), - ))), - ); - let xt1_report = ( - pool.api().hash_and_length(&xt1).0, - Some(BlockchainError::ApplyExtrinsicFailed(ApplyExtrinsicFailed::Validity( - TransactionValidityError::Invalid(InvalidTransaction::Future), - ))), - ); - let invalid_txs = vec![xt0_report, xt1_report]; - let result = pool.report_invalid(Some(header01.hash()), &invalid_txs[..]); - // future does not cause tx to be removed from the pool - assert!(result.is_empty()); - assert_ready_iterator!(header01.hash(), pool, [xt2, xt3]); - - assert_pool_status!(header02.hash(), &pool, 4, 0); - assert_ready_iterator!(header02.hash(), pool, [xt0, xt1, xt2, xt3]); - - assert_watcher_stream!(xt0_watcher, [TransactionStatus::Ready]); - assert_watcher_stream!(xt1_watcher, [TransactionStatus::Ready]); - assert_watcher_stream!(xt2_watcher, [TransactionStatus::Ready]); - assert_watcher_stream!(xt3_watcher, [TransactionStatus::Ready]); -} - -#[test] -fn fatp_invalid_tx_is_removed_from_the_pool() { - sp_tracing::try_init_simple(); - - let (pool, api, _) = TestPoolBuilder::new().build(); - api.set_nonce(api.genesis_hash(), Bob.into(), 300); - api.set_nonce(api.genesis_hash(), Charlie.into(), 400); - api.set_nonce(api.genesis_hash(), Dave.into(), 500); - - let header01 = api.push_block(1, vec![], true); - block_on(pool.maintain(new_best_block_event(&pool, None, header01.hash()))); - - let xt0 = uxt(Alice, 200); - let xt1 = uxt(Bob, 300); - let xt2 = uxt(Charlie, 400); - let xt3 = uxt(Dave, 500); - - let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); - let xt1_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap(); - let xt2_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap(); - let xt3_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).unwrap(); - - assert_pool_status!(header01.hash(), &pool, 4, 0); - assert_ready_iterator!(header01.hash(), pool, [xt0, xt1, xt2, xt3]); - - let xt0_report = ( - pool.api().hash_and_length(&xt0).0, - Some(BlockchainError::ApplyExtrinsicFailed(ApplyExtrinsicFailed::Validity( - TransactionValidityError::Invalid(InvalidTransaction::BadProof), - ))), - ); - let xt1_report = (pool.api().hash_and_length(&xt1).0, None); - let invalid_txs = vec![xt0_report, xt1_report]; - let result = pool.report_invalid(Some(header01.hash()), &invalid_txs[..]); - assert_eq!(result[0].hash, pool.api().hash_and_length(&xt0).0); - assert_pool_status!(header01.hash(), &pool, 2, 0); - assert_ready_iterator!(header01.hash(), pool, [xt2, xt3]); - - let header02 = api.push_block_with_parent(header01.hash(), vec![], true); - block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02.hash()))); - assert_pool_status!(header02.hash(), &pool, 2, 0); - assert_ready_iterator!(header02.hash(), pool, [xt2, xt3]); - - assert_watcher_stream!(xt0_watcher, [TransactionStatus::Ready, TransactionStatus::Invalid]); - assert_watcher_stream!(xt1_watcher, [TransactionStatus::Ready, TransactionStatus::Invalid]); - assert_watcher_stream!(xt2_watcher, [TransactionStatus::Ready]); - assert_watcher_stream!(xt3_watcher, [TransactionStatus::Ready]); -} - -#[test] -fn fatp_invalid_tx_is_removed_from_the_pool2() { - sp_tracing::try_init_simple(); - - let (pool, api, _) = TestPoolBuilder::new().build(); - api.set_nonce(api.genesis_hash(), Bob.into(), 300); - api.set_nonce(api.genesis_hash(), Charlie.into(), 400); - api.set_nonce(api.genesis_hash(), Dave.into(), 500); - - let header01 = api.push_block(1, vec![], true); - block_on(pool.maintain(new_best_block_event(&pool, None, header01.hash()))); - - let xt0 = uxt(Alice, 200); - let xt1 = uxt(Bob, 300); - let xt2 = uxt(Charlie, 400); - let xt3 = uxt(Dave, 500); - - let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); - let xt1_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap(); - let xt2_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap(); - let xt3_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).unwrap(); - - assert_pool_status!(header01.hash(), &pool, 4, 0); - assert_ready_iterator!(header01.hash(), pool, [xt0, xt1, xt2, xt3]); - - let header02a = api.push_block_with_parent(header01.hash(), vec![], true); - block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02a.hash()))); - assert_pool_status!(header02a.hash(), &pool, 4, 0); - assert_ready_iterator!(header02a.hash(), pool, [xt0, xt1, xt2, xt3]); - - let header02b = api.push_block_with_parent(header01.hash(), vec![], true); - block_on(pool.maintain(new_best_block_event(&pool, Some(header02a.hash()), header02b.hash()))); - - assert_pool_status!(header02b.hash(), &pool, 4, 0); - assert_ready_iterator!(header02b.hash(), pool, [xt0, xt1, xt2, xt3]); - - let xt0_report = ( - pool.api().hash_and_length(&xt0).0, - Some(BlockchainError::ApplyExtrinsicFailed(ApplyExtrinsicFailed::Validity( - TransactionValidityError::Invalid(InvalidTransaction::BadProof), - ))), - ); - let xt1_report = (pool.api().hash_and_length(&xt1).0, None); - let invalid_txs = vec![xt0_report, xt1_report]; - let result = pool.report_invalid(Some(header01.hash()), &invalid_txs[..]); - assert_eq!(result[0].hash, pool.api().hash_and_length(&xt0).0); - assert_ready_iterator!(header01.hash(), pool, [xt2, xt3]); - assert_pool_status!(header02a.hash(), &pool, 2, 0); - assert_ready_iterator!(header02a.hash(), pool, [xt2, xt3]); - assert_pool_status!(header02b.hash(), &pool, 2, 0); - assert_ready_iterator!(header02b.hash(), pool, [xt2, xt3]); - - let header03 = api.push_block_with_parent(header02b.hash(), vec![], true); - block_on(pool.maintain(new_best_block_event(&pool, Some(header02b.hash()), header03.hash()))); - assert_pool_status!(header03.hash(), &pool, 2, 0); - assert_ready_iterator!(header03.hash(), pool, [xt2, xt3]); - - assert_watcher_stream!(xt0_watcher, [TransactionStatus::Ready, TransactionStatus::Invalid]); - assert_watcher_stream!(xt1_watcher, [TransactionStatus::Ready, TransactionStatus::Invalid]); - assert_watcher_stream!(xt2_watcher, [TransactionStatus::Ready]); - assert_watcher_stream!(xt3_watcher, [TransactionStatus::Ready]); -} From af464fa7df7ecc66edd49b2eb4b074ddd9296975 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Fri, 17 Jan 2025 16:13:51 +0100 Subject: [PATCH 49/60] more tests, fixes, docs updated --- .../client/transaction-pool/api/src/lib.rs | 3 +- .../fork_aware_txpool/fork_aware_txpool.rs | 6 + .../src/fork_aware_txpool/view_store.rs | 31 +-- .../transaction-pool/tests/fatp_invalid.rs | 216 +++++++++++------- 4 files changed, 158 insertions(+), 98 deletions(-) diff --git a/substrate/client/transaction-pool/api/src/lib.rs b/substrate/client/transaction-pool/api/src/lib.rs index 725f4646791da..7a6dc7143d67f 100644 --- a/substrate/client/transaction-pool/api/src/lib.rs +++ b/substrate/client/transaction-pool/api/src/lib.rs @@ -297,7 +297,8 @@ pub trait TransactionPool: Send + Sync { /// block. /// /// The transaction pool implementation decides which transactions to remove. Transactions - /// dependent on invalid ones will also be removed. + /// removed from the pool will be notified with `TransactionStatus::Invalid` event (if + /// `submit_and_watch` was used for submission). /// /// If the tuple's error is None, the transaction will be forcibly removed from the pool. /// 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 1cb4b5d747d20..d401492ff7f53 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 @@ -797,11 +797,17 @@ where at: Option<::Hash>, invalid_tx_errors: &[(TxHash, Option)], ) -> Vec> { + log::debug!(target: LOG_TARGET, "fatp::report_invalid {}", invalid_tx_errors.len()); + log_xt_trace!(data: tuple, target:LOG_TARGET, invalid_tx_errors, "[{:?}] fatp::report_invalid {:?}"); self.metrics .report(|metrics| metrics.reported_invalid_txs.inc_by(invalid_tx_errors.len() as _)); let removed = self.view_store.report_invalid(at, invalid_tx_errors); + removed.iter().for_each(|tx| { + self.mempool.remove_transaction(&tx.hash); + }); + self.metrics .report(|metrics| metrics.removed_invalid_txs.inc_by(removed.len() as _)); 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 8b8cc58e1fd73..e8e07ff68d3df 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 @@ -660,25 +660,26 @@ where /// optional error encountered during the transaction execution at a specific (also optional) /// block. /// - /// Removal operation applies to provided transactions and their descendants. + /// Removal operation applies to provided transactions. Their descendants can be removed from + /// the view, but will not be invalidated or banned. /// /// Invalid future and stale transaction will be removed only from given `at` view, and will be - /// kept in the transaction pool. Such transaction will not be reported in returned vector. They + /// 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. /// - /// For other errors, the transaction will be removed from the pool, and it will be included in - /// the returned vector. Additionally, transactions - /// provided as input will be banned from re-entering the pool. + /// 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 + /// from re-entering the pool. /// - /// If the tuple's error is None, the transaction will be forcibly removed from the pool, + /// If the tuple's error is None, the transaction will be forcibly removed from the view_store, /// banned and included into the returned vector. /// - /// For every transaction removed from the pool (including descendants) an Invalid event is - /// triggered. + /// For every transaction removed from the view_store (excluding descendants) an Invalid event + /// is triggered. /// - /// Returns the list of actually removed transactions, which may include transactions dependent - /// on the provided set. + /// Returns the list of actually removed transactions from the mempool, which were included in + /// the provided input list. pub(crate) fn report_invalid( &self, at: Option, @@ -708,13 +709,17 @@ where }); let mut removed = vec![]; - for tx in &remove_from_pool { + for tx_hash in &remove_from_pool { let removed_from_pool = - self.remove_transaction_subtree(*tx, |listener, removed_tx_hash| { + self.remove_transaction_subtree(*tx_hash, |listener, removed_tx_hash| { listener.invalid(&removed_tx_hash); }); - removed.extend(removed_from_pool); + removed_from_pool + .iter() + .find(|tx| tx.hash == *tx_hash) + .map(|tx| removed.push(tx.clone())); } + self.listener.invalidate_transactions(&remove_from_pool); removed diff --git a/substrate/client/transaction-pool/tests/fatp_invalid.rs b/substrate/client/transaction-pool/tests/fatp_invalid.rs index dec9c4d6032da..0d2e737f855f1 100644 --- a/substrate/client/transaction-pool/tests/fatp_invalid.rs +++ b/substrate/client/transaction-pool/tests/fatp_invalid.rs @@ -36,52 +36,74 @@ use substrate_test_runtime_client::Sr25519Keyring::*; use substrate_test_runtime_transaction_pool::uxt; #[test] -fn fatp_invalid_devel_ideas() { +fn fatp_invalid_three_views_stale_gets_rejected() { sp_tracing::try_init_simple(); let (pool, api, _) = pool(); - let xt0 = uxt(Alice, 206); - let xt1 = uxt(Alice, 206); + let header01 = api.push_block(1, vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, None, header01.hash()))); - let result = block_on(pool.submit_one(invalid_hash(), SOURCE, xt1.clone())); - assert!(result.is_ok()); + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Alice, 200); - let header01a = api.push_block(1, vec![xt0.clone()], true); - let header01b = api.push_block(1, vec![xt0.clone()], true); + let header02a = api.push_block_with_parent(header01.hash(), vec![], true); + let header02b = api.push_block_with_parent(header01.hash(), vec![], true); + let header02c = api.push_block_with_parent(header01.hash(), vec![], true); + api.set_nonce(header02a.hash(), Alice.into(), 201); + api.set_nonce(header02b.hash(), Alice.into(), 201); + api.set_nonce(header02c.hash(), Alice.into(), 201); - api.set_nonce(header01a.hash(), Alice.into(), 201); - api.set_nonce(header01b.hash(), Alice.into(), 202); + block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02a.hash()))); + block_on(pool.maintain(new_best_block_event(&pool, Some(header02a.hash()), header02b.hash()))); + block_on(pool.maintain(new_best_block_event(&pool, Some(header02b.hash()), header02c.hash()))); - let event = new_best_block_event(&pool, None, header01a.hash()); - block_on(pool.maintain(event)); + let result0 = block_on(pool.submit_one(invalid_hash(), SOURCE, xt0.clone())); + let result1 = block_on(pool.submit_one(invalid_hash(), SOURCE, xt1.clone())); - let event = new_best_block_event(&pool, None, header01b.hash()); - block_on(pool.maintain(event)); + assert!(matches!( + result0.as_ref().unwrap_err().0, + TxPoolError::InvalidTransaction(InvalidTransaction::Stale) + )); + assert!(matches!( + result1.as_ref().unwrap_err().0, + TxPoolError::InvalidTransaction(InvalidTransaction::Stale) + )); +} + +#[test] +fn fatp_invalid_three_views_invalid_gets_rejected() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + + let header01 = api.push_block(1, vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, None, header01.hash()))); - let mut xts = (199..204).map(|i| uxt(Alice, i)).collect::>(); - xts.push(xt0); - xts.push(xt1); - - let results = block_on(pool.submit_at(invalid_hash(), SOURCE, xts.clone())).unwrap(); - - log::debug!(target:LOG_TARGET, "res: {:#?}", results); - log::debug!(target:LOG_TARGET, "stats: {:#?}", pool.status_all()); - - (0..2).for_each(|i| { - assert!(matches!( - results[i].as_ref().unwrap_err().0, - TxPoolError::InvalidTransaction(InvalidTransaction::Stale,) - )); - }); - //note: tx at 2 is valid at header01a and invalid at header01b - (2..5).for_each(|i| { - assert_eq!(*results[i].as_ref().unwrap(), api.hash_and_length(&xts[i]).0); - }); - //xt0 at index 5 (transaction from the imported block, gets banned when pruned) - assert!(matches!(results[5].as_ref().unwrap_err().0, TxPoolError::TemporarilyBanned)); - //xt1 at index 6 - assert!(matches!(results[6].as_ref().unwrap_err().0, TxPoolError::AlreadyImported(_))); + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Alice, 200); + let header02a = api.push_block_with_parent(header01.hash(), vec![], true); + let header02b = api.push_block_with_parent(header01.hash(), vec![], true); + let header02c = api.push_block_with_parent(header01.hash(), vec![], true); + + block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02a.hash()))); + block_on(pool.maintain(new_best_block_event(&pool, Some(header02a.hash()), header02b.hash()))); + block_on(pool.maintain(new_best_block_event(&pool, Some(header02b.hash()), header02c.hash()))); + + api.add_invalid(&xt0); + api.add_invalid(&xt1); + + let result0 = block_on(pool.submit_one(invalid_hash(), SOURCE, xt0.clone())); + let result1 = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).map(|_| ()); + + assert!(matches!( + result0.as_ref().unwrap_err().0, + TxPoolError::InvalidTransaction(InvalidTransaction::Custom(_)) + )); + assert!(matches!( + result1.as_ref().unwrap_err().0, + TxPoolError::InvalidTransaction(InvalidTransaction::Custom(_)) + )); } #[test] @@ -99,11 +121,11 @@ fn fatp_transactions_purging_invalid_on_finalization_works() { let watcher1 = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap(); let watcher2 = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap(); - block_on(pool.submit_one(invalid_hash(), SOURCE, xt3.clone())).unwrap(); + let watcher3 = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).unwrap(); assert_eq!(api.validation_requests().len(), 3); assert_eq!(pool.status_all()[&header01.hash()].ready, 3); - assert_eq!(pool.mempool_len(), (1, 2)); + assert_eq!(pool.mempool_len(), (0, 3)); let header02 = api.push_block(2, vec![], true); api.add_invalid(&xt1); @@ -112,27 +134,23 @@ fn fatp_transactions_purging_invalid_on_finalization_works() { block_on(pool.maintain(finalized_block_event(&pool, header01.hash(), header02.hash()))); // wait 10 blocks for revalidation - let mut prev_header = header02; - for n in 3..=13 { + let mut prev_header = header02.clone(); + for n in 3..=11 { let header = api.push_block(n, vec![], true); let event = finalized_block_event(&pool, prev_header.hash(), header.hash()); block_on(pool.maintain(event)); prev_header = header; } - //todo: should it work at all? (it requires better revalidation: mempool keeping validated txs) - //additionally it also requires revalidation of finalized view. - // assert_eq!(pool.status_all()[&header02.hash()].ready, 0); assert_eq!(pool.mempool_len(), (0, 0)); - let xt1_events = futures::executor::block_on_stream(watcher1).collect::>(); - let xt2_events = futures::executor::block_on_stream(watcher2).collect::>(); - assert_eq!(xt1_events, vec![TransactionStatus::Ready, TransactionStatus::Invalid]); - assert_eq!(xt2_events, vec![TransactionStatus::Ready, TransactionStatus::Invalid]); + assert_watcher_stream!(watcher1, [TransactionStatus::Ready, TransactionStatus::Invalid]); + assert_watcher_stream!(watcher2, [TransactionStatus::Ready, TransactionStatus::Invalid]); + assert_watcher_stream!(watcher3, [TransactionStatus::Ready, TransactionStatus::Invalid]); } #[test] -fn should_revalidate_during_maintenance() { +fn fatp_transactions_purging_invalid_on_finalization_works2() { sp_tracing::try_init_simple(); let (pool, api, _) = pool(); @@ -141,8 +159,8 @@ fn should_revalidate_during_maintenance() { let header01 = api.push_block(1, vec![], true); block_on(pool.maintain(new_best_block_event(&pool, None, header01.hash()))); - block_on(pool.submit_one(invalid_hash(), SOURCE, xt1.clone())).unwrap(); + let watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap(); assert_eq!(pool.status_all()[&header01.hash()].ready, 2); assert_eq!(api.validation_requests().len(), 2); @@ -151,7 +169,6 @@ fn should_revalidate_during_maintenance() { api.add_invalid(&xt2); block_on(pool.maintain(finalized_block_event(&pool, api.genesis_hash(), header02.hash()))); - //todo: shall revalidation check finalized (fork's tip) view? assert_eq!(pool.status_all()[&header02.hash()].ready, 1); // wait 10 blocks for revalidation @@ -163,10 +180,8 @@ fn should_revalidate_during_maintenance() { prev_header = header; } - assert_eq!( - futures::executor::block_on_stream(watcher).collect::>(), - vec![TransactionStatus::Ready, TransactionStatus::Invalid], - ); + assert_watcher_stream!(watcher, [TransactionStatus::Ready, TransactionStatus::Invalid]); + assert_eq!(pool.status_all()[&prev_header.hash()].ready, 0); } #[test] @@ -198,13 +213,13 @@ fn should_not_retain_invalid_hashes_from_retracted() { prev_header = header; } - assert_eq!( - futures::executor::block_on_stream(watcher).collect::>(), - vec![ + assert_watcher_stream!( + watcher, + [ TransactionStatus::Ready, TransactionStatus::InBlock((header02a.hash(), 0)), TransactionStatus::Invalid - ], + ] ); //todo: shall revalidation check finalized (fork's tip) view? @@ -265,44 +280,32 @@ fn fatp_watcher_invalid_many_revalidation() { prev_header = header; } - let xt0_events = futures::executor::block_on_stream(xt0_watcher).collect::>(); - let xt1_events = futures::executor::block_on_stream(xt1_watcher).collect::>(); - let xt2_events = futures::executor::block_on_stream(xt2_watcher).collect::>(); - let xt3_events = futures::executor::block_on_stream(xt3_watcher).collect::>(); - let xt4_events = futures::executor::block_on_stream(xt4_watcher).collect::>(); - - log::debug!("xt0_events: {:#?}", xt0_events); - log::debug!("xt1_events: {:#?}", xt1_events); - log::debug!("xt2_events: {:#?}", xt2_events); - log::debug!("xt3_events: {:#?}", xt3_events); - log::debug!("xt4_events: {:#?}", xt4_events); - - assert_eq!( - xt0_events, - vec![ + assert_watcher_stream!( + xt0_watcher, + [ TransactionStatus::Ready, TransactionStatus::InBlock((header03.hash(), 0)), TransactionStatus::Finalized((header03.hash(), 0)) - ], + ] ); - assert_eq!( - xt1_events, - vec![ + assert_watcher_stream!( + xt1_watcher, + [ TransactionStatus::Ready, TransactionStatus::InBlock((header03.hash(), 1)), TransactionStatus::Finalized((header03.hash(), 1)) - ], + ] ); - assert_eq!( - xt2_events, - vec![ + assert_watcher_stream!( + xt2_watcher, + [ TransactionStatus::Ready, TransactionStatus::InBlock((header03.hash(), 2)), TransactionStatus::Finalized((header03.hash(), 2)) - ], + ] ); - assert_eq!(xt3_events, vec![TransactionStatus::Ready, TransactionStatus::Invalid],); - assert_eq!(xt4_events, vec![TransactionStatus::Ready, TransactionStatus::Invalid],); + assert_watcher_stream!(xt3_watcher, [TransactionStatus::Ready, TransactionStatus::Invalid]); + assert_watcher_stream!(xt4_watcher, [TransactionStatus::Ready, TransactionStatus::Invalid]); } #[test] @@ -581,6 +584,51 @@ fn fatp_invalid_tx_is_removed_from_the_pool() { assert_watcher_stream!(xt3_watcher, [TransactionStatus::Ready]); } +#[test] +fn fatp_invalid_tx_is_removed_from_the_pool_future_subtree_stays() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = TestPoolBuilder::new().build(); + + let header01 = api.push_block(1, vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, None, header01.hash()))); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Alice, 201); + let xt2 = uxt(Alice, 202); + let xt3 = uxt(Alice, 203); + + let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); + let xt1_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap(); + let xt2_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap(); + let xt3_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).unwrap(); + + assert_pool_status!(header01.hash(), &pool, 4, 0); + assert_ready_iterator!(header01.hash(), pool, [xt0, xt1, xt2, xt3]); + + let xt0_report = ( + pool.api().hash_and_length(&xt0).0, + Some(BlockchainError::ApplyExtrinsicFailed(ApplyExtrinsicFailed::Validity( + TransactionValidityError::Invalid(InvalidTransaction::BadProof), + ))), + ); + let invalid_txs = vec![xt0_report]; + let result = pool.report_invalid(Some(header01.hash()), &invalid_txs); + assert_eq!(result[0].hash, pool.api().hash_and_length(&xt0).0); + assert_pool_status!(header01.hash(), &pool, 0, 0); + assert_ready_iterator!(header01.hash(), pool, []); + + let header02 = api.push_block_with_parent(header01.hash(), vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02.hash()))); + assert_pool_status!(header02.hash(), &pool, 0, 3); + assert_future_iterator!(header02.hash(), pool, [xt1, xt2, xt3]); + + assert_watcher_stream!(xt0_watcher, [TransactionStatus::Ready, TransactionStatus::Invalid]); + assert_watcher_stream!(xt1_watcher, [TransactionStatus::Ready]); + assert_watcher_stream!(xt2_watcher, [TransactionStatus::Ready]); + assert_watcher_stream!(xt3_watcher, [TransactionStatus::Ready]); +} + #[test] fn fatp_invalid_tx_is_removed_from_the_pool2() { sp_tracing::try_init_simple(); From 562f30d3152137ead93da29e8ac408cd937ca7bd Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Mon, 3 Feb 2025 10:22:23 +0100 Subject: [PATCH 50/60] fix --- .../transaction-pool/src/fork_aware_txpool/fork_aware_txpool.rs | 1 - 1 file changed, 1 deletion(-) 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 ab32788dc00f6..cbd0f16f1e2d9 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 @@ -858,7 +858,6 @@ where self.mempool.remove_transaction(&tx.hash); }); - //todo: add metrics reported / removed self.metrics .report(|metrics| metrics.removed_invalid_txs.inc_by(removed.len() as _)); From 8d63728d08f7cc114200e34100dff1e6cd588c81 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Mon, 3 Feb 2025 17:14:19 +0100 Subject: [PATCH 51/60] report_invalid: sp_blockchain::Error -> TransactionValidityError --- .../basic-authorship/src/basic_authorship.rs | 8 ++++- .../client/transaction-pool/api/src/lib.rs | 3 +- .../fork_aware_txpool/fork_aware_txpool.rs | 2 +- .../src/fork_aware_txpool/view_store.rs | 12 +++---- .../single_state_txpool.rs | 11 +++---- .../src/transaction_pool_wrapper.rs | 4 +-- .../transaction-pool/tests/fatp_invalid.rs | 33 +++++-------------- 7 files changed, 30 insertions(+), 43 deletions(-) diff --git a/substrate/client/basic-authorship/src/basic_authorship.rs b/substrate/client/basic-authorship/src/basic_authorship.rs index f844c7f605476..b744c19be9bbd 100644 --- a/substrate/client/basic-authorship/src/basic_authorship.rs +++ b/substrate/client/basic-authorship/src/basic_authorship.rs @@ -512,7 +512,13 @@ where target: LOG_TARGET, "[{:?}] Invalid transaction: {} at: {}", pending_tx_hash, e, self.parent_hash ); - unqueue_invalid.push((pending_tx_hash, Some(e))); + + let error_to_report = match e { + ApplyExtrinsicFailed(Validity(e)) => Some(e), + _ => None, + }; + + unqueue_invalid.push((pending_tx_hash, error_to_report)); }, } }; diff --git a/substrate/client/transaction-pool/api/src/lib.rs b/substrate/client/transaction-pool/api/src/lib.rs index 7a6dc7143d67f..9ee22f77f3885 100644 --- a/substrate/client/transaction-pool/api/src/lib.rs +++ b/substrate/client/transaction-pool/api/src/lib.rs @@ -33,6 +33,7 @@ const LOG_TARGET: &str = "txpool::api"; pub use sp_runtime::transaction_validity::{ TransactionLongevity, TransactionPriority, TransactionSource, TransactionTag, + TransactionValidityError, }; /// Transaction pool status. @@ -309,7 +310,7 @@ pub trait TransactionPool: Send + Sync { fn report_invalid( &self, at: Option<::Hash>, - invalid_tx_errors: &[(TxHash, Option)], + invalid_tx_errors: &[(TxHash, Option)], ) -> Vec>; // *** logging 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 cbd0f16f1e2d9..43ef76253a393 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 @@ -845,7 +845,7 @@ where fn report_invalid( &self, at: Option<::Hash>, - invalid_tx_errors: &[(TxHash, Option)], + invalid_tx_errors: &[(TxHash, Option)], ) -> Vec> { debug!(target: LOG_TARGET, len = ?invalid_tx_errors.len(), "fatp::report_invalid"); log_xt_trace!(data: tuple, target:LOG_TARGET, invalid_tx_errors, "fatp::report_invalid {:?}"); 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 602f832f27edd..ac2096cfb2d77 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 @@ -35,7 +35,7 @@ use futures::prelude::*; use itertools::Itertools; use parking_lot::RwLock; use sc_transaction_pool_api::{error::Error as PoolError, PoolStatus}; -use sp_blockchain::{ApplyExtrinsicFailed, Error as BlockchainError, TreeRoute}; +use sp_blockchain::TreeRoute; use sp_runtime::{ generic::BlockId, traits::Block as BlockT, @@ -721,17 +721,15 @@ where pub(crate) fn report_invalid( &self, at: Option, - invalid_tx_errors: &[(ExtrinsicHash, Option)], + invalid_tx_errors: &[(ExtrinsicHash, Option)], ) -> Vec> { let mut remove_from_view = vec![]; let mut remove_from_pool = vec![]; invalid_tx_errors.iter().for_each(|(hash, e)| match e { - Some(BlockchainError::ApplyExtrinsicFailed(ApplyExtrinsicFailed::Validity( - TransactionValidityError::Invalid( - InvalidTransaction::Future | InvalidTransaction::Stale, - ), - ))) => { + Some(TransactionValidityError::Invalid( + InvalidTransaction::Future | InvalidTransaction::Stale, + )) => { remove_from_view.push(*hash); }, _ => { diff --git a/substrate/client/transaction-pool/src/single_state_txpool/single_state_txpool.rs b/substrate/client/transaction-pool/src/single_state_txpool/single_state_txpool.rs index cd86919f91119..8fcf48e21bb29 100644 --- a/substrate/client/transaction-pool/src/single_state_txpool/single_state_txpool.rs +++ b/substrate/client/transaction-pool/src/single_state_txpool/single_state_txpool.rs @@ -45,7 +45,10 @@ use sp_blockchain::{HashAndNumber, TreeRoute}; use sp_core::traits::SpawnEssentialNamed; use sp_runtime::{ generic::BlockId, - traits::{AtLeast32Bit, Block as BlockT, Header as HeaderT, NumberFor, Zero}, + traits::{ + AtLeast32Bit, Block as BlockT, Header as HeaderT, NumberFor, SaturatedConversion, Zero, + }, + transaction_validity::TransactionValidityError, }; use std::{ collections::{HashMap, HashSet}, @@ -326,7 +329,7 @@ where fn report_invalid( &self, _at: Option<::Hash>, - invalid_tx_errors: &[(TxHash, Option)], + invalid_tx_errors: &[(TxHash, Option)], ) -> Vec> { let hashes = invalid_tx_errors.iter().map(|(hash, _)| *hash).collect::>(); let removed = self.pool.validated_pool().remove_invalid(&hashes); @@ -464,10 +467,6 @@ where at: Block::Hash, xt: sc_transaction_pool_api::LocalTransactionFor, ) -> Result { - use sp_runtime::{ - traits::SaturatedConversion, transaction_validity::TransactionValidityError, - }; - let validity = self .api .validate_transaction_blocking(at, TransactionSource::Local, Arc::from(xt.clone()))? diff --git a/substrate/client/transaction-pool/src/transaction_pool_wrapper.rs b/substrate/client/transaction-pool/src/transaction_pool_wrapper.rs index b10094f7a8b9c..5f97cbdb7bdd4 100644 --- a/substrate/client/transaction-pool/src/transaction_pool_wrapper.rs +++ b/substrate/client/transaction-pool/src/transaction_pool_wrapper.rs @@ -28,7 +28,7 @@ use async_trait::async_trait; use sc_transaction_pool_api::{ ChainEvent, ImportNotificationStream, LocalTransactionFor, LocalTransactionPool, MaintainedTransactionPool, PoolStatus, ReadyTransactions, TransactionFor, TransactionPool, - TransactionSource, TransactionStatusStreamFor, TxHash, + TransactionSource, TransactionStatusStreamFor, TransactionValidityError, TxHash, }; use sp_runtime::traits::Block as BlockT; use std::{collections::HashMap, pin::Pin, sync::Arc}; @@ -110,7 +110,7 @@ where fn report_invalid( &self, at: Option<::Hash>, - invalid_tx_errors: &[(TxHash, Option)], + invalid_tx_errors: &[(TxHash, Option)], ) -> Vec> { self.0.report_invalid(at, invalid_tx_errors) } diff --git a/substrate/client/transaction-pool/tests/fatp_invalid.rs b/substrate/client/transaction-pool/tests/fatp_invalid.rs index 0d2e737f855f1..339edb8fc55a1 100644 --- a/substrate/client/transaction-pool/tests/fatp_invalid.rs +++ b/substrate/client/transaction-pool/tests/fatp_invalid.rs @@ -30,7 +30,6 @@ use sc_transaction_pool_api::{ error::{Error as TxPoolError, IntoPoolError}, MaintainedTransactionPool, TransactionPool, TransactionStatus, }; -use sp_blockchain::{ApplyExtrinsicFailed, Error as BlockchainError}; use sp_runtime::transaction_validity::{InvalidTransaction, TransactionValidityError}; use substrate_test_runtime_client::Sr25519Keyring::*; use substrate_test_runtime_transaction_pool::uxt; @@ -442,9 +441,7 @@ fn fatp_invalid_report_stale_or_future_works_as_expected() { // future/stale are ignored when at is None let xt0_report = ( pool.api().hash_and_length(&xt0).0, - Some(BlockchainError::ApplyExtrinsicFailed(ApplyExtrinsicFailed::Validity( - TransactionValidityError::Invalid(InvalidTransaction::Future), - ))), + Some(TransactionValidityError::Invalid(InvalidTransaction::Future)), ); let invalid_txs = vec![xt0_report]; let result = pool.report_invalid(None, &invalid_txs[..]); @@ -454,15 +451,11 @@ fn fatp_invalid_report_stale_or_future_works_as_expected() { // future/stale are applied when at is provided let xt0_report = ( pool.api().hash_and_length(&xt0).0, - Some(BlockchainError::ApplyExtrinsicFailed(ApplyExtrinsicFailed::Validity( - TransactionValidityError::Invalid(InvalidTransaction::Future), - ))), + Some(TransactionValidityError::Invalid(InvalidTransaction::Future)), ); let xt1_report = ( pool.api().hash_and_length(&xt1).0, - Some(BlockchainError::ApplyExtrinsicFailed(ApplyExtrinsicFailed::Validity( - TransactionValidityError::Invalid(InvalidTransaction::Stale), - ))), + Some(TransactionValidityError::Invalid(InvalidTransaction::Stale)), ); let invalid_txs = vec![xt0_report, xt1_report]; let result = pool.report_invalid(Some(header01.hash()), &invalid_txs[..]); @@ -510,15 +503,11 @@ fn fatp_invalid_report_future_dont_remove_from_pool() { let xt0_report = ( pool.api().hash_and_length(&xt0).0, - Some(BlockchainError::ApplyExtrinsicFailed(ApplyExtrinsicFailed::Validity( - TransactionValidityError::Invalid(InvalidTransaction::Stale), - ))), + Some(TransactionValidityError::Invalid(InvalidTransaction::Stale)), ); let xt1_report = ( pool.api().hash_and_length(&xt1).0, - Some(BlockchainError::ApplyExtrinsicFailed(ApplyExtrinsicFailed::Validity( - TransactionValidityError::Invalid(InvalidTransaction::Future), - ))), + Some(TransactionValidityError::Invalid(InvalidTransaction::Future)), ); let invalid_txs = vec![xt0_report, xt1_report]; let result = pool.report_invalid(Some(header01.hash()), &invalid_txs[..]); @@ -562,9 +551,7 @@ fn fatp_invalid_tx_is_removed_from_the_pool() { let xt0_report = ( pool.api().hash_and_length(&xt0).0, - Some(BlockchainError::ApplyExtrinsicFailed(ApplyExtrinsicFailed::Validity( - TransactionValidityError::Invalid(InvalidTransaction::BadProof), - ))), + Some(TransactionValidityError::Invalid(InvalidTransaction::BadProof)), ); let xt1_report = (pool.api().hash_and_length(&xt1).0, None); let invalid_txs = vec![xt0_report, xt1_report]; @@ -608,9 +595,7 @@ fn fatp_invalid_tx_is_removed_from_the_pool_future_subtree_stays() { let xt0_report = ( pool.api().hash_and_length(&xt0).0, - Some(BlockchainError::ApplyExtrinsicFailed(ApplyExtrinsicFailed::Validity( - TransactionValidityError::Invalid(InvalidTransaction::BadProof), - ))), + Some(TransactionValidityError::Invalid(InvalidTransaction::BadProof)), ); let invalid_txs = vec![xt0_report]; let result = pool.report_invalid(Some(header01.hash()), &invalid_txs); @@ -667,9 +652,7 @@ fn fatp_invalid_tx_is_removed_from_the_pool2() { let xt0_report = ( pool.api().hash_and_length(&xt0).0, - Some(BlockchainError::ApplyExtrinsicFailed(ApplyExtrinsicFailed::Validity( - TransactionValidityError::Invalid(InvalidTransaction::BadProof), - ))), + Some(TransactionValidityError::Invalid(InvalidTransaction::BadProof)), ); let xt1_report = (pool.api().hash_and_length(&xt1).0, None); let invalid_txs = vec![xt0_report, xt1_report]; From 44d55f3da9cb088d13aecc477d416d543d8390de Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Tue, 4 Feb 2025 21:21:49 +0100 Subject: [PATCH 52/60] merge fix --- .../client/transaction-pool/src/fork_aware_txpool/view_store.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 d569eccd61d7f..3ce80e68b5ee9 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 @@ -737,7 +737,7 @@ where .map(|tx| removed.push(tx.clone())); } - self.listener.invalidate_transactions(&remove_from_pool); + self.listener.transactions_invalidated(&remove_from_pool); removed } From 8d7334ef4edf1bec60b22fe060c7a25a0c7825ec Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Tue, 4 Feb 2025 21:22:53 +0100 Subject: [PATCH 53/60] logging fixed - tracing --- .../fork_aware_txpool/multi_view_listener.rs | 39 ++++++++----------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/multi_view_listener.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/multi_view_listener.rs index 107c2941ec183..1ef733f3565a6 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/multi_view_listener.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/multi_view_listener.rs @@ -291,25 +291,19 @@ where match request { TransactionStatusUpdate::TransactionInvalidated(..) => if self.handle_invalidate_transaction() { - log::trace!(target: LOG_TARGET, "[{:?}] mvl sending out: Invalid", self.tx_hash); return Some(TransactionStatus::Invalid) }, TransactionStatusUpdate::TransactionFinalized(_, block, index) => { - log::trace!(target: LOG_TARGET, "[{:?}] mvl sending out: Finalized", self.tx_hash); self.terminate = true; return Some(TransactionStatus::Finalized((block, index))) }, - TransactionStatusUpdate::TransactionBroadcasted(_, peers) => { - log::trace!(target: LOG_TARGET, "[{:?}] mvl sending out: Broadcasted", self.tx_hash); - return Some(TransactionStatus::Broadcast(peers)) - }, + TransactionStatusUpdate::TransactionBroadcasted(_, peers) => + return Some(TransactionStatus::Broadcast(peers)), TransactionStatusUpdate::TransactionDropped(_, DroppedReason::LimitsEnforced) => { - log::trace!(target: LOG_TARGET, "[{:?}] mvl sending out: Dropped", self.tx_hash); self.terminate = true; return Some(TransactionStatus::Dropped) }, TransactionStatusUpdate::TransactionDropped(_, DroppedReason::Usurped(by)) => { - log::trace!(target: LOG_TARGET, "[{:?}] mvl sending out: Usurped({:?})", self.tx_hash, by); self.terminate = true; return Some(TransactionStatus::Usurped(by)) }, @@ -466,32 +460,31 @@ where biased; Some((view_hash, (tx_hash, status))) = next_event(&mut aggregated_streams_map) => { if let Entry::Occupied(mut ctrl) = external_watchers_tx_hash_map.write().entry(tx_hash) { - log::trace!( + trace!( target: LOG_TARGET, - "[{:?}] aggregated_stream_map event: view:{} status:{:?}", - tx_hash, - view_hash, - status + ?tx_hash, + ?view_hash, + ?status, + "aggregated_stream_map event", ); - if let Err(e) = ctrl + if let Err(error) = ctrl .get_mut() .unbounded_send(ExternalWatcherCommand::ViewTransactionStatus(view_hash, status)) { - trace!(target: LOG_TARGET, "[{:?}] send status failed: {:?}", tx_hash, e); + trace!(target: LOG_TARGET, ?tx_hash, ?error, "send status failed"); ctrl.remove(); } } }, cmd = command_receiver.next() => { - log::trace!(target: LOG_TARGET, "cmd {:?}", cmd); match cmd { Some(ControllerCommand::AddViewStream(h,stream)) => { aggregated_streams_map.insert(h,stream); // //todo: aysnc and join all? external_watchers_tx_hash_map.write().retain(|tx_hash, ctrl| { ctrl.unbounded_send(ExternalWatcherCommand::AddView(h)) - .inspect_err(|e| { - trace!(target: LOG_TARGET, "[{:?}] invalidate_transaction: send message failed: {:?}", tx_hash, e); + .inspect_err(|error| { + trace!(target: LOG_TARGET, ?tx_hash, ?error, "add_view: send message failed"); }) .is_ok() }) @@ -501,8 +494,8 @@ where //todo: aysnc and join all? external_watchers_tx_hash_map.write().retain(|tx_hash, ctrl| { ctrl.unbounded_send(ExternalWatcherCommand::RemoveView(h)) - .inspect_err(|e| { - trace!(target: LOG_TARGET, "[{:?}] invalidate_transaction: send message failed: {:?}", tx_hash, e); + .inspect_err(|error| { + trace!(target: LOG_TARGET, ?tx_hash, ?error, "remove_view: send message failed"); }) .is_ok() }) @@ -511,11 +504,11 @@ where Some(ControllerCommand::TransactionStatusRequest(request)) => { let tx_hash = request.hash(); if let Entry::Occupied(mut ctrl) = external_watchers_tx_hash_map.write().entry(tx_hash) { - if let Err(e) = ctrl + if let Err(error) = ctrl .get_mut() .unbounded_send(ExternalWatcherCommand::PoolTransactionStatus(request)) { - trace!(target: LOG_TARGET, "[{:?}] send message failed: {:?}", tx_hash, e); + trace!(target: LOG_TARGET, ?tx_hash, ?error, "send message failed"); ctrl.remove(); } } @@ -583,7 +576,7 @@ where Some( futures::stream::unfold(external_ctx, |mut ctx| async move { if ctx.terminate { - log::trace!(target: LOG_TARGET, "[{:?}] terminate", ctx.tx_hash); + trace!(target: LOG_TARGET, tx_hash = ?ctx.tx_hash, "terminate"); return None } loop { From 48214a381438f9b78653b8995bb4e62df9da504a Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Wed, 5 Feb 2025 15:55:40 +0100 Subject: [PATCH 54/60] Dropped(invalid) supported When view report transaction as invalid it shall be treated as dropped. This commit adds the support for this. --- .../src/fork_aware_txpool/dropped_watcher.rs | 20 ++++++++++- .../fork_aware_txpool/fork_aware_txpool.rs | 8 +++-- .../fork_aware_txpool/multi_view_listener.rs | 5 ++- .../transaction-pool/src/graph/listener.rs | 5 ++- .../client/transaction-pool/tests/fatp.rs | 34 ++++++++----------- 5 files changed, 47 insertions(+), 25 deletions(-) diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/dropped_watcher.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/dropped_watcher.rs index be20a16089619..9e078617635d5 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/dropped_watcher.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/dropped_watcher.rs @@ -62,6 +62,11 @@ impl DroppedTransaction { pub fn new_enforced_by_limts(tx_hash: Hash) -> Self { Self { reason: DroppedReason::LimitsEnforced, tx_hash } } + + /// Creates a new instance with reason set to `DroppedReason::Invalid`. + pub fn new_invalid(tx_hash: Hash) -> Self { + Self { reason: DroppedReason::Invalid, tx_hash } + } } /// Provides reason of why transactions was dropped. @@ -71,6 +76,8 @@ pub enum DroppedReason { Usurped(Hash), /// Transaction was dropped because of internal pool limits being enforced. LimitsEnforced, + /// Transaction was dropped because of being invalid. + Invalid, } /// Dropped-logic related event from the single view. @@ -279,12 +286,23 @@ where return Some(DroppedTransaction::new_enforced_by_limts(tx_hash)) } } else { - debug!(target: LOG_TARGET, ?tx_hash, "dropped_watcher: removing (non-tracked) tx"); + debug!(target: LOG_TARGET, ?tx_hash, "dropped_watcher: removing (non-tracked dropped) tx"); return Some(DroppedTransaction::new_enforced_by_limts(tx_hash)) } }, TransactionStatus::Usurped(by) => return Some(DroppedTransaction::new_usurped(tx_hash, by)), + TransactionStatus::Invalid => { + if let Some(mut views_keeping_tx_valid) = self.transaction_views(tx_hash) { + views_keeping_tx_valid.get_mut().remove(&block_hash); + if views_keeping_tx_valid.get().is_empty() { + return Some(DroppedTransaction::new_invalid(tx_hash)) + } + } else { + debug!(target: LOG_TARGET, ?tx_hash, "dropped_watcher: removing (non-tracked invalid) tx"); + return Some(DroppedTransaction::new_invalid(tx_hash)) + } + }, _ => {}, }; None 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 7938d87501545..85c517e7e0d09 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 @@ -289,14 +289,16 @@ where tx_hash = ?new_tx_hash, "error: dropped_monitor_task: no entry in mempool for new transaction" ); - } + }; + }, + DroppedReason::LimitsEnforced | DroppedReason::Invalid => { + view_store.remove_transaction_subtree(tx_hash, |_, _| {}); }, - DroppedReason::LimitsEnforced => {}, }; mempool.remove_transaction(&tx_hash); - view_store.listener.transaction_dropped(dropped); import_notification_sink.clean_notified_items(&[tx_hash]); + view_store.listener.transaction_dropped(dropped); } } diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/multi_view_listener.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/multi_view_listener.rs index 1ef733f3565a6..1f7635e1a135b 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/multi_view_listener.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/multi_view_listener.rs @@ -307,8 +307,11 @@ where self.terminate = true; return Some(TransactionStatus::Usurped(by)) }, + TransactionStatusUpdate::TransactionDropped(_, DroppedReason::Invalid) => { + self.terminate = true; + return Some(TransactionStatus::Invalid) + }, }; - None } /// Handles various transaction status updates from individual views and manages internal states diff --git a/substrate/client/transaction-pool/src/graph/listener.rs b/substrate/client/transaction-pool/src/graph/listener.rs index 0e70334ea0e24..340b6d429ae7e 100644 --- a/substrate/client/transaction-pool/src/graph/listener.rs +++ b/substrate/client/transaction-pool/src/graph/listener.rs @@ -43,7 +43,8 @@ pub struct Listener { watchers: HashMap>>, finality_watchers: LinkedHashMap, Vec>, - /// The sink used to notify dropped by enforcing limits or by being usurped transactions. + /// The sink used to notify dropped by enforcing limits or by being usurped, or invalid + /// transactions. /// /// Note: Ready and future statuses are alse communicated through this channel, enabling the /// stream consumer to track views that reference the transaction. @@ -195,6 +196,8 @@ impl Listener>()); assert_eq!(xt0_events, vec![TransactionStatus::Future]); @@ -1001,15 +1000,12 @@ fn fatp_watcher_future_and_finalized() { assert_pool_status!(header01.hash(), &pool, 1, 1); - let header02 = api.push_block(2, vec![xt0], true); - let event = ChainEvent::Finalized { - hash: header02.hash(), - tree_route: Arc::from(vec![header01.hash()]), - }; - // let event = new_best_block_event(&pool, Some(header01.hash()), header02.hash()); - block_on(pool.maintain(event)); + let header02 = api.push_block_with_parent(header01.hash(), vec![xt0], true); + let header03 = api.push_block_with_parent(header02.hash(), vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header03.hash()))); + block_on(pool.maintain(finalized_block_event(&pool, header01.hash(), header02.hash()))); - assert_pool_status!(header02.hash(), &pool, 0, 1); + assert_pool_status!(header03.hash(), &pool, 0, 1); let xt1_status = block_on(xt1_watcher.take(1).collect::>()); assert_eq!(xt1_status, vec![TransactionStatus::Future]); From bfec26253219044adaf6cdb3fff542c12460ed5a Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Wed, 5 Feb 2025 15:59:33 +0100 Subject: [PATCH 55/60] improved mempool revalidation Mempool revalidation now removes the invalid transactions from the view_store. As a result handling `transactions_invalidated` in multi-view-listener does not require handling the case when transaction is still dangling in some view. (It is guaranteed that invalid transaction was removed from the view_store and can be safely notified as Invalid to external listner). --- .../fork_aware_txpool/fork_aware_txpool.rs | 1 + .../fork_aware_txpool/multi_view_listener.rs | 36 ++-------- .../fork_aware_txpool/revalidation_worker.rs | 22 ++++--- .../src/fork_aware_txpool/tx_mem_pool.rs | 66 +++++++++++++------ 4 files changed, 66 insertions(+), 59 deletions(-) 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 85c517e7e0d09..218379f737d5a 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 @@ -1381,6 +1381,7 @@ where self.revalidation_queue .revalidate_mempool( self.mempool.clone(), + self.view_store.clone(), HashAndNumber { hash: finalized_hash, number: finalized_number }, ) .await; diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/multi_view_listener.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/multi_view_listener.rs index 1f7635e1a135b..bf91a109bfd0c 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/multi_view_listener.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/multi_view_listener.rs @@ -289,10 +289,10 @@ where request: TransactionStatusUpdate, ) -> Option, BlockHash>> { match request { - TransactionStatusUpdate::TransactionInvalidated(..) => - if self.handle_invalidate_transaction() { - return Some(TransactionStatus::Invalid) - }, + TransactionStatusUpdate::TransactionInvalidated(..) => { + self.terminate = true; + return Some(TransactionStatus::Invalid) + }, TransactionStatusUpdate::TransactionFinalized(_, block, index) => { self.terminate = true; return Some(TransactionStatus::Finalized((block, index))) @@ -375,34 +375,6 @@ where } } - /// Handles transaction invalidation sent via side channel. - /// - /// Function may set the context termination flag, which will close the stream. - /// - /// Returns true if the event should be sent out, and false if the invalidation request should - /// be skipped. - fn handle_invalidate_transaction(&mut self) -> bool { - let keys = self.known_views.clone(); - trace!( - target: LOG_TARGET, - tx_hash = ?self.tx_hash, - views = ?self.known_views.iter().collect::>(), - "got invalidate_transaction" - ); - if self.views_keeping_tx_valid.is_disjoint(&keys) { - self.terminate = true; - true - } else { - //todo [#5477] - // - handle corner case: this may happen when tx is invalid for mempool, but somehow - // some view still sees it as ready/future. In that case we don't send the invalid - // event, as transaction can still be included. Probably we should set some flag here - // and allow for invalid sent from the view. - // - add debug / metrics, - false - } - } - /// Adds a new aggragted transaction status stream. /// /// Inserts a new view's transaction status stream into the stream map. The view is represented diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/revalidation_worker.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/revalidation_worker.rs index 0025d3e9f2d42..2f3d31d0e6fde 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/revalidation_worker.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/revalidation_worker.rs @@ -28,7 +28,7 @@ use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnbound use sp_blockchain::HashAndNumber; use sp_runtime::traits::Block as BlockT; -use super::tx_mem_pool::TxMemPool; +use super::{tx_mem_pool::TxMemPool, view_store::ViewStore}; use futures::prelude::*; use tracing::{trace, warn}; @@ -45,7 +45,7 @@ where /// Communication channels with maintain thread are also provided. RevalidateView(Arc>, FinishRevalidationWorkerChannels), /// Request to revalidated the given instance of the [`TxMemPool`] at provided block hash. - RevalidateMempool(Arc>, HashAndNumber), + RevalidateMempool(Arc>, Arc>, HashAndNumber), } /// The background revalidation worker. @@ -81,8 +81,11 @@ where match payload { WorkerPayload::RevalidateView(view, worker_channels) => view.revalidate(worker_channels).await, - WorkerPayload::RevalidateMempool(mempool, finalized_hash_and_number) => - mempool.revalidate(finalized_hash_and_number).await, + WorkerPayload::RevalidateMempool( + mempool, + view_store, + finalized_hash_and_number, + ) => mempool.revalidate(view_store, finalized_hash_and_number).await, }; } } @@ -164,6 +167,7 @@ where pub async fn revalidate_mempool( &self, mempool: Arc>, + view_store: Arc>, finalized_hash: HashAndNumber, ) { trace!( @@ -173,9 +177,11 @@ where ); if let Some(ref to_worker) = self.background { - if let Err(error) = - to_worker.unbounded_send(WorkerPayload::RevalidateMempool(mempool, finalized_hash)) - { + if let Err(error) = to_worker.unbounded_send(WorkerPayload::RevalidateMempool( + mempool, + view_store, + finalized_hash, + )) { warn!( target: LOG_TARGET, ?error, @@ -183,7 +189,7 @@ where ); } } else { - mempool.revalidate(finalized_hash).await + mempool.revalidate(view_store, finalized_hash).await } } } diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs index e141016ccb28b..a99dffae1c0ef 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs @@ -28,7 +28,7 @@ use std::{ cmp::Ordering, - collections::HashMap, + collections::{HashMap, HashSet}, sync::{ atomic::{self, AtomicU64}, Arc, @@ -56,8 +56,9 @@ use crate::{ }; use super::{ - metrics::MetricsLink as PrometheusMetrics, multi_view_listener::MultiViewListener, - view_store::ViewStoreSubmitOutcome, + metrics::MetricsLink as PrometheusMetrics, + multi_view_listener::MultiViewListener, + view_store::{ViewStore, ViewStoreSubmitOutcome}, }; /// The minimum interval between single transaction revalidations. Given in blocks. @@ -483,7 +484,7 @@ where trace!( target: LOG_TARGET, ?finalized_block, - "mempool::revalidate" + "mempool::revalidate_inner" ); let start = Instant::now(); @@ -531,7 +532,7 @@ where target: LOG_TARGET, ?tx_hash, ?validation_result, - "Purging: invalid" + "mempool::revalidate_inner invalid" ); Some(tx_hash) }, @@ -545,7 +546,7 @@ where count, invalid_hashes = invalid_hashes.len(), ?duration, - "mempool::revalidate" + "mempool::revalidate_inner" ); invalid_hashes @@ -570,23 +571,50 @@ where /// Revalidates transactions in the memory pool against a given finalized block and removes /// invalid ones. - pub(super) async fn revalidate(&self, finalized_block: HashAndNumber) { - trace!( - target: LOG_TARGET, - ?finalized_block, - "purge_transactions" - ); - let invalid_hashes = self.revalidate_inner(finalized_block.clone()).await; + pub(super) async fn revalidate( + &self, + view_store: Arc>, + finalized_block: HashAndNumber, + ) { + let revalidated_invalid_hashes = self.revalidate_inner(finalized_block.clone()).await; + + let mut invalid_hashes_subtrees = + revalidated_invalid_hashes.clone().into_iter().collect::>(); + for tx in &revalidated_invalid_hashes { + invalid_hashes_subtrees.extend( + view_store + .remove_transaction_subtree(*tx, |_, _| {}) + .into_iter() + .map(|tx| tx.hash), + ); + } + + { + let mut transactions = self.transactions.write(); + invalid_hashes_subtrees.iter().for_each(|tx_hash| { + transactions.remove(&tx_hash); + }); + }; self.metrics.report(|metrics| { - metrics.mempool_revalidation_invalid_txs.inc_by(invalid_hashes.len() as _) + metrics + .mempool_revalidation_invalid_txs + .inc_by(invalid_hashes_subtrees.len() as _) }); - let mut transactions = self.transactions.write(); - invalid_hashes.iter().for_each(|i| { - transactions.remove(i); - }); - self.listener.transactions_invalidated(&invalid_hashes); + let revalidated_invalid_hashes_len = revalidated_invalid_hashes.len(); + let invalid_hashes_subtrees_len = invalid_hashes_subtrees.len(); + + self.listener + .transactions_invalidated(&invalid_hashes_subtrees.into_iter().collect::>()); + + trace!( + target: LOG_TARGET, + ?finalized_block, + revalidated_invalid_hashes_len, + invalid_hashes_subtrees_len, + "mempool::revalidate" + ); } /// Updates the priority of transaction stored in mempool using provided view_store submission From 95c8ff28550a1cf88bfe641480fe8273616b4a81 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Wed, 5 Feb 2025 16:13:44 +0100 Subject: [PATCH 56/60] view_store::report_invalid does not trigger event --- .../transaction-pool/src/fork_aware_txpool/view_store.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) 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 3ce80e68b5ee9..5a8dc3c7d47c7 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 @@ -727,10 +727,7 @@ where let mut removed = vec![]; for tx_hash in &remove_from_pool { - let removed_from_pool = - self.remove_transaction_subtree(*tx_hash, |listener, removed_tx_hash| { - listener.invalid(&removed_tx_hash); - }); + let removed_from_pool = self.remove_transaction_subtree(*tx_hash, |_, _| {}); removed_from_pool .iter() .find(|tx| tx.hash == *tx_hash) From 37fb2e934fccae9ec83c1328482d3998b2e7f137 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Wed, 5 Feb 2025 16:15:02 +0100 Subject: [PATCH 57/60] logging --- .../src/fork_aware_txpool/fork_aware_txpool.rs | 7 ++++++- .../transaction-pool/src/fork_aware_txpool/view_store.rs | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) 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 218379f737d5a..adbe60c37787f 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 @@ -1487,7 +1487,12 @@ where self.mempool.try_insert_with_replacement(xt, priority, source, watched)?; for worst_hash in &insertion_info.removed { - log::trace!(target: LOG_TARGET, "removed: {worst_hash:?} replaced by {tx_hash:?}"); + trace!( + target: LOG_TARGET, + tx_hash = ?worst_hash, + new_tx_hash = ?tx_hash, + "removed: replaced by" + ); self.view_store .listener .transaction_dropped(DroppedTransaction::new_enforced_by_limts(*worst_hash)); 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 5a8dc3c7d47c7..017b137434f9e 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 @@ -261,7 +261,7 @@ where target: LOG_TARGET, ?tx_hash, %error, - "submit_local: err" + "submit_local failed" ); Err(error) }, @@ -310,7 +310,7 @@ where target: LOG_TARGET, ?tx_hash, %error, - "submit_and_watch: err" + "submit_and_watch failed" ); return Err(error); }, From 50a453989e4a46ed2c5a47d8afa3405ac8994350 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Wed, 5 Feb 2025 16:15:38 +0100 Subject: [PATCH 58/60] formatting --- .../fork_aware_txpool/fork_aware_txpool.rs | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) 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 adbe60c37787f..7c49133e97e07 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 @@ -752,26 +752,24 @@ where // // Finally, it collects the hashes of updated transactions or submission errors (either // from the mempool or view_store) into a returned vector. + const RESULTS_ASSUMPTION : &str = + "The number of Ok results in mempool is exactly the same as the size of view_store submission result. qed."; Ok(mempool_results - .into_iter() - .map(|result| { - result - .map_err(Into::into) - .and_then(|insertion| { - submission_results - .next() - .expect("The number of Ok results in mempool is exactly the same as the size of view_store submission result. qed.") - .inspect_err(|_|{ - mempool.remove_transaction(&insertion.hash); - }) + .into_iter() + .map(|result| { + result.map_err(Into::into).and_then(|insertion| { + submission_results.next().expect(RESULTS_ASSUMPTION).inspect_err(|_| { + mempool.remove_transaction(&insertion.hash); }) - }) - .map(|r| r.map(|r| { + }) + .map(|r| { + r.map(|r| { mempool.update_transaction_priority(&r); r.hash() - })) - .collect::>()) + }) + }) + .collect::>()) } /// Submits a single transaction and returns a future resolving to the submission results. From 5b70ad908e1db8c232b32816a96157a41476651b Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Wed, 5 Feb 2025 16:18:24 +0100 Subject: [PATCH 59/60] mempool: remove_transaction -> remove_transactions --- .../src/fork_aware_txpool/fork_aware_txpool.rs | 18 +++++++++--------- .../src/fork_aware_txpool/tx_mem_pool.rs | 14 +++++++------- 2 files changed, 16 insertions(+), 16 deletions(-) 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 7c49133e97e07..54371aa5d278f 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 @@ -296,7 +296,7 @@ where }, }; - mempool.remove_transaction(&tx_hash); + mempool.remove_transactions(&[tx_hash]); import_notification_sink.clean_notified_items(&[tx_hash]); view_store.listener.transaction_dropped(dropped); } @@ -759,7 +759,7 @@ where .map(|result| { result.map_err(Into::into).and_then(|insertion| { submission_results.next().expect(RESULTS_ASSUMPTION).inspect_err(|_| { - mempool.remove_transaction(&insertion.hash); + mempool.remove_transactions(&[insertion.hash]); }) }) }) @@ -826,7 +826,7 @@ where .submit_and_watch(at, timed_source, xt) .await .inspect_err(|_| { - self.mempool.remove_transaction(&xt_hash); + self.mempool.remove_transactions(&[xt_hash]); }) .map(|mut outcome| { self.mempool.update_transaction_priority(&outcome); @@ -854,12 +854,12 @@ where let removed = self.view_store.report_invalid(at, invalid_tx_errors); - removed.iter().for_each(|tx| { - self.mempool.remove_transaction(&tx.hash); - }); + let removed_hashes = removed.iter().map(|tx| tx.hash).collect::>(); + self.mempool.remove_transactions(&removed_hashes); + self.import_notification_sink.clean_notified_items(&removed_hashes); self.metrics - .report(|metrics| metrics.removed_invalid_txs.inc_by(removed.len() as _)); + .report(|metrics| metrics.removed_invalid_txs.inc_by(removed_hashes.len() as _)); removed } @@ -990,7 +990,7 @@ where self.view_store .submit_local(xt) .inspect_err(|_| { - self.mempool.remove_transaction(&insertion.hash); + self.mempool.remove_transactions(&[insertion.hash]); }) .map(|outcome| { self.mempool.update_transaction_priority(&outcome); @@ -1248,7 +1248,7 @@ where for result in results { if let Err(tx_hash) = result { self.view_store.listener.transactions_invalidated(&[tx_hash]); - self.mempool.remove_transaction(&tx_hash); + self.mempool.remove_transactions(&[tx_hash]); } } } diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs index a99dffae1c0ef..fb39741c50177 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs @@ -468,13 +468,13 @@ where self.transactions.clone_map() } - /// Removes a transaction with given hash from the memory pool. - pub(super) fn remove_transaction( - &self, - tx_hash: &ExtrinsicHash, - ) -> Option>> { - debug!(target: LOG_TARGET, ?tx_hash, "mempool::remove_transaction"); - self.transactions.write().remove(tx_hash) + /// Removes transactions with given hashes from the memory pool. + pub(super) fn remove_transactions(&self, tx_hashes: &[ExtrinsicHash]) { + log_xt_trace!(target: LOG_TARGET, tx_hashes, "mempool::remove_transaction"); + let mut transactions = self.transactions.write(); + for tx_hash in tx_hashes { + transactions.remove(tx_hash); + } } /// Revalidates a batch of transactions against the provided finalized block. From 0e5fe6d63fec50aaafceb8437853140f6642b714 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Thu, 6 Feb 2025 08:48:17 +0100 Subject: [PATCH 60/60] clippy --- .../rpc-spec-v2/src/transaction/tests/middleware_pool.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/substrate/client/rpc-spec-v2/src/transaction/tests/middleware_pool.rs b/substrate/client/rpc-spec-v2/src/transaction/tests/middleware_pool.rs index 124029121253c..de6da1f0f53f4 100644 --- a/substrate/client/rpc-spec-v2/src/transaction/tests/middleware_pool.rs +++ b/substrate/client/rpc-spec-v2/src/transaction/tests/middleware_pool.rs @@ -21,7 +21,7 @@ use codec::Encode; use sc_transaction_pool::BasicPool; use sc_transaction_pool_api::{ ImportNotificationStream, PoolStatus, ReadyTransactions, TransactionFor, TransactionPool, - TransactionSource, TransactionStatusStreamFor, TxHash, + TransactionSource, TransactionStatusStreamFor, TransactionValidityError, TxHash, }; use crate::hex_string; @@ -140,7 +140,7 @@ impl TransactionPool for MiddlewarePool { fn report_invalid( &self, at: Option<::Hash>, - invalid_tx_errors: &[(TxHash, Option)], + invalid_tx_errors: &[(TxHash, Option)], ) -> Vec> { self.inner_pool.report_invalid(at, invalid_tx_errors) }