From 515cb4042d097581ed6b4195e57b04494e385a17 Mon Sep 17 00:00:00 2001
From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com>
Date: Tue, 11 Feb 2025 21:24:36 +0100
Subject: [PATCH] View: added observer and streams + some renames

---
 .../src/fork_aware_txpool/dropped_watcher.rs  |   3 +-
 .../fork_aware_txpool/fork_aware_txpool.rs    |  58 +++---
 .../src/fork_aware_txpool/mod.rs              |   2 +-
 .../fork_aware_txpool/multi_view_listener.rs  |   4 +-
 .../fork_aware_txpool/revalidation_worker.rs  |  10 +-
 .../src/fork_aware_txpool/view.rs             | 194 ++++++++++++++++--
 .../src/fork_aware_txpool/view_store.rs       |  21 +-
 7 files changed, 226 insertions(+), 66 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 be20a160896..17f73f9ea77 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
@@ -74,7 +74,8 @@ pub enum DroppedReason<Hash> {
 }
 
 /// Dropped-logic related event from the single view.
-pub type ViewStreamEvent<C> = crate::graph::TransactionStatusEvent<ExtrinsicHash<C>, BlockHash<C>>;
+pub type ViewStreamEvent<C> =
+	crate::fork_aware_txpool::view::TransactionStatusEvent<ExtrinsicHash<C>, BlockHash<C>>;
 
 /// Dropped-logic stream of events coming from the single view.
 type ViewStream<C> = Pin<Box<dyn futures::Stream<Item = ViewStreamEvent<C>> + Send>>;
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 ffe6c20d92b..ce5dfca49e9 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
@@ -466,7 +466,7 @@ where
 		};
 
 		if let Ok((Some(best_tree_route), Some(best_view))) = best_result {
-			let tmp_view: View<ChainApi> =
+			let (tmp_view, _, _): (View<ChainApi>, _, _) =
 				View::new_from_other(&best_view, &HashAndNumber { hash: at, number: block_number });
 
 			let mut all_extrinsics = vec![];
@@ -1056,26 +1056,28 @@ where
 			?tree_route,
 			"build_new_view"
 		);
-		let mut view = if let Some(origin_view) = origin_view {
-			let mut view = View::new_from_other(&origin_view, at);
-			if !tree_route.retracted().is_empty() {
-				view.pool.clear_recently_pruned();
-			}
-			view
-		} else {
-			debug!(
-				target: LOG_TARGET,
-				?at,
-				"creating non-cloned view"
-			);
-			View::new(
-				self.api.clone(),
-				at.clone(),
-				self.options.clone(),
-				self.metrics.clone(),
-				self.is_validator.clone(),
-			)
-		};
+		let (mut view, view_dropped_stream, view_aggregated_stream) =
+			if let Some(origin_view) = origin_view {
+				let (mut view, view_dropped_stream, view_aggragated_stream) =
+					View::new_from_other(&origin_view, at);
+				if !tree_route.retracted().is_empty() {
+					view.pool.clear_recently_pruned();
+				}
+				(view, view_dropped_stream, view_aggragated_stream)
+			} else {
+				debug!(
+					target: LOG_TARGET,
+					?at,
+					"creating non-cloned view"
+				);
+				View::new(
+					self.api.clone(),
+					at.clone(),
+					self.options.clone(),
+					self.metrics.clone(),
+					self.is_validator.clone(),
+				)
+			};
 
 		let start = Instant::now();
 		// 1. Capture all import notification from the very beginning, so first register all
@@ -1085,15 +1087,13 @@ where
 			view.pool.validated_pool().import_notification_stream().boxed(),
 		);
 
-		self.view_store.dropped_stream_controller.add_view(
-			view.at.hash,
-			view.pool.validated_pool().create_dropped_by_limits_stream().boxed(),
-		);
+		self.view_store
+			.dropped_stream_controller
+			.add_view(view.at.hash, view_dropped_stream.boxed());
 
-		self.view_store.listener.add_view_aggregated_stream(
-			view.at.hash,
-			view.pool.validated_pool().create_aggregated_stream().boxed(),
-		);
+		self.view_store
+			.listener
+			.add_view_aggregated_stream(view.at.hash, view_aggregated_stream.boxed());
 		// sync the transactions statuses and referencing views in all the listeners with newly
 		// cloned view.
 		view.pool.validated_pool().retrigger_notifications();
diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/mod.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/mod.rs
index 2c4da0182a2..fce2d4ad6b2 100644
--- a/substrate/client/transaction-pool/src/fork_aware_txpool/mod.rs
+++ b/substrate/client/transaction-pool/src/fork_aware_txpool/mod.rs
@@ -323,7 +323,7 @@
 //! [`MultiViewListener`]: crate::fork_aware_txpool::multi_view_listener::MultiViewListener
 //! [`Pool`]: crate::graph::Pool
 //! [`Watcher`]: crate::graph::watcher::Watcher
-//! [`AggregatedStream`]: crate::graph::AggregatedStream
+//! [`AggregatedStream`]: crate::fork_aware_txpool::view::AggregatedStream
 //! [`Options`]: crate::graph::Options
 //! [`vp::import_notification_stream`]: ../graph/validated_pool/struct.ValidatedPool.html#method.import_notification_stream
 //! [`vp::enforce_limits`]: ../graph/validated_pool/struct.ValidatedPool.html#method.enforce_limits
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 107c2941ec1..d3d85aaf093 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
@@ -22,8 +22,8 @@
 
 use crate::{
 	common::tracing_log_xt::log_xt_trace,
-	fork_aware_txpool::stream_map_util::next_event,
-	graph::{self, BlockHash, ExtrinsicHash, TransactionStatusEvent},
+	fork_aware_txpool::{stream_map_util::next_event, view::TransactionStatusEvent},
+	graph::{self, BlockHash, ExtrinsicHash},
 	LOG_TARGET,
 };
 use futures::{Future, FutureExt, Stream, StreamExt};
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 0025d3e9f2d..b7e7584640d 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
@@ -205,13 +205,9 @@ mod tests {
 		let api = Arc::new(TestApi::default());
 		let block0 = api.expect_hash_and_number(0);
 
-		let view = Arc::new(View::new(
-			api.clone(),
-			block0,
-			Default::default(),
-			Default::default(),
-			false.into(),
-		));
+		let view = Arc::new(
+			View::new(api.clone(), block0, Default::default(), Default::default(), false.into()).0,
+		);
 		let queue = Arc::new(RevalidationQueue::new());
 
 		let uxt = uxt(Transfer {
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 55544495612..15253e0d145 100644
--- a/substrate/client/transaction-pool/src/fork_aware_txpool/view.rs
+++ b/substrate/client/transaction-pool/src/fork_aware_txpool/view.rs
@@ -27,13 +27,14 @@ use super::metrics::MetricsLink as PrometheusMetrics;
 use crate::{
 	common::tracing_log_xt::log_xt_trace,
 	graph::{
-		self, base_pool::TimedTransactionSource, ExtrinsicFor, ExtrinsicHash, IsValidator,
-		ValidatedPoolSubmitOutcome, ValidatedTransaction, ValidatedTransactionFor,
+		self, base_pool::TimedTransactionSource, BlockHash, ExtrinsicFor, ExtrinsicHash,
+		IsValidator, ValidatedPoolSubmitOutcome, ValidatedTransaction, ValidatedTransactionFor,
 	},
 	LOG_TARGET,
 };
 use parking_lot::Mutex;
-use sc_transaction_pool_api::{error::Error as TxPoolError, PoolStatus};
+use sc_transaction_pool_api::{error::Error as TxPoolError, PoolStatus, TransactionStatus};
+use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender};
 use sp_blockchain::HashAndNumber;
 use sp_runtime::{
 	generic::BlockId, traits::Block as BlockT, transaction_validity::TransactionValidityError,
@@ -108,13 +109,139 @@ impl<ChainApi: graph::ChainApi> FinishRevalidationWorkerChannels<ChainApi> {
 	}
 }
 
+/// Single event used in aggregated stream. Tuple containing hash of transactions and its status.
+pub(super) type TransactionStatusEvent<H, BH> = (H, TransactionStatus<H, BH>);
+/// Warning threshold for (unbounded) channel used in aggregated view's streams.
+const VIEW_STREAM_WARN_THRESHOLD: usize = 100_000;
+
+/// Stream of events providing statuses of all the transactions within the pool.
+pub(super) type AggregatedStream<H, BH> = TracingUnboundedReceiver<TransactionStatusEvent<H, BH>>;
+
+/// Type alias for a stream of events intended to track dropped transactions.
+type DroppedMonitoringStream<H, BH> = TracingUnboundedReceiver<TransactionStatusEvent<H, BH>>;
+
+/// Notification handler for transactions updates triggered in `ValidatedPool`.
+///
+/// `ViewPoolObserver` handles transaction status changes notifications coming from an instance of
+/// validated pool associated with the `View` and forwards them through specified channels
+/// into the View's streams.
+pub(super) struct ViewPoolObserver<ChainApi: graph::ChainApi> {
+	/// The sink used to notify dropped by enforcing limits or by being usurped transactions.
+	///
+	/// Note: Ready and future statuses are alse communicated through this channel, enabling the
+	/// stream consumer to track views that reference the transaction.
+	dropped_stream_sink: TracingUnboundedSender<
+		TransactionStatusEvent<ExtrinsicHash<ChainApi>, BlockHash<ChainApi>>,
+	>,
+
+	/// The sink of the single, merged stream providing updates for all the transactions in the
+	/// associated pool.
+	///
+	/// Note: some of the events which are currently ignored on the other side of this channel
+	/// (external watcher) are not relayed.
+	aggregated_stream_sink: TracingUnboundedSender<
+		TransactionStatusEvent<ExtrinsicHash<ChainApi>, BlockHash<ChainApi>>,
+	>,
+}
+
+impl<C: graph::ChainApi> graph::EventHandler<C> for ViewPoolObserver<C> {
+	// note: skipped, notified by ForkAwareTxPool directly to multi view listener.
+	fn broadcasted(&self, _: ExtrinsicHash<C>, _: Vec<String>) {}
+	fn dropped(&self, _: ExtrinsicHash<C>) {}
+	fn invalid(&self, _: ExtrinsicHash<C>) {}
+	fn finalized(&self, _: ExtrinsicHash<C>, _: BlockHash<C>, _: usize) {}
+	fn retracted(&self, _: ExtrinsicHash<C>, _: BlockHash<C>) {
+		// note: [#5479], we do not send to aggregated stream.
+	}
+
+	fn ready(&self, tx: ExtrinsicHash<C>) {
+		let status = TransactionStatus::Ready;
+		self.send_to_dropped_stream_sink(tx, status.clone());
+		self.send_to_aggregated_stream_sink(tx, status);
+	}
+
+	fn future(&self, tx: ExtrinsicHash<C>) {
+		let status = TransactionStatus::Future;
+		self.send_to_dropped_stream_sink(tx, status.clone());
+		self.send_to_aggregated_stream_sink(tx, status);
+	}
+
+	fn limits_enforced(&self, tx: ExtrinsicHash<C>) {
+		let status = TransactionStatus::Dropped;
+		self.send_to_dropped_stream_sink(tx, status);
+	}
+
+	fn usurped(&self, tx: ExtrinsicHash<C>, by: ExtrinsicHash<C>) {
+		let status = TransactionStatus::Usurped(by.clone());
+		self.send_to_dropped_stream_sink(tx, status);
+	}
+
+	fn pruned(&self, tx: ExtrinsicHash<C>, block_hash: BlockHash<C>, tx_index: usize) {
+		let status = TransactionStatus::InBlock((block_hash, tx_index));
+		self.send_to_aggregated_stream_sink(tx, status);
+	}
+
+	fn finality_timeout(&self, tx: ExtrinsicHash<C>, hash: BlockHash<C>) {
+		let status = TransactionStatus::FinalityTimeout(hash);
+		//todo: do we need this? [related issue: #5482]
+		self.send_to_aggregated_stream_sink(tx, status);
+	}
+}
+
+impl<ChainApi: graph::ChainApi> ViewPoolObserver<ChainApi> {
+	/// Creates an instance of `ViewPoolObserver` together with associated view's streams.
+	///
+	/// This methods creates an event handler that shall be registered in the `ValidatedPool`
+	/// instance associated with the view. It also creates new view's streams:
+	/// - a single stream intended to watch dropped transactions only. The stream can be used to
+	///   subscribe to events related to dropping of all extrinsics in the pool.
+	/// - a single merged stream for all extrinsics in the associated pool. The stream can be used
+	/// to subscribe to life-cycle events of all extrinsics in the pool. For fork-aware
+	/// pool implementation this approach seems to be more efficient than using individual
+	/// streams for every transaction.
+	fn new() -> (
+		Self,
+		DroppedMonitoringStream<ExtrinsicHash<ChainApi>, BlockHash<ChainApi>>,
+		AggregatedStream<ExtrinsicHash<ChainApi>, BlockHash<ChainApi>>,
+	) {
+		let (dropped_stream_sink, dropped_stream) =
+			tracing_unbounded("mpsc_txpool_watcher", VIEW_STREAM_WARN_THRESHOLD);
+		let (aggregated_stream_sink, aggregated_stream) =
+			tracing_unbounded("mpsc_txpool_aggregated_stream", VIEW_STREAM_WARN_THRESHOLD);
+
+		(Self { dropped_stream_sink, aggregated_stream_sink }, dropped_stream, aggregated_stream)
+	}
+
+	/// Sends given event to the `dropped_stream_sink`.
+	fn send_to_dropped_stream_sink(
+		&self,
+		tx: ExtrinsicHash<ChainApi>,
+		status: TransactionStatus<ExtrinsicHash<ChainApi>, BlockHash<ChainApi>>,
+	) {
+		if let Err(e) = self.dropped_stream_sink.unbounded_send((tx.clone(), status.clone())) {
+			trace!(target: LOG_TARGET, "[{:?}] dropped_sink: {:?} send message failed: {:?}", tx, status, e);
+		}
+	}
+
+	/// Sends given event to the `aggregated_stream_sink`.
+	fn send_to_aggregated_stream_sink(
+		&self,
+		tx: ExtrinsicHash<ChainApi>,
+		status: TransactionStatus<ExtrinsicHash<ChainApi>, BlockHash<ChainApi>>,
+	) {
+		if let Err(e) = self.aggregated_stream_sink.unbounded_send((tx.clone(), status.clone())) {
+			trace!(target: LOG_TARGET, "[{:?}] aggregated_stream {:?} send message failed: {:?}", tx, status, e);
+		}
+	}
+}
+
 /// Represents the state of transaction pool for given block.
 ///
 /// Refer to [*View*](../index.html#view) section for more details on the purpose and life cycle of
 /// the `View`.
 pub(super) struct View<ChainApi: graph::ChainApi> {
 	/// The internal pool keeping the set of ready and future transaction at the given block.
-	pub(super) pool: graph::Pool<ChainApi>,
+	pub(super) pool: graph::Pool<ChainApi, ViewPoolObserver<ChainApi>>,
 	/// The hash and number of the block with which this view is associated.
 	pub(super) at: HashAndNumber<ChainApi::Block>,
 	/// Endpoints of communication channel with background worker.
@@ -135,24 +262,50 @@ where
 		options: graph::Options,
 		metrics: PrometheusMetrics,
 		is_validator: IsValidator,
-	) -> Self {
+	) -> (
+		Self,
+		DroppedMonitoringStream<ExtrinsicHash<ChainApi>, BlockHash<ChainApi>>,
+		AggregatedStream<ExtrinsicHash<ChainApi>, BlockHash<ChainApi>>,
+	) {
 		metrics.report(|metrics| metrics.non_cloned_views.inc());
-		Self {
-			pool: graph::Pool::new(options, is_validator, api),
-			at,
-			revalidation_worker_channels: Mutex::from(None),
-			metrics,
-		}
+		let (event_handler, dropped_stream, aggregated_stream) = ViewPoolObserver::new();
+		(
+			Self {
+				pool: graph::Pool::new_with_event_handler(
+					options,
+					is_validator,
+					api,
+					event_handler,
+				),
+				at,
+				revalidation_worker_channels: Mutex::from(None),
+				metrics,
+			},
+			dropped_stream,
+			aggregated_stream,
+		)
 	}
 
 	/// Creates a copy of the other view.
-	pub(super) fn new_from_other(&self, at: &HashAndNumber<ChainApi::Block>) -> Self {
-		View {
-			at: at.clone(),
-			pool: self.pool.deep_clone(),
-			revalidation_worker_channels: Mutex::from(None),
-			metrics: self.metrics.clone(),
-		}
+	pub(super) fn new_from_other(
+		&self,
+		at: &HashAndNumber<ChainApi::Block>,
+	) -> (
+		Self,
+		DroppedMonitoringStream<ExtrinsicHash<ChainApi>, BlockHash<ChainApi>>,
+		AggregatedStream<ExtrinsicHash<ChainApi>, BlockHash<ChainApi>>,
+	) {
+		let (event_handler, dropped_stream, aggregated_stream) = ViewPoolObserver::new();
+		(
+			View {
+				at: at.clone(),
+				pool: self.pool.deep_clone_with_event_handler(event_handler),
+				revalidation_worker_channels: Mutex::from(None),
+				metrics: self.metrics.clone(),
+			},
+			dropped_stream,
+			aggregated_stream,
+		)
 	}
 
 	/// Imports single unvalidated extrinsic into the view.
@@ -498,7 +651,10 @@ where
 		listener_action: F,
 	) -> Vec<ExtrinsicHash<ChainApi>>
 	where
-		F: Fn(&mut crate::graph::Listener<ChainApi>, ExtrinsicHash<ChainApi>),
+		F: Fn(
+			&mut crate::graph::EventDispatcher<ChainApi, ViewPoolObserver<ChainApi>>,
+			ExtrinsicHash<ChainApi>,
+		),
 	{
 		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 e534decf9b1..6688ac549dc 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
@@ -20,7 +20,7 @@
 
 use super::{
 	multi_view_listener::{MultiViewListener, TxStatusStream},
-	view::View,
+	view::{View, ViewPoolObserver},
 };
 use crate::{
 	fork_aware_txpool::dropped_watcher::MultiViewDroppedWatcherController,
@@ -58,8 +58,13 @@ where
 
 /// Helper type representing the callback allowing to trigger per-transaction events on
 /// `ValidatedPool`'s listener.
-type RemovalListener<ChainApi> =
-	Arc<dyn Fn(&mut crate::graph::Listener<ChainApi>, ExtrinsicHash<ChainApi>) + Send + Sync>;
+type RemovalCallback<ChainApi> = Arc<
+	dyn Fn(
+			&mut crate::graph::EventDispatcher<ChainApi, ViewPoolObserver<ChainApi>>,
+			ExtrinsicHash<ChainApi>,
+		) + Send
+		+ Sync,
+>;
 
 /// Helper struct to maintain the context for pending transaction removal, executed for
 /// newly inserted views.
@@ -70,7 +75,7 @@ where
 	/// Hash of the transaction that will be removed,
 	xt_hash: ExtrinsicHash<ChainApi>,
 	/// Action that shall be executed on underlying `ValidatedPool`'s listener.
-	listener_action: RemovalListener<ChainApi>,
+	listener_action: RemovalCallback<ChainApi>,
 }
 
 /// This enum represents an action that should be executed on the newly built
@@ -115,7 +120,7 @@ where
 	/// Creates new unprocessed instance of pending transaction removal.
 	fn new_removal_action(
 		xt_hash: ExtrinsicHash<ChainApi>,
-		listener: RemovalListener<ChainApi>,
+		listener: RemovalCallback<ChainApi>,
 	) -> Self {
 		Self {
 			processed: false,
@@ -806,8 +811,10 @@ where
 		listener_action: F,
 	) -> Vec<ExtrinsicHash<ChainApi>>
 	where
-		F: Fn(&mut crate::graph::Listener<ChainApi>, ExtrinsicHash<ChainApi>)
-			+ Clone
+		F: Fn(
+				&mut crate::graph::EventDispatcher<ChainApi, ViewPoolObserver<ChainApi>>,
+				ExtrinsicHash<ChainApi>,
+			) + Clone
 			+ Send
 			+ Sync
 			+ 'static,
-- 
GitLab