Skip to main content

sc_statement_store/
subscription.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19//! Subscription logic for statement store.
20//!
21//! Manages subscriptions to statement topics and notifies subscribers when new statements arrive.
22//! Uses multiple matcher tasks to handle subscriptions concurrently, each responsible for a subset
23//! of subscriptions. Each matcher task maintains its own list of subscriptions and matches incoming
24//! statements against them. When a new statement is submitted, it is sent to all matcher tasks for
25//! processing. If a statement matches a subscription's filter, it is sent to the subscriber via an
26//! async channel.
27//!
28//! This design allows for efficient handling of a large number of subscriptions and statements and
29//! can be scaled by adjusting the number of matcher tasks.
30
31// Buffer size for the matcher task channels, to backpressure the submission senders.
32// This value is generous to allow for bursts of statements without dropping any or backpressuring
33// too early.
34const MATCHERS_TASK_CHANNEL_BUFFER_SIZE: usize = 80_000;
35
36// Buffer size for individual subscriptions.
37const SUBSCRIPTION_BUFFER_SIZE: usize = 128;
38
39use futures::{Stream, StreamExt};
40use itertools::Itertools;
41
42use crate::LOG_TARGET;
43use sc_utils::id_sequence::SeqID;
44use sp_core::{traits::SpawnNamed, Bytes, Encode};
45pub use sp_statement_store::StatementStore;
46use sp_statement_store::{
47	OptimizedTopicFilter, Result, Statement, StatementEvent, Topic, MAX_TOPICS,
48};
49use std::{
50	collections::{hash_map::Entry, HashMap, HashSet},
51	sync::atomic::AtomicU64,
52};
53
54/// Trait for initiating statement store subscriptions from the RPC module.
55pub trait StatementStoreSubscriptionApi: Send + Sync {
56	/// Subscribe to statements matching the topic filter.
57	///
58	/// Returns existing matching statements, a sender channel to send matched statements and a
59	/// stream for receiving matched statements when they arrive.
60	fn subscribe_statement(
61		&self,
62		topic_filter: OptimizedTopicFilter,
63	) -> Result<(Vec<Vec<u8>>, async_channel::Sender<StatementEvent>, SubscriptionStatementsStream)>;
64}
65
66/// Messages sent to matcher tasks.
67#[derive(Clone, Debug)]
68pub enum MatcherMessage {
69	/// A new statement has been submitted.
70	NewStatement(Statement),
71	/// A new subscription has been created.
72	Subscribe(SubscriptionInfo),
73	/// Unsubscribe the subscription with the given ID.
74	Unsubscribe(SeqID),
75}
76
77// Handle to manage all subscriptions.
78pub struct SubscriptionsHandle {
79	// Sequence generator for subscription IDs, atomic for thread safety.
80	// Subscription creation is expensive enough that we don't worry about overflow here.
81	id_sequence: AtomicU64,
82	//  Subscriptions matchers handlers.
83	matchers: SubscriptionsMatchersHandlers,
84}
85
86impl SubscriptionsHandle {
87	/// Create a new SubscriptionsHandle with the given task spawner and number of filter workers.
88	pub(crate) fn new(
89		task_spawner: Box<dyn SpawnNamed>,
90		num_matcher_workers: usize,
91	) -> SubscriptionsHandle {
92		let mut subscriptions_matchers_senders = Vec::with_capacity(num_matcher_workers);
93
94		for task in 0..num_matcher_workers {
95			let (subscription_matcher_sender, subscription_matcher_receiver) =
96				async_channel::bounded(MATCHERS_TASK_CHANNEL_BUFFER_SIZE);
97			subscriptions_matchers_senders.push(subscription_matcher_sender);
98			task_spawner.spawn_blocking(
99				"statement-store-subscription-filters",
100				Some("statement-store"),
101				Box::pin(async move {
102					let mut subscriptions = SubscriptionsInfo::new();
103					log::debug!(
104						target: LOG_TARGET,
105						"Started statement subscription matcher task: {task}"
106					);
107					loop {
108						let res = subscription_matcher_receiver.recv().await;
109						match res {
110							Ok(MatcherMessage::NewStatement(statement)) => {
111								subscriptions.notify_matching_filters(&statement);
112							},
113							Ok(MatcherMessage::Subscribe(info)) => {
114								subscriptions.subscribe(info);
115							},
116							Ok(MatcherMessage::Unsubscribe(seq_id)) => {
117								subscriptions.unsubscribe(seq_id);
118							},
119							Err(_) => {
120								// Expected when the subscription manager is dropped at shutdown.
121								log::error!(
122									target: LOG_TARGET,
123									"Statement subscription matcher channel closed: {task}"
124								);
125								break;
126							},
127						};
128					}
129				}),
130			);
131		}
132		SubscriptionsHandle {
133			id_sequence: AtomicU64::new(0),
134			matchers: SubscriptionsMatchersHandlers::new(subscriptions_matchers_senders),
135		}
136	}
137
138	// Generate the next unique subscription ID.
139	fn next_id(&self) -> SeqID {
140		let id = self.id_sequence.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
141		SeqID::from(id)
142	}
143
144	/// Subscribe to statements matching the topic filter.
145	pub(crate) fn subscribe(
146		&self,
147		topic_filter: OptimizedTopicFilter,
148	) -> (async_channel::Sender<StatementEvent>, SubscriptionStatementsStream) {
149		let next_id = self.next_id();
150		let (tx, rx) = async_channel::bounded(SUBSCRIPTION_BUFFER_SIZE);
151		let subscription_info =
152			SubscriptionInfo { topic_filter: topic_filter.clone(), seq_id: next_id, tx };
153
154		let result = (
155			subscription_info.tx.clone(),
156			SubscriptionStatementsStream {
157				rx,
158				sub_id: subscription_info.seq_id,
159				matchers: self.matchers.clone(),
160			},
161		);
162
163		self.matchers
164			.send_by_seq_id(subscription_info.seq_id, MatcherMessage::Subscribe(subscription_info));
165		result
166	}
167
168	pub(crate) fn notify(&self, statement: Statement) {
169		self.matchers.send_all(MatcherMessage::NewStatement(statement));
170	}
171}
172
173// Information about all subscriptions.
174// Each matcher task will have its own instance of this struct.
175struct SubscriptionsInfo {
176	// Subscriptions organized by topic for MatchAll filters.
177	//
178	// Maps each topic to an array of HashMaps, where the array is indexed by
179	// `(number_of_topics_in_filter - 1)`. For example, a subscription requiring
180	// topics [A, B] (2 topics) will be stored at index 1 under both topic A and B.
181	//
182	// This structure allows efficient matching: when a statement arrives with N topics,
183	// we only need to check subscriptions that require exactly N or fewer topics.
184	subscriptions_match_all_by_topic:
185		HashMap<Topic, [HashMap<SeqID, SubscriptionInfo>; MAX_TOPICS]>,
186	// Subscriptions organized by topic for MatchAny filters.
187	subscriptions_match_any_by_topic: HashMap<Topic, HashMap<SeqID, SubscriptionInfo>>,
188	// Subscriptions that listen with Any filter (i.e., no topic filtering).
189	subscriptions_any: HashMap<SeqID, SubscriptionInfo>,
190	// Mapping from subscription ID to topic filter.
191	by_sub_id: HashMap<SeqID, OptimizedTopicFilter>,
192}
193
194// Information about a single subscription.
195#[derive(Clone, Debug)]
196pub(crate) struct SubscriptionInfo {
197	// The filter used for this subscription.
198	topic_filter: OptimizedTopicFilter,
199	// The unique ID of this subscription.
200	seq_id: SeqID,
201	// Channel to send matched statements to the subscriber.
202	tx: async_channel::Sender<StatementEvent>,
203}
204
205impl SubscriptionsInfo {
206	fn new() -> SubscriptionsInfo {
207		SubscriptionsInfo {
208			subscriptions_match_all_by_topic: HashMap::new(),
209			subscriptions_match_any_by_topic: HashMap::new(),
210			subscriptions_any: HashMap::new(),
211			by_sub_id: HashMap::new(),
212		}
213	}
214
215	// Subscribe a new subscription.
216	fn subscribe(&mut self, subscription_info: SubscriptionInfo) {
217		self.by_sub_id
218			.insert(subscription_info.seq_id, subscription_info.topic_filter.clone());
219		match &subscription_info.topic_filter {
220			OptimizedTopicFilter::Any => {
221				self.subscriptions_any.insert(subscription_info.seq_id, subscription_info);
222			},
223			OptimizedTopicFilter::MatchAll(topics) => {
224				for topic in topics {
225					self.subscriptions_match_all_by_topic.entry(*topic).or_default()
226						[topics.len() - 1]
227						.insert(subscription_info.seq_id, subscription_info.clone());
228				}
229			},
230			OptimizedTopicFilter::MatchAny(topics) => {
231				for topic in topics {
232					self.subscriptions_match_any_by_topic
233						.entry(*topic)
234						.or_default()
235						.insert(subscription_info.seq_id, subscription_info.clone());
236				}
237			},
238		};
239	}
240
241	// Notify a single subscriber, marking it for unsubscribing if sending fails.
242	fn notify_subscriber(
243		&self,
244		subscription: &SubscriptionInfo,
245		bytes_to_send: Bytes,
246		needs_unsubscribing: &mut HashSet<SeqID>,
247	) {
248		if let Err(err) = subscription.tx.try_send(StatementEvent::NewStatements {
249			statements: vec![bytes_to_send],
250			remaining: None,
251		}) {
252			log::debug!(
253				target: LOG_TARGET,
254				"Failed to send statement to subscriber {:?}: {:?} unsubscribing it", subscription.seq_id, err
255			);
256			// Mark subscription for unsubscribing, to give it a chance to recover the buffers are
257			// generous enough, if subscription cannot keep up we unsubscribe it.
258			needs_unsubscribing.insert(subscription.seq_id);
259		}
260	}
261
262	fn notify_matching_filters(&mut self, statement: &Statement) {
263		self.notify_match_all_subscribers_best(statement);
264		self.notify_match_any_subscribers(statement);
265		self.notify_any_subscribers(statement);
266	}
267
268	// Notify all subscribers with MatchAny filters that match the given statement.
269	fn notify_match_any_subscribers(&mut self, statement: &Statement) {
270		let mut needs_unsubscribing: HashSet<SeqID> = HashSet::new();
271		let mut already_notified: HashSet<SeqID> = HashSet::new();
272
273		let bytes_to_send: Bytes = statement.encode().into();
274		for statement_topic in statement.topics() {
275			if let Some(subscriptions) = self.subscriptions_match_any_by_topic.get(statement_topic)
276			{
277				for subscription in subscriptions
278					.values()
279					.filter(|subscription| already_notified.insert(subscription.seq_id))
280				{
281					self.notify_subscriber(
282						subscription,
283						bytes_to_send.clone(),
284						&mut needs_unsubscribing,
285					);
286				}
287			}
288		}
289
290		// Unsubscribe any subscriptions that failed to receive messages, to give them a chance to
291		// recover and not miss statements.
292		for sub_id in needs_unsubscribing {
293			self.unsubscribe(sub_id);
294		}
295	}
296
297	// Notify all subscribers with MatchAll filters that match the given statement.
298	fn notify_match_all_subscribers_best(&mut self, statement: &Statement) {
299		let bytes_to_send: Bytes = statement.encode().into();
300		let mut needs_unsubscribing: HashSet<SeqID> = HashSet::new();
301		let mut already_notified: HashSet<SeqID> = HashSet::new();
302		let num_topics = statement.topics().len();
303
304		// Check all combinations of topics in the statement to find matching subscriptions.
305		// This works well because the maximum allowed topics is small (MAX_TOPICS = 4).
306		for num_topics_to_check in 1..=num_topics {
307			for topics_combination in statement.topics().iter().combinations(num_topics_to_check) {
308				// Find the topic with the fewest subscriptions to minimize the number of checks.
309				let Some(Some(topic_with_fewest)) = topics_combination
310					.iter()
311					.map(|topic| self.subscriptions_match_all_by_topic.get(*topic))
312					.min_by_key(|subscriptions| {
313						subscriptions.map_or(0, |subscriptions_by_length| {
314							subscriptions_by_length[num_topics_to_check - 1].len()
315						})
316					})
317				else {
318					continue;
319				};
320
321				// A multi-topic `MatchAll` subscription is registered under each of its topics, so
322				// it can be selected across several combinations. Notify each subscription only the
323				// first time it matches, mirroring the `MatchAny` path, to avoid duplicate
324				// delivery.
325				for subscription in
326					topic_with_fewest[num_topics_to_check - 1].values().filter(|subscription| {
327						subscription.topic_filter.matches(statement) &&
328							already_notified.insert(subscription.seq_id)
329					}) {
330					self.notify_subscriber(
331						subscription,
332						bytes_to_send.clone(),
333						&mut needs_unsubscribing,
334					);
335				}
336			}
337		}
338		// Unsubscribe any subscriptions that failed to receive messages, to give them a chance to
339		// recover and not miss statements.
340		for sub_id in needs_unsubscribing {
341			self.unsubscribe(sub_id);
342		}
343	}
344
345	// Notify all subscribers that don't filter by topic and want to receive all statements.
346	fn notify_any_subscribers(&mut self, statement: &Statement) {
347		let mut needs_unsubscribing: HashSet<SeqID> = HashSet::new();
348
349		let bytes_to_send: Bytes = statement.encode().into();
350		for subscription in self.subscriptions_any.values() {
351			self.notify_subscriber(subscription, bytes_to_send.clone(), &mut needs_unsubscribing);
352		}
353
354		// Unsubscribe any subscriptions that failed to receive messages, to give them a chance to
355		// recover and not miss statements.
356		for sub_id in needs_unsubscribing {
357			self.unsubscribe(sub_id);
358		}
359	}
360
361	// Unsubscribe a subscription by its ID.
362	fn unsubscribe(&mut self, id: SeqID) {
363		let Some(entry) = self.by_sub_id.remove(&id) else {
364			return;
365		};
366
367		let topics = match &entry {
368			OptimizedTopicFilter::Any => {
369				self.subscriptions_any.remove(&id);
370				return;
371			},
372			OptimizedTopicFilter::MatchAll(topics) => topics,
373			OptimizedTopicFilter::MatchAny(topics) => topics,
374		};
375
376		// Remove subscription from relevant maps.
377		for topic in topics {
378			// Check MatchAny map.
379			if let Entry::Occupied(mut entry) = self.subscriptions_match_any_by_topic.entry(*topic)
380			{
381				entry.get_mut().remove(&id);
382				if entry.get().is_empty() {
383					entry.remove();
384				}
385			}
386			// Check MatchAll map.
387			if let Entry::Occupied(mut entry) = self.subscriptions_match_all_by_topic.entry(*topic)
388			{
389				for subscriptions in entry.get_mut().iter_mut() {
390					if subscriptions.remove(&id).is_some() {
391						break;
392					}
393				}
394				if entry.get().iter().all(|s| s.is_empty()) {
395					entry.remove();
396				}
397			}
398		}
399	}
400}
401
402// Handlers to communicate with subscription matcher tasks.
403#[derive(Clone)]
404pub struct SubscriptionsMatchersHandlers {
405	// Channels to send messages to matcher tasks.
406	matchers: Vec<async_channel::Sender<MatcherMessage>>,
407}
408
409impl SubscriptionsMatchersHandlers {
410	/// Create new SubscriptionsMatchersHandlers with the given matcher task senders.
411	fn new(matchers: Vec<async_channel::Sender<MatcherMessage>>) -> SubscriptionsMatchersHandlers {
412		SubscriptionsMatchersHandlers { matchers }
413	}
414
415	// Send a message to the matcher task responsible for the given subscription ID.
416	fn send_by_seq_id(&self, id: SeqID, message: MatcherMessage) {
417		let index: u64 = id.into();
418		// If matchers channels are full we backpressure the sender, in this case it will be the
419		// processing of new statements.
420		if let Err(err) = self.matchers[index as usize % self.matchers.len()].send_blocking(message)
421		{
422			log::error!(
423				target: LOG_TARGET,
424				"Failed to send statement to matcher task: {:?}", err
425			);
426		}
427	}
428
429	// Send a message to all matcher tasks.
430	fn send_all(&self, message: MatcherMessage) {
431		for sender in &self.matchers {
432			if let Err(err) = sender.send_blocking(message.clone()) {
433				log::error!(
434					target: LOG_TARGET,
435					"Failed to send message to matcher task: {:?}", err
436				);
437			}
438		}
439	}
440}
441
442// Stream of statements for a subscription.
443pub struct SubscriptionStatementsStream {
444	// Channel to receive statements.
445	pub rx: async_channel::Receiver<StatementEvent>,
446	// Subscription ID, used for cleanup on drop.
447	sub_id: SeqID,
448	// Reference to the matchers for cleanup.
449	matchers: SubscriptionsMatchersHandlers,
450}
451
452// When the stream is dropped, unsubscribe from the matchers.
453impl Drop for SubscriptionStatementsStream {
454	fn drop(&mut self) {
455		self.matchers
456			.send_by_seq_id(self.sub_id, MatcherMessage::Unsubscribe(self.sub_id));
457	}
458}
459
460impl Stream for SubscriptionStatementsStream {
461	type Item = StatementEvent;
462
463	fn poll_next(
464		mut self: std::pin::Pin<&mut Self>,
465		cx: &mut std::task::Context<'_>,
466	) -> std::task::Poll<Option<Self::Item>> {
467		self.rx.poll_next_unpin(cx)
468	}
469}
470
471#[cfg(test)]
472mod tests {
473
474	use crate::tests::signed_statement;
475
476	use super::*;
477	use sp_core::Decode;
478	use sp_statement_store::Topic;
479
480	fn unwrap_statement(item: StatementEvent) -> Bytes {
481		match item {
482			StatementEvent::NewStatements { mut statements, .. } => {
483				assert_eq!(statements.len(), 1, "Expected exactly one statement in batch");
484				statements.remove(0)
485			},
486		}
487	}
488	#[test]
489	fn test_subscribe_unsubscribe() {
490		let mut subscriptions = SubscriptionsInfo::new();
491
492		let (tx1, _rx1) = async_channel::bounded::<StatementEvent>(10);
493		let topic1 = Topic::from([8u8; 32]);
494		let topic2 = Topic::from([9u8; 32]);
495		let sub_info1 = SubscriptionInfo {
496			topic_filter: OptimizedTopicFilter::MatchAll(
497				vec![topic1, topic2].into_iter().collect(),
498			),
499			seq_id: SeqID::from(1),
500			tx: tx1,
501		};
502		subscriptions.subscribe(sub_info1.clone());
503		assert!(subscriptions.subscriptions_match_all_by_topic.contains_key(&topic1));
504		assert!(subscriptions.subscriptions_match_all_by_topic.contains_key(&topic2));
505		assert!(subscriptions.by_sub_id.contains_key(&sub_info1.seq_id));
506		assert!(!subscriptions.subscriptions_any.contains_key(&sub_info1.seq_id));
507
508		subscriptions.unsubscribe(sub_info1.seq_id);
509		assert!(!subscriptions.subscriptions_match_all_by_topic.contains_key(&topic1));
510		assert!(!subscriptions.subscriptions_match_all_by_topic.contains_key(&topic2));
511	}
512
513	#[test]
514	fn test_subscribe_any() {
515		let mut subscriptions = SubscriptionsInfo::new();
516		let (tx1, _rx1) = async_channel::bounded::<StatementEvent>(10);
517		let sub_info1 = SubscriptionInfo {
518			topic_filter: OptimizedTopicFilter::Any,
519			seq_id: SeqID::from(1),
520			tx: tx1,
521		};
522		subscriptions.subscribe(sub_info1.clone());
523		assert!(subscriptions.subscriptions_any.contains_key(&sub_info1.seq_id));
524		assert!(subscriptions.by_sub_id.contains_key(&sub_info1.seq_id));
525		subscriptions.unsubscribe(sub_info1.seq_id);
526		assert!(!subscriptions.subscriptions_any.contains_key(&sub_info1.seq_id));
527	}
528
529	#[test]
530	fn test_subscribe_match_any() {
531		let mut subscriptions = SubscriptionsInfo::new();
532
533		let (tx1, _rx1) = async_channel::bounded::<StatementEvent>(10);
534		let topic1 = Topic::from([8u8; 32]);
535		let topic2 = Topic::from([9u8; 32]);
536		let sub_info1 = SubscriptionInfo {
537			topic_filter: OptimizedTopicFilter::MatchAny(
538				vec![topic1, topic2].into_iter().collect(),
539			),
540			seq_id: SeqID::from(1),
541			tx: tx1,
542		};
543		subscriptions.subscribe(sub_info1.clone());
544		assert!(subscriptions.subscriptions_match_any_by_topic.contains_key(&topic1));
545		assert!(subscriptions.subscriptions_match_any_by_topic.contains_key(&topic2));
546		assert!(subscriptions.by_sub_id.contains_key(&sub_info1.seq_id));
547		assert!(!subscriptions.subscriptions_any.contains_key(&sub_info1.seq_id));
548
549		subscriptions.unsubscribe(sub_info1.seq_id);
550		assert!(!subscriptions.subscriptions_match_all_by_topic.contains_key(&topic1));
551		assert!(!subscriptions.subscriptions_match_all_by_topic.contains_key(&topic2));
552	}
553
554	#[test]
555	fn test_notify_any_subscribers() {
556		let mut subscriptions = SubscriptionsInfo::new();
557
558		let (tx1, rx1) = async_channel::bounded::<StatementEvent>(10);
559		let sub_info1 = SubscriptionInfo {
560			topic_filter: OptimizedTopicFilter::Any,
561			seq_id: SeqID::from(1),
562			tx: tx1,
563		};
564		subscriptions.subscribe(sub_info1.clone());
565
566		let statement = signed_statement(1);
567		subscriptions.notify_matching_filters(&statement);
568
569		let received = unwrap_statement(rx1.try_recv().expect("Should receive statement"));
570		let decoded_statement: Statement =
571			Statement::decode(&mut &received.0[..]).expect("Should decode statement");
572		assert_eq!(decoded_statement, statement);
573	}
574
575	#[test]
576	fn test_notify_match_all_subscribers() {
577		let mut subscriptions = SubscriptionsInfo::new();
578
579		let (tx1, rx1) = async_channel::bounded::<StatementEvent>(10);
580		let topic1 = Topic::from([8u8; 32]);
581		let topic2 = Topic::from([9u8; 32]);
582		let sub_info1 = SubscriptionInfo {
583			topic_filter: OptimizedTopicFilter::MatchAll(
584				vec![topic1, topic2].into_iter().collect(),
585			),
586			seq_id: SeqID::from(1),
587			tx: tx1,
588		};
589		subscriptions.subscribe(sub_info1.clone());
590
591		let mut statement = signed_statement(1);
592		statement.set_topic(0, topic2);
593		subscriptions.notify_matching_filters(&statement);
594
595		// Should not receive yet, only one topic matched.
596		assert!(rx1.try_recv().is_err());
597
598		statement.set_topic(1, topic1);
599		subscriptions.notify_matching_filters(&statement);
600
601		let received = unwrap_statement(rx1.try_recv().expect("Should receive statement"));
602		let decoded_statement: Statement =
603			Statement::decode(&mut &received.0[..]).expect("Should decode statement");
604		assert_eq!(decoded_statement, statement);
605	}
606
607	#[test]
608	fn test_notify_match_any_subscribers() {
609		let mut subscriptions = SubscriptionsInfo::new();
610		let (tx1, rx1) = async_channel::bounded::<StatementEvent>(10);
611		let (tx2, rx2) = async_channel::bounded::<StatementEvent>(10);
612
613		let topic1 = Topic::from([8u8; 32]);
614		let topic2 = Topic::from([9u8; 32]);
615		let sub_info1 = SubscriptionInfo {
616			topic_filter: OptimizedTopicFilter::MatchAny(
617				vec![topic1, topic2].into_iter().collect(),
618			),
619			seq_id: SeqID::from(1),
620			tx: tx1,
621		};
622
623		let sub_info2 = SubscriptionInfo {
624			topic_filter: OptimizedTopicFilter::MatchAny(vec![topic2].into_iter().collect()),
625			seq_id: SeqID::from(2),
626			tx: tx2,
627		};
628
629		subscriptions.subscribe(sub_info1.clone());
630		subscriptions.subscribe(sub_info2.clone());
631
632		let mut statement = signed_statement(1);
633		statement.set_topic(0, topic1);
634		statement.set_topic(1, topic2);
635		subscriptions.notify_match_any_subscribers(&statement);
636
637		let received = unwrap_statement(rx1.try_recv().expect("Should receive statement"));
638		let decoded_statement: Statement =
639			Statement::decode(&mut &received.0[..]).expect("Should decode statement");
640		assert_eq!(decoded_statement, statement);
641
642		let received = unwrap_statement(rx2.try_recv().expect("Should receive statement"));
643		let decoded_statement: Statement =
644			Statement::decode(&mut &received.0[..]).expect("Should decode statement");
645		assert_eq!(decoded_statement, statement);
646	}
647
648	#[tokio::test]
649	async fn test_subscription_handle_with_different_workers_number() {
650		for num_workers in 1..5 {
651			let subscriptions_handle = SubscriptionsHandle::new(
652				Box::new(sp_core::testing::TaskExecutor::new()),
653				num_workers,
654			);
655
656			let topic1 = Topic::from([8u8; 32]);
657			let topic2 = Topic::from([9u8; 32]);
658
659			let streams = (0..5)
660				.into_iter()
661				.map(|_| {
662					subscriptions_handle.subscribe(OptimizedTopicFilter::MatchAll(
663						vec![topic1, topic2].into_iter().collect(),
664					))
665				})
666				.collect::<Vec<_>>();
667
668			let mut statement = signed_statement(1);
669			statement.set_topic(0, topic2);
670			subscriptions_handle.notify(statement.clone());
671
672			statement.set_topic(1, topic1);
673			subscriptions_handle.notify(statement.clone());
674
675			for (_tx, mut stream) in streams {
676				let received =
677					unwrap_statement(stream.next().await.expect("Should receive statement"));
678				let decoded_statement: Statement =
679					Statement::decode(&mut &received.0[..]).expect("Should decode statement");
680				assert_eq!(decoded_statement, statement);
681			}
682		}
683	}
684
685	#[tokio::test]
686	async fn test_handle_unsubscribe() {
687		let subscriptions_handle =
688			SubscriptionsHandle::new(Box::new(sp_core::testing::TaskExecutor::new()), 2);
689
690		let topic1 = Topic::from([8u8; 32]);
691		let topic2 = Topic::from([9u8; 32]);
692
693		let (tx, mut stream) = subscriptions_handle
694			.subscribe(OptimizedTopicFilter::MatchAll(vec![topic1, topic2].into_iter().collect()));
695
696		let mut statement = signed_statement(1);
697		statement.set_topic(0, topic1);
698		statement.set_topic(1, topic2);
699
700		// Send a statement and verify it's received.
701		subscriptions_handle.notify(statement.clone());
702
703		let received = unwrap_statement(stream.next().await.expect("Should receive statement"));
704		let decoded_statement: Statement =
705			Statement::decode(&mut &received.0[..]).expect("Should decode statement");
706		assert_eq!(decoded_statement, statement);
707
708		// Drop the stream to trigger unsubscribe.
709		drop(stream);
710
711		// Give some time for the unsubscribe message to be processed.
712		tokio::time::sleep(std::time::Duration::from_millis(1000)).await;
713
714		// Send another statement after unsubscribe.
715		let mut statement2 = signed_statement(2);
716		statement2.set_topic(0, topic1);
717		statement2.set_topic(1, topic2);
718		subscriptions_handle.notify(statement2.clone());
719
720		// The tx channel should be closed/disconnected since the subscription was removed.
721		// Give some time for the notification to potentially arrive (it shouldn't).
722		tokio::time::sleep(std::time::Duration::from_millis(1000)).await;
723
724		// The sender should fail to send since the subscription is gone.
725		// We verify by checking that the tx channel is disconnected.
726		assert!(tx.is_closed(), "Sender should be closed after unsubscribe");
727	}
728
729	#[test]
730	fn test_unsubscribe_nonexistent() {
731		let mut subscriptions = SubscriptionsInfo::new();
732		// Unsubscribing a non-existent subscription should not panic.
733		subscriptions.unsubscribe(SeqID::from(999));
734		// Verify internal state is still valid.
735		assert!(subscriptions.by_sub_id.is_empty());
736		assert!(subscriptions.subscriptions_any.is_empty());
737		assert!(subscriptions.subscriptions_match_all_by_topic.is_empty());
738		assert!(subscriptions.subscriptions_match_any_by_topic.is_empty());
739	}
740
741	#[test]
742	fn test_multiple_subscriptions_same_topic() {
743		let mut subscriptions = SubscriptionsInfo::new();
744
745		let (tx1, rx1) = async_channel::bounded::<StatementEvent>(10);
746		let (tx2, rx2) = async_channel::bounded::<StatementEvent>(10);
747		let topic1 = Topic::from([8u8; 32]);
748		let topic2 = Topic::from([9u8; 32]);
749
750		let sub_info1 = SubscriptionInfo {
751			topic_filter: OptimizedTopicFilter::MatchAll(
752				vec![topic1, topic2].into_iter().collect(),
753			),
754			seq_id: SeqID::from(1),
755			tx: tx1,
756		};
757		let sub_info2 = SubscriptionInfo {
758			topic_filter: OptimizedTopicFilter::MatchAll(
759				vec![topic1, topic2].into_iter().collect(),
760			),
761			seq_id: SeqID::from(2),
762			tx: tx2,
763		};
764
765		subscriptions.subscribe(sub_info1.clone());
766		subscriptions.subscribe(sub_info2.clone());
767
768		// Both subscriptions should be registered under each topic.
769		assert_eq!(
770			subscriptions
771				.subscriptions_match_all_by_topic
772				.get(&topic1)
773				.unwrap()
774				.iter()
775				.map(|s| s.len())
776				.sum::<usize>(),
777			2
778		);
779		assert_eq!(
780			subscriptions
781				.subscriptions_match_all_by_topic
782				.get(&topic2)
783				.unwrap()
784				.iter()
785				.map(|s| s.len())
786				.sum::<usize>(),
787			2
788		);
789
790		// Send a matching statement.
791		let mut statement = signed_statement(1);
792		statement.set_topic(0, topic1);
793		statement.set_topic(1, topic2);
794		subscriptions.notify_matching_filters(&statement);
795
796		// Both should receive.
797		assert!(rx1.try_recv().is_ok());
798		assert!(rx2.try_recv().is_ok());
799
800		// Unsubscribe one.
801		subscriptions.unsubscribe(sub_info1.seq_id);
802
803		// Only one subscription should remain.
804		assert_eq!(
805			subscriptions
806				.subscriptions_match_all_by_topic
807				.get(&topic1)
808				.unwrap()
809				.iter()
810				.map(|s| s.len())
811				.sum::<usize>(),
812			1
813		);
814		assert_eq!(
815			subscriptions
816				.subscriptions_match_all_by_topic
817				.get(&topic2)
818				.unwrap()
819				.iter()
820				.map(|s| s.len())
821				.sum::<usize>(),
822			1
823		);
824		assert!(!subscriptions.by_sub_id.contains_key(&sub_info1.seq_id));
825		assert!(subscriptions.by_sub_id.contains_key(&sub_info2.seq_id));
826
827		// Send another statement.
828		subscriptions.notify_matching_filters(&statement);
829
830		// Only sub2 should receive.
831		assert!(rx2.try_recv().is_ok());
832		assert!(rx1.try_recv().is_err());
833	}
834
835	#[test]
836	fn test_subscriber_auto_unsubscribe_on_channel_full() {
837		let mut subscriptions = SubscriptionsInfo::new();
838
839		// Create a channel with capacity 1.
840		let (tx1, rx1) = async_channel::bounded::<StatementEvent>(1);
841		let topic1 = Topic::from([8u8; 32]);
842
843		let sub_info1 = SubscriptionInfo {
844			topic_filter: OptimizedTopicFilter::MatchAny(vec![topic1].into_iter().collect()),
845			seq_id: SeqID::from(1),
846			tx: tx1,
847		};
848		subscriptions.subscribe(sub_info1.clone());
849
850		let mut statement = signed_statement(1);
851		statement.set_topic(0, topic1);
852
853		// First notification should succeed.
854		subscriptions.notify_matching_filters(&statement);
855		assert!(rx1.try_recv().is_ok());
856
857		// Fill the channel.
858		subscriptions.notify_matching_filters(&statement);
859		// Channel is now full.
860
861		// Next notification should trigger auto-unsubscribe.
862		subscriptions.notify_matching_filters(&statement);
863
864		// Subscription should be removed.
865		assert!(!subscriptions.by_sub_id.contains_key(&sub_info1.seq_id));
866		assert!(!subscriptions.subscriptions_match_any_by_topic.contains_key(&topic1));
867	}
868
869	#[test]
870	fn test_match_any_receives_once_per_statement() {
871		let mut subscriptions = SubscriptionsInfo::new();
872
873		let (tx1, rx1) = async_channel::bounded::<StatementEvent>(10);
874		let topic1 = Topic::from([8u8; 32]);
875		let topic2 = Topic::from([9u8; 32]);
876
877		// Subscribe to MatchAny with both topics.
878		let sub_info1 = SubscriptionInfo {
879			topic_filter: OptimizedTopicFilter::MatchAny(
880				vec![topic1, topic2].into_iter().collect(),
881			),
882			seq_id: SeqID::from(1),
883			tx: tx1,
884		};
885		subscriptions.subscribe(sub_info1.clone());
886
887		// Create a statement that matches BOTH topics.
888		let mut statement = signed_statement(1);
889		statement.set_topic(0, topic1);
890		statement.set_topic(1, topic2);
891
892		subscriptions.notify_match_any_subscribers(&statement);
893
894		// Should receive exactly once, not twice.
895		let received = unwrap_statement(rx1.try_recv().expect("Should receive statement"));
896		let decoded_statement: Statement =
897			Statement::decode(&mut &received.0[..]).expect("Should decode statement");
898		assert_eq!(decoded_statement, statement);
899
900		// No more messages.
901		assert!(rx1.try_recv().is_err());
902	}
903
904	#[test]
905	fn test_match_all_receives_once_per_statement() {
906		// A `MatchAll` subscriber must receive each matching statement exactly once, even when it
907		// is registered under several of the statement's topics.
908		let mut subscriptions = SubscriptionsInfo::new();
909
910		let (tx1, rx1) = async_channel::bounded::<StatementEvent>(10);
911		let (tx2, _rx2) = async_channel::bounded::<StatementEvent>(10);
912		let (tx3, _rx3) = async_channel::bounded::<StatementEvent>(10);
913
914		let topic1 = Topic::from([1u8; 32]);
915		let topic2 = Topic::from([2u8; 32]);
916		let topic3 = Topic::from([3u8; 32]);
917		let topic4 = Topic::from([4u8; 32]);
918
919		// The subscription under test: MatchAll on topic1 AND topic2 (stored under both topics).
920		let sub_info1 = SubscriptionInfo {
921			topic_filter: OptimizedTopicFilter::MatchAll(
922				vec![topic1, topic2].into_iter().collect(),
923			),
924			seq_id: SeqID::from(1),
925			tx: tx1,
926		};
927		subscriptions.subscribe(sub_info1.clone());
928
929		// Extra MatchAll subscriptions on topic3 so the matcher encounters sub_info1 across
930		// several topic combinations of the statement below. They do not match the statement
931		// themselves (topic4 is absent).
932		let sub_info2 = SubscriptionInfo {
933			topic_filter: OptimizedTopicFilter::MatchAll(
934				vec![topic3, topic4].into_iter().collect(),
935			),
936			seq_id: SeqID::from(2),
937			tx: tx2,
938		};
939		let sub_info3 = SubscriptionInfo {
940			topic_filter: OptimizedTopicFilter::MatchAll(
941				vec![topic3, topic4].into_iter().collect(),
942			),
943			seq_id: SeqID::from(3),
944			tx: tx3,
945		};
946		subscriptions.subscribe(sub_info2);
947		subscriptions.subscribe(sub_info3);
948
949		// Statement carrying topic1, topic2 and topic3.
950		let mut statement = signed_statement(1);
951		statement.set_topic(0, topic1);
952		statement.set_topic(1, topic2);
953		statement.set_topic(2, topic3);
954
955		subscriptions.notify_match_all_subscribers_best(&statement);
956
957		// sub_info1 must receive the statement exactly once.
958		let received = unwrap_statement(rx1.try_recv().expect("Should receive statement"));
959		let decoded_statement: Statement =
960			Statement::decode(&mut &received.0[..]).expect("Should decode statement");
961		assert_eq!(decoded_statement, statement);
962
963		assert!(
964			rx1.try_recv().is_err(),
965			"MatchAll subscriber must receive each statement only once"
966		);
967	}
968
969	#[test]
970	fn test_match_all_with_single_topic_matches_statement_with_two_topics() {
971		let mut subscriptions = SubscriptionsInfo::new();
972
973		let (tx1, rx1) = async_channel::bounded::<StatementEvent>(10);
974		let topic1 = Topic::from([8u8; 32]);
975		let topic2 = Topic::from([9u8; 32]);
976
977		// Subscribe with MatchAll on only topic1.
978		let sub_info1 = SubscriptionInfo {
979			topic_filter: OptimizedTopicFilter::MatchAll(vec![topic1].into_iter().collect()),
980			seq_id: SeqID::from(1),
981			tx: tx1,
982		};
983		subscriptions.subscribe(sub_info1.clone());
984
985		// Create a statement that has BOTH topic1 and topic2.
986		let mut statement = signed_statement(1);
987		statement.set_topic(0, topic1);
988		statement.set_topic(1, topic2);
989
990		subscriptions.notify_matching_filters(&statement);
991
992		// Should receive because the statement contains topic1 (which is the only required topic).
993		let received = unwrap_statement(rx1.try_recv().expect("Should receive statement"));
994		let decoded_statement: Statement =
995			Statement::decode(&mut &received.0[..]).expect("Should decode statement");
996		assert_eq!(decoded_statement, statement);
997
998		// No more messages.
999		assert!(rx1.try_recv().is_err());
1000	}
1001
1002	#[test]
1003	fn test_match_all_no_matching_topics() {
1004		let mut subscriptions = SubscriptionsInfo::new();
1005
1006		let (tx1, rx1) = async_channel::bounded::<StatementEvent>(10);
1007		let topic1 = Topic::from([8u8; 32]);
1008		let topic2 = Topic::from([9u8; 32]);
1009		let topic3 = Topic::from([10u8; 32]);
1010
1011		let sub_info1 = SubscriptionInfo {
1012			topic_filter: OptimizedTopicFilter::MatchAll(
1013				vec![topic1, topic2].into_iter().collect(),
1014			),
1015			seq_id: SeqID::from(1),
1016			tx: tx1,
1017		};
1018		subscriptions.subscribe(sub_info1.clone());
1019
1020		// Statement with completely different topics.
1021		let mut statement = signed_statement(1);
1022		statement.set_topic(0, topic3);
1023
1024		subscriptions.notify_matching_filters(&statement);
1025
1026		// Should not receive anything.
1027		assert!(rx1.try_recv().is_err());
1028	}
1029
1030	#[test]
1031	fn test_match_all_with_unsubscribed_topic_first_in_statement() {
1032		// This test exposes a bug where `return` is used instead of `continue` in
1033		// `notify_match_all_subscribers_best`. When a statement has a topic that has no
1034		// subscriptions (not in the map), the function returns early instead of checking
1035		// subsequent topic combinations.
1036		let mut subscriptions = SubscriptionsInfo::new();
1037
1038		let (tx1, rx1) = async_channel::bounded::<StatementEvent>(10);
1039		// topic1 will have NO subscriptions
1040		let topic1 = Topic::from([1u8; 32]);
1041		// topic2 WILL have a subscription
1042		let topic2 = Topic::from([2u8; 32]);
1043
1044		// Subscribe only to topic2 with MatchAll filter.
1045		let sub_info1 = SubscriptionInfo {
1046			topic_filter: OptimizedTopicFilter::MatchAll(vec![topic2].into_iter().collect()),
1047			seq_id: SeqID::from(1),
1048			tx: tx1,
1049		};
1050		subscriptions.subscribe(sub_info1);
1051
1052		// Create a statement with BOTH topics. topic1 comes first (lower bytes).
1053		// When iterating combinations(1), [topic1] is checked before [topic2].
1054		// Since topic1 has no subscriptions, the buggy `return` exits early,
1055		// preventing the [topic2] combination from being checked.
1056		let mut statement = signed_statement(1);
1057		statement.set_topic(0, topic1);
1058		statement.set_topic(1, topic2);
1059
1060		subscriptions.notify_match_all_subscribers_best(&statement);
1061
1062		// With the bug: rx1.try_recv() fails because the function returned early.
1063		// With the fix: rx1.try_recv() succeeds because [topic2] combination is checked.
1064		let received = unwrap_statement(rx1.try_recv().expect(
1065			"Should receive statement - if this fails, the `return` bug in \
1066			 notify_match_all_subscribers_best is present (should be `continue`)",
1067		));
1068		let decoded_statement: Statement =
1069			Statement::decode(&mut &received.0[..]).expect("Should decode statement");
1070		assert_eq!(decoded_statement, statement);
1071	}
1072
1073	#[tokio::test]
1074	async fn test_handle_with_match_any_filter() {
1075		let subscriptions_handle =
1076			SubscriptionsHandle::new(Box::new(sp_core::testing::TaskExecutor::new()), 2);
1077
1078		let topic1 = Topic::from([8u8; 32]);
1079		let topic2 = Topic::from([9u8; 32]);
1080
1081		let (_tx, mut stream) = subscriptions_handle
1082			.subscribe(OptimizedTopicFilter::MatchAny(vec![topic1, topic2].into_iter().collect()));
1083
1084		// Statement matching only topic1.
1085		let mut statement1 = signed_statement(1);
1086		statement1.set_topic(0, topic1);
1087		subscriptions_handle.notify(statement1.clone());
1088
1089		let received = unwrap_statement(stream.next().await.expect("Should receive statement"));
1090		let decoded_statement: Statement =
1091			Statement::decode(&mut &received.0[..]).expect("Should decode statement");
1092		assert_eq!(decoded_statement, statement1);
1093
1094		// Statement matching only topic2.
1095		let mut statement2 = signed_statement(2);
1096		statement2.set_topic(0, topic2);
1097		subscriptions_handle.notify(statement2.clone());
1098
1099		let received = unwrap_statement(stream.next().await.expect("Should receive statement"));
1100		let decoded_statement: Statement =
1101			Statement::decode(&mut &received.0[..]).expect("Should decode statement");
1102		assert_eq!(decoded_statement, statement2);
1103	}
1104
1105	#[tokio::test]
1106	async fn test_handle_with_any_filter() {
1107		let subscriptions_handle =
1108			SubscriptionsHandle::new(Box::new(sp_core::testing::TaskExecutor::new()), 2);
1109
1110		let (_tx, mut stream) = subscriptions_handle.subscribe(OptimizedTopicFilter::Any);
1111
1112		// Send statements with various topics.
1113		let statement1 = signed_statement(1);
1114		subscriptions_handle.notify(statement1.clone());
1115
1116		let received = unwrap_statement(stream.next().await.expect("Should receive statement"));
1117		let decoded_statement: Statement =
1118			Statement::decode(&mut &received.0[..]).expect("Should decode statement");
1119		assert_eq!(decoded_statement, statement1);
1120
1121		let mut statement2 = signed_statement(2);
1122		statement2.set_topic(0, Topic::from([99u8; 32]));
1123		subscriptions_handle.notify(statement2.clone());
1124
1125		let received = unwrap_statement(stream.next().await.expect("Should receive statement"));
1126		let decoded_statement: Statement =
1127			Statement::decode(&mut &received.0[..]).expect("Should decode statement");
1128		assert_eq!(decoded_statement, statement2);
1129	}
1130
1131	#[tokio::test]
1132	async fn test_handle_multiple_subscribers_different_filters() {
1133		let subscriptions_handle =
1134			SubscriptionsHandle::new(Box::new(sp_core::testing::TaskExecutor::new()), 2);
1135
1136		let topic1 = Topic::from([8u8; 32]);
1137		let topic2 = Topic::from([9u8; 32]);
1138
1139		// Subscriber 1: MatchAll on topic1 and topic2.
1140		let (_tx1, mut stream1) = subscriptions_handle
1141			.subscribe(OptimizedTopicFilter::MatchAll(vec![topic1, topic2].into_iter().collect()));
1142
1143		// Subscriber 2: MatchAny on topic1.
1144		let (_tx2, mut stream2) = subscriptions_handle
1145			.subscribe(OptimizedTopicFilter::MatchAny(vec![topic1].into_iter().collect()));
1146
1147		// Subscriber 3: Any.
1148		let (_tx3, mut stream3) = subscriptions_handle.subscribe(OptimizedTopicFilter::Any);
1149
1150		// Statement matching only topic1.
1151		let mut statement1 = signed_statement(1);
1152		statement1.set_topic(0, topic1);
1153		subscriptions_handle.notify(statement1.clone());
1154
1155		// stream1 should NOT receive (needs both topics).
1156		// stream2 should receive (MatchAny topic1).
1157		// stream3 should receive (Any).
1158
1159		let received2 = unwrap_statement(stream2.next().await.expect("stream2 should receive"));
1160		let decoded2: Statement = Statement::decode(&mut &received2.0[..]).unwrap();
1161		assert_eq!(decoded2, statement1);
1162
1163		let received3 = unwrap_statement(stream3.next().await.expect("stream3 should receive"));
1164		let decoded3: Statement = Statement::decode(&mut &received3.0[..]).unwrap();
1165		assert_eq!(decoded3, statement1);
1166
1167		// Statement matching both topics.
1168		let mut statement2 = signed_statement(2);
1169		statement2.set_topic(0, topic1);
1170		statement2.set_topic(1, topic2);
1171		subscriptions_handle.notify(statement2.clone());
1172
1173		// All should receive.
1174		let received1 = unwrap_statement(stream1.next().await.expect("stream1 should receive"));
1175		let decoded1: Statement = Statement::decode(&mut &received1.0[..]).unwrap();
1176		assert_eq!(decoded1, statement2);
1177
1178		let received2 = unwrap_statement(stream2.next().await.expect("stream2 should receive"));
1179		let decoded2: Statement = Statement::decode(&mut &received2.0[..]).unwrap();
1180		assert_eq!(decoded2, statement2);
1181
1182		let received3 = unwrap_statement(stream3.next().await.expect("stream3 should receive"));
1183		let decoded3: Statement = Statement::decode(&mut &received3.0[..]).unwrap();
1184		assert_eq!(decoded3, statement2);
1185	}
1186
1187	#[test]
1188	fn test_statement_without_topics_matches_only_any_filter() {
1189		let mut subscriptions = SubscriptionsInfo::new();
1190
1191		let (tx_match_all, rx_match_all) = async_channel::bounded::<StatementEvent>(10);
1192		let (tx_match_any, rx_match_any) = async_channel::bounded::<StatementEvent>(10);
1193		let (tx_any, rx_any) = async_channel::bounded::<StatementEvent>(10);
1194
1195		let topic1 = Topic::from([8u8; 32]);
1196		let topic2 = Topic::from([9u8; 32]);
1197
1198		// Subscribe with MatchAll filter.
1199		let sub_match_all = SubscriptionInfo {
1200			topic_filter: OptimizedTopicFilter::MatchAll(
1201				vec![topic1, topic2].into_iter().collect(),
1202			),
1203			seq_id: SeqID::from(1),
1204			tx: tx_match_all,
1205		};
1206		subscriptions.subscribe(sub_match_all);
1207
1208		// Subscribe with MatchAny filter.
1209		let sub_match_any = SubscriptionInfo {
1210			topic_filter: OptimizedTopicFilter::MatchAny(
1211				vec![topic1, topic2].into_iter().collect(),
1212			),
1213			seq_id: SeqID::from(2),
1214			tx: tx_match_any,
1215		};
1216		subscriptions.subscribe(sub_match_any);
1217
1218		// Subscribe with Any filter.
1219		let sub_any = SubscriptionInfo {
1220			topic_filter: OptimizedTopicFilter::Any,
1221			seq_id: SeqID::from(3),
1222			tx: tx_any,
1223		};
1224		subscriptions.subscribe(sub_any);
1225
1226		// Create a statement without any topics set.
1227		let statement = signed_statement(1);
1228		assert!(statement.topics().is_empty(), "Statement should have no topics");
1229
1230		// Notify all matching filters.
1231		subscriptions.notify_matching_filters(&statement);
1232
1233		// Any should receive (matches all statements regardless of topics).
1234		let received =
1235			unwrap_statement(rx_any.try_recv().expect("Any filter should receive statement"));
1236		let decoded_statement: Statement =
1237			Statement::decode(&mut &received.0[..]).expect("Should decode statement");
1238		assert_eq!(decoded_statement, statement);
1239
1240		// MatchAll should NOT receive (statement has no topics, filter requires topic1 AND topic2).
1241		assert!(
1242			rx_match_all.try_recv().is_err(),
1243			"MatchAll should not receive statement without topics"
1244		);
1245
1246		// MatchAny should NOT receive (statement has no topics, filter requires topic1 OR topic2).
1247		assert!(
1248			rx_match_any.try_recv().is_err(),
1249			"MatchAny should not receive statement without topics"
1250		);
1251	}
1252}