Skip to main content

sc_statement_store/
lib.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//! Disk-backed statement store.
20//!
21//! This module contains an implementation of `sp_statement_store::StatementStore` which is backed
22//! by a database.
23//!
24//! Constraint management.
25//!
26//! The statement store validates statements using node-side signature verification and
27//! static runtime allowance limits.
28//! The following constraints are then checked:
29//! * For a given account id, there may be at most `max_count` statements with `max_size` total data
30//!   size. To satisfy this, statements for this account ID are removed from the store starting with
31//!   the lowest priority until a constraint is satisfied.
32//! * There may not be more than `MAX_TOTAL_STATEMENTS` total statements with `MAX_TOTAL_SIZE` size.
33//!   To satisfy this, statements are removed from the store starting with the lowest
34//!   `global_priority` until a constraint is satisfied.
35//!
36//! When a new statement is inserted that would not satisfy constraints in the first place, no
37//! statements are deleted and `Ignored` result is returned.
38//! The order in which statements with the same priority are deleted is unspecified.
39//!
40//! Statement expiration.
41//!
42//! Each time a statement is removed from the store (Either evicted by higher priority statement or
43//! explicitly with the `remove` function) the statement is marked as expired. Expired statements
44//! can't be added to the store for `Options::purge_after_sec` seconds. This is to prevent old
45//! statements from being propagated on the network.
46
47#![warn(missing_docs)]
48#![warn(unused_extern_crates)]
49
50mod metrics;
51mod subscription;
52
53#[cfg(feature = "test-helpers")]
54pub mod subxt_client;
55#[cfg(feature = "test-helpers")]
56pub mod test_utils;
57
58use crate::subscription::{SubscriptionStatementsStream, SubscriptionsHandle};
59use futures::FutureExt;
60use metrics::MetricsLink as PrometheusMetrics;
61use parking_lot::{lock_api::RwLockUpgradableReadGuard, RwLock};
62use prometheus_endpoint::Registry as PrometheusRegistry;
63use sc_client_api::{backend::StorageProvider, Backend, StorageKey};
64use sc_keystore::LocalKeystore;
65use sp_blockchain::HeaderBackend;
66use sp_core::{crypto::UncheckedFrom, hexdisplay::HexDisplay, traits::SpawnNamed, Decode, Encode};
67use sp_runtime::traits::Block as BlockT;
68use sp_statement_store::{
69	runtime_api::{StatementSource, StatementStoreExt},
70	AccountId, BlockHash, Channel, DecryptionKey, FilterDecision, Hash, InvalidReason,
71	OptimizedTopicFilter, Proof, RejectionReason, Result, SignatureVerificationResult, Statement,
72	StatementAllowance, StatementEvent, SubmitResult, Topic,
73};
74pub use sp_statement_store::{Error, StatementStore, MAX_TOPICS};
75use std::{
76	collections::{BTreeMap, BTreeSet, HashMap, HashSet},
77	sync::Arc,
78	time::{Duration, Instant},
79};
80pub use subscription::StatementStoreSubscriptionApi;
81
82const KEY_VERSION: &[u8] = b"version".as_slice();
83const CURRENT_VERSION: u32 = 1;
84
85const LOG_TARGET: &str = "statement-store";
86
87/// The amount of time an expired statement is kept before it is removed from the store entirely.
88pub const DEFAULT_PURGE_AFTER_SEC: u64 = 2 * 24 * 60 * 60; // 48h
89/// The maximum number of statements the statement store can hold.
90pub const DEFAULT_MAX_TOTAL_STATEMENTS: usize = 4 * 1024 * 1024; // ~4 million
91/// The maximum amount of data the statement store can hold, regardless of the number of
92/// statements from which the data originates.
93pub const DEFAULT_MAX_TOTAL_SIZE: usize = 2 * 1024 * 1024 * 1024; // 2GiB
94/// The maximum size of a single statement in bytes.
95/// Accounts for the 1-byte vector length prefix when statements are gossiped as `Vec<Statement>`.
96pub const MAX_STATEMENT_SIZE: usize =
97	sc_network_statement::config::MAX_STATEMENT_NOTIFICATION_SIZE as usize - 1;
98
99/// Maximum number of statements to expire in a single iteration.
100const MAX_EXPIRY_STATEMENTS_PER_ITERATION: usize = 10_000;
101/// Maximum number of accounts to check for expiry in a single iteration.
102const MAX_EXPIRY_ACCOUNTS_PER_ITERATION: usize = 10_000;
103/// Maximum time in milliseconds to spend checking for expiry in a single iteration.
104const MAX_EXPIRY_TIME_PER_ITERATION: Duration = Duration::from_millis(100);
105
106/// Number of subscription filter worker tasks.
107const NUM_FILTER_WORKERS: usize = 1;
108
109const MAINTENANCE_PERIOD: std::time::Duration = std::time::Duration::from_secs(29);
110
111/// Specifies which block hash to use when reading statement allowances.
112enum AllowanceBlock {
113	/// Use a specific block hash.
114	Block(BlockHash),
115	/// Use the best (latest) block hash.
116	Best,
117	/// Use the finalized block hash.
118	Finalized,
119}
120
121// Period between enforcing limits (checking for expired statements and making sure statements stay
122// within allowances). Different from maintenance period to avoid keeping the lock for too long for
123// maintenance tasks.
124const ENFORCE_LIMITS_PERIOD: std::time::Duration = std::time::Duration::from_secs(31);
125
126mod col {
127	pub const META: u8 = 0;
128	pub const STATEMENTS: u8 = 1;
129	pub const EXPIRED: u8 = 2;
130
131	pub const COUNT: u8 = 3;
132}
133
134#[derive(Eq, PartialEq, Debug, Ord, PartialOrd, Clone, Copy)]
135struct Expiry(u64);
136
137impl Expiry {
138	/// Returns the expiration timestamp in seconds
139	fn get_expiration_timestamp_secs(self) -> u64 {
140		self.0 >> 32
141	}
142}
143
144#[derive(PartialEq, Eq)]
145struct PriorityKey {
146	hash: Hash,
147	expiry: Expiry,
148}
149
150impl PartialOrd for PriorityKey {
151	fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
152		Some(self.cmp(other))
153	}
154}
155
156impl Ord for PriorityKey {
157	fn cmp(&self, other: &Self) -> std::cmp::Ordering {
158		self.expiry.cmp(&other.expiry).then_with(|| self.hash.cmp(&other.hash))
159	}
160}
161
162#[derive(PartialEq, Eq)]
163struct ChannelEntry {
164	hash: Hash,
165	expiry: Expiry,
166}
167
168#[derive(Default)]
169struct StatementsForAccount {
170	// Statements ordered by priority.
171	by_priority: BTreeMap<PriorityKey, (Option<Channel>, usize)>,
172	// Channel to statement map. Only one statement per channel is allowed.
173	channels: HashMap<Channel, ChannelEntry>,
174	// Sum of all `Data` field sizes.
175	data_size: usize,
176}
177
178impl StatementsForAccount {
179	/// Returns an iterator over statements that have expired by `current_time`.
180	fn expired_by_iter(
181		&self,
182		current_time: u64,
183	) -> impl Iterator<Item = (&PriorityKey, &(Option<Channel>, usize))> {
184		let range = PriorityKey { hash: Hash::default(), expiry: Expiry(0) }..PriorityKey {
185			hash: Hash::default(),
186			expiry: Expiry(current_time << 32),
187		};
188		self.by_priority.range(range)
189	}
190}
191
192/// Default number of concurrent workers for statement validation.
193pub const DEFAULT_NETWORK_WORKERS: usize = 1;
194
195/// Default maximum statements per second per peer before rate limiting kicks in.
196pub use sc_network_statement::config::DEFAULT_STATEMENTS_PER_SECOND as DEFAULT_RATE_LIMIT;
197
198/// Statement store and network handler configuration.
199#[derive(Debug, Clone, Copy)]
200pub struct Config {
201	/// Maximum statements allowed in the store. Once this limit is reached lower-priority
202	/// statements may be evicted.
203	pub max_total_statements: usize,
204	/// Maximum total data size allowed in the store. Once this limit is reached lower-priority
205	/// statements may be evicted.
206	pub max_total_size: usize,
207	/// Number of seconds for which removed statements won't be allowed to be added back in.
208	pub purge_after_sec: u64,
209	/// Number of concurrent workers for statement validation from the network.
210	pub network_workers: usize,
211	/// Maximum statements per second per peer before rate limiting kicks in.
212	pub rate_limit: u32,
213}
214
215impl Config {
216	/// Validate the configuration, returning an error if any values are invalid.
217	pub fn validate(&self) -> Result<()> {
218		if self.max_total_statements == 0 {
219			return Err(Error::InvalidConfig(
220				"max_total_statements must be greater than zero".into(),
221			));
222		}
223		if self.max_total_size == 0 {
224			return Err(Error::InvalidConfig("max_total_size must be greater than zero".into()));
225		}
226		if self.network_workers == 0 {
227			return Err(Error::InvalidConfig("network_workers must be greater than zero".into()));
228		}
229		Ok(())
230	}
231}
232
233impl Default for Config {
234	fn default() -> Self {
235		Config {
236			max_total_statements: DEFAULT_MAX_TOTAL_STATEMENTS,
237			max_total_size: DEFAULT_MAX_TOTAL_SIZE,
238			purge_after_sec: DEFAULT_PURGE_AFTER_SEC,
239			network_workers: DEFAULT_NETWORK_WORKERS,
240			rate_limit: DEFAULT_RATE_LIMIT,
241		}
242	}
243}
244
245/// Tracks evicted statement hashes to suppress re-gossip until their purge deadline elapses
246#[derive(Default)]
247struct EvictedIndex {
248	hashes: HashSet<Hash>,
249	queue: BTreeSet<(u64, Hash)>,
250	pending_cleanup: Vec<Hash>,
251}
252
253impl EvictedIndex {
254	fn insert(&mut self, hash: Hash, purge_at: u64) {
255		if self.hashes.len() >= DEFAULT_MAX_TOTAL_STATEMENTS {
256			if let Some(&key) = self.queue.iter().next() {
257				self.queue.remove(&key);
258				self.hashes.remove(&key.1);
259				self.pending_cleanup.push(key.1);
260			}
261		}
262		self.hashes.insert(hash);
263		self.queue.insert((purge_at, hash));
264	}
265
266	fn contains(&self, hash: &Hash) -> bool {
267		self.hashes.contains(hash)
268	}
269
270	fn len(&self) -> usize {
271		self.hashes.len()
272	}
273
274	/// Removes and returns all hashes whose purge deadline is at or before `current_time`,
275	/// plus any hashes displaced by the capacity cap since the last call.
276	fn drain_due(&mut self, current_time: u64) -> Vec<Hash> {
277		let cutoff = (current_time.saturating_add(1), Hash::default());
278		let to_keep = self.queue.split_off(&cutoff);
279		let due = std::mem::replace(&mut self.queue, to_keep);
280		let mut result: Vec<Hash> = std::mem::take(&mut self.pending_cleanup);
281		result.extend(due.into_iter().map(|(_, hash)| {
282			self.hashes.remove(&hash);
283			hash
284		}));
285		result
286	}
287}
288
289#[derive(Default)]
290struct Index {
291	recent: HashSet<Hash>,
292	by_topic: HashMap<Topic, HashSet<Hash>>,
293	by_dec_key: HashMap<Option<DecryptionKey>, HashSet<Hash>>,
294	topics_and_keys: HashMap<Hash, ([Option<Topic>; MAX_TOPICS], Option<DecryptionKey>)>,
295	entries: HashMap<Hash, (AccountId, Expiry, usize)>,
296	evicted: EvictedIndex,
297	accounts: HashMap<AccountId, StatementsForAccount>,
298	accounts_to_check_for_expiry_stmts: Vec<AccountId>,
299	config: Config,
300	total_size: usize,
301}
302
303struct ClientWrapper<Block, Client, BE> {
304	client: Arc<Client>,
305	_block: std::marker::PhantomData<Block>,
306	_backend: std::marker::PhantomData<BE>,
307}
308
309impl<Block, Client, BE> ClientWrapper<Block, Client, BE>
310where
311	Block: BlockT,
312	Block::Hash: From<BlockHash>,
313	BE: Backend<Block> + 'static,
314	Client: HeaderBackend<Block> + StorageProvider<Block, BE> + Send + Sync + 'static,
315{
316	fn read_allowance(
317		&self,
318		account_id: &AccountId,
319		allowance_block: AllowanceBlock,
320	) -> Result<Option<StatementAllowance>> {
321		use sp_statement_store::{statement_allowance_key, StatementAllowance};
322
323		let block_hash = match allowance_block {
324			AllowanceBlock::Block(hash) => hash.into(),
325			AllowanceBlock::Best => self.client.info().best_hash,
326			AllowanceBlock::Finalized => self.client.info().finalized_hash,
327		};
328		let key = statement_allowance_key(account_id);
329		let storage_key = StorageKey(key);
330		self.client
331			.storage(block_hash, &storage_key)
332			.map_err(|e| Error::Storage(format!("Failed to read allowance: {:?}", e)))?
333			.map(|value| {
334				StatementAllowance::decode(&mut &value.0[..])
335					.map_err(|e| Error::Decode(format!("Failed to decode allowance: {:?}", e)))
336			})
337			.transpose()
338	}
339}
340
341/// Statement store.
342pub struct Store {
343	db: parity_db::Db,
344	index: RwLock<Index>,
345	read_allowance_fn:
346		Box<dyn Fn(&AccountId, AllowanceBlock) -> Result<Option<StatementAllowance>> + Send + Sync>,
347	subscription_manager: SubscriptionsHandle,
348	keystore: Arc<LocalKeystore>,
349	// Used for testing
350	time_override: Option<u64>,
351	metrics: PrometheusMetrics,
352}
353
354enum IndexQuery {
355	Unknown,
356	Exists,
357	Expired,
358}
359
360impl Index {
361	fn new(config: Config) -> Index {
362		Index { config, ..Default::default() }
363	}
364
365	fn insert_new(
366		&mut self,
367		hash: Hash,
368		account: AccountId,
369		statement: &Statement,
370		is_recent: bool,
371	) {
372		let mut all_topics = [None; MAX_TOPICS];
373		let mut nt = 0;
374		while let Some(t) = statement.topic(nt) {
375			self.by_topic.entry(t).or_default().insert(hash);
376			all_topics[nt] = Some(t);
377			nt += 1;
378		}
379		let key = statement.decryption_key();
380		self.by_dec_key.entry(key).or_default().insert(hash);
381		if nt > 0 || key.is_some() {
382			self.topics_and_keys.insert(hash, (all_topics, key));
383		}
384		let expiry = Expiry(statement.expiry());
385		self.entries.insert(hash, (account, expiry, statement.data_len()));
386		if is_recent {
387			self.recent.insert(hash);
388		}
389		self.total_size += statement.data_len();
390		let account_info = self.accounts.entry(account).or_default();
391		account_info.data_size += statement.data_len();
392		if let Some(channel) = statement.channel() {
393			account_info.channels.insert(channel, ChannelEntry { hash, expiry });
394		}
395		account_info
396			.by_priority
397			.insert(PriorityKey { hash, expiry }, (statement.channel(), statement.data_len()));
398	}
399
400	fn query(&self, hash: &Hash) -> IndexQuery {
401		if self.entries.contains_key(hash) {
402			return IndexQuery::Exists;
403		}
404		if self.evicted.contains(hash) {
405			return IndexQuery::Expired;
406		}
407		IndexQuery::Unknown
408	}
409
410	fn insert_expired(&mut self, hash: Hash, timestamp: u64) {
411		let purge_at = timestamp.saturating_add(self.config.purge_after_sec);
412		self.evicted.insert(hash, purge_at);
413	}
414
415	fn iterate_with(
416		&self,
417		key: Option<DecryptionKey>,
418		topic: &OptimizedTopicFilter,
419		f: impl FnMut(&Hash) -> Result<()>,
420	) -> Result<()> {
421		match topic {
422			OptimizedTopicFilter::Any => self.iterate_with_any(key, f),
423			OptimizedTopicFilter::MatchAll(topics) => {
424				self.iterate_with_match_all(key, topics.iter(), f)
425			},
426			OptimizedTopicFilter::MatchAny(topics) => {
427				self.iterate_with_match_any(key, topics.iter(), f)
428			},
429		}
430	}
431
432	fn iterate_with_match_any<'a>(
433		&self,
434		key: Option<DecryptionKey>,
435		match_any_topics: impl ExactSizeIterator<Item = &'a Topic>,
436		mut f: impl FnMut(&Hash) -> Result<()>,
437	) -> Result<()> {
438		let Some(key_set) = self.by_dec_key.get(&key).filter(|k| !k.is_empty()) else {
439			return Ok(());
440		};
441
442		for t in match_any_topics {
443			let set = self.by_topic.get(t);
444
445			for item in set.iter().flat_map(|set| set.iter()) {
446				if key_set.contains(item) {
447					log::trace!(
448						target: LOG_TARGET,
449						"Iterating by topic/key: statement {:?}",
450						HexDisplay::from(item)
451					);
452					f(item)?
453				}
454			}
455		}
456		Ok(())
457	}
458
459	fn iterate_with_any(
460		&self,
461		key: Option<DecryptionKey>,
462		mut f: impl FnMut(&Hash) -> Result<()>,
463	) -> Result<()> {
464		let key_set = self.by_dec_key.get(&key);
465		if key_set.map_or(true, |s| s.is_empty()) {
466			// Key does not exist in the index.
467			return Ok(());
468		}
469
470		for item in key_set.map(|hashes| hashes.iter()).into_iter().flatten() {
471			f(item)?
472		}
473		Ok(())
474	}
475
476	fn iterate_with_match_all<'a>(
477		&self,
478		key: Option<DecryptionKey>,
479		match_all_topics: impl ExactSizeIterator<Item = &'a Topic>,
480		mut f: impl FnMut(&Hash) -> Result<()>,
481	) -> Result<()> {
482		let empty = HashSet::new();
483		let mut sets: [&HashSet<Hash>; MAX_TOPICS + 1] = [&empty; MAX_TOPICS + 1];
484		let num_topics = match_all_topics.len();
485		if num_topics > MAX_TOPICS {
486			return Ok(());
487		}
488		let key_set = self.by_dec_key.get(&key);
489		if key_set.map_or(true, |s| s.is_empty()) {
490			// Key does not exist in the index.
491			return Ok(());
492		}
493		sets[0] = key_set.expect("Function returns if key_set is None");
494		for (i, t) in match_all_topics.enumerate() {
495			let set = self.by_topic.get(t);
496			if set.map_or(0, |s| s.len()) == 0 {
497				// At least one of the match_all_topics does not exist in the index.
498				return Ok(());
499			}
500			sets[i + 1] = set.expect("Function returns if set is None");
501		}
502		let sets = &mut sets[0..num_topics + 1];
503		// Start with the smallest topic set or the key set.
504		sets.sort_by_key(|s| s.len());
505		for item in sets[0] {
506			if sets[1..].iter().all(|set| set.contains(item)) {
507				log::trace!(
508					target: LOG_TARGET,
509					"Iterating by topic/key: statement {:?}",
510					HexDisplay::from(item)
511				);
512				f(item)?
513			}
514		}
515		Ok(())
516	}
517
518	fn maintain(&mut self, current_time: u64) -> Vec<Hash> {
519		self.evicted.drain_due(current_time)
520	}
521
522	fn take_recent(&mut self) -> HashSet<Hash> {
523		std::mem::take(&mut self.recent)
524	}
525
526	fn make_expired(&mut self, hash: &Hash, current_time: u64) -> bool {
527		if let Some((account, expiry, len)) = self.entries.remove(hash) {
528			self.total_size -= len;
529			if let Some((topics, key)) = self.topics_and_keys.remove(hash) {
530				for t in topics.into_iter().flatten() {
531					if let std::collections::hash_map::Entry::Occupied(mut set) =
532						self.by_topic.entry(t)
533					{
534						set.get_mut().remove(hash);
535						if set.get().is_empty() {
536							set.remove_entry();
537						}
538					}
539				}
540				if let std::collections::hash_map::Entry::Occupied(mut set) =
541					self.by_dec_key.entry(key)
542				{
543					set.get_mut().remove(hash);
544					if set.get().is_empty() {
545						set.remove_entry();
546					}
547				}
548			}
549			let _ = self.recent.remove(hash);
550			if current_time < expiry.get_expiration_timestamp_secs() {
551				let purge_at = expiry
552					.get_expiration_timestamp_secs()
553					.min(current_time.saturating_add(self.config.purge_after_sec));
554				self.evicted.insert(*hash, purge_at);
555			}
556			if let std::collections::hash_map::Entry::Occupied(mut account_rec) =
557				self.accounts.entry(account)
558			{
559				let key = PriorityKey { hash: *hash, expiry };
560				if let Some((channel, len)) = account_rec.get_mut().by_priority.remove(&key) {
561					account_rec.get_mut().data_size -= len;
562					if let Some(channel) = channel {
563						account_rec.get_mut().channels.remove(&channel);
564					}
565				}
566				if account_rec.get().by_priority.is_empty() {
567					account_rec.remove_entry();
568				}
569			}
570			log::trace!(target: LOG_TARGET, "Expired statement {:?}", HexDisplay::from(hash));
571			true
572		} else {
573			false
574		}
575	}
576
577	fn insert(
578		&mut self,
579		hash: Hash,
580		statement: &Statement,
581		account: &AccountId,
582		validation: &StatementAllowance,
583		current_time: u64,
584	) -> std::result::Result<HashSet<Hash>, RejectionReason> {
585		let statement_len = statement.data_len();
586		if statement_len > validation.max_size as usize {
587			log::debug!(
588				target: LOG_TARGET,
589				"Ignored oversize message: {:?} ({} bytes)",
590				HexDisplay::from(&hash),
591				statement_len,
592			);
593			return Err(RejectionReason::DataTooLarge {
594				submitted_size: statement_len,
595				available_size: validation.max_size as usize,
596			});
597		}
598
599		let mut evicted = HashSet::new();
600		let mut would_free_size = 0;
601		let expiry = Expiry(statement.expiry());
602		let (max_size, max_count) = (validation.max_size as usize, validation.max_count as usize);
603		// It may happen that we can't delete enough lower priority messages
604		// to satisfy size constraints. We check for that before deleting anything,
605		// taking into account channel message replacement.
606		if let Some(account_rec) = self.accounts.get(account) {
607			if let Some(channel) = statement.channel() {
608				if let Some(channel_record) = account_rec.channels.get(&channel) {
609					if expiry <= channel_record.expiry {
610						// Trying to replace channel message with lower expiry.
611						log::debug!(
612							target: LOG_TARGET,
613							"Ignored lower priority channel message: {:?} {:?} <= {:?}",
614							HexDisplay::from(&hash),
615							expiry,
616							channel_record.expiry,
617						);
618						return Err(RejectionReason::ChannelPriorityTooLow {
619							submitted_expiry: expiry.0,
620							min_expiry: channel_record.expiry.0,
621						});
622					} else {
623						// Would replace channel message. Still need to check for size constraints
624						// below.
625						log::debug!(
626							target: LOG_TARGET,
627							"Replacing higher priority channel message: {:?} ({:?}) > {:?} ({:?})",
628							HexDisplay::from(&hash),
629							expiry,
630							HexDisplay::from(&channel_record.hash),
631							channel_record.expiry,
632						);
633						let key = PriorityKey {
634							hash: channel_record.hash,
635							expiry: channel_record.expiry,
636						};
637						if let Some((_channel, len)) = account_rec.by_priority.get(&key) {
638							would_free_size += *len;
639							evicted.insert(channel_record.hash);
640						}
641					}
642				}
643			}
644			// Check if we can evict enough lower priority statements to satisfy constraints
645			for (entry, (_, len)) in account_rec.by_priority.iter() {
646				if (account_rec.data_size - would_free_size + statement_len <= max_size) &&
647					account_rec.by_priority.len() + 1 - evicted.len() <= max_count
648				{
649					// Satisfied
650					break;
651				}
652				if evicted.contains(&entry.hash) {
653					// Already accounted for above
654					continue;
655				}
656				if entry.expiry >= expiry {
657					log::debug!(
658						target: LOG_TARGET,
659						"Ignored message due to constraints {:?} {:?} < {:?}",
660						HexDisplay::from(&hash),
661						expiry,
662						entry.expiry,
663					);
664					return Err(RejectionReason::AccountFull {
665						submitted_expiry: expiry.0,
666						min_expiry: entry.expiry.0,
667					});
668				}
669				evicted.insert(entry.hash);
670				would_free_size += len;
671			}
672		}
673		// Now check global constraints as well.
674		if !((self.total_size - would_free_size + statement_len <= self.config.max_total_size) &&
675			self.entries.len() + 1 - evicted.len() <= self.config.max_total_statements)
676		{
677			log::debug!(
678				target: LOG_TARGET,
679				"Ignored statement {} because the store is full (size={}, count={})",
680				HexDisplay::from(&hash),
681				self.total_size,
682				self.entries.len(),
683			);
684			return Err(RejectionReason::StoreFull);
685		}
686
687		for h in &evicted {
688			self.make_expired(h, current_time);
689		}
690		self.insert_new(hash, *account, statement, true);
691		Ok(evicted)
692	}
693}
694
695impl Store {
696	/// Create a new shared store instance. There should only be one per process.
697	/// `path` will be used to open a statement database or create a new one if it does not exist.
698	pub fn new_shared<Block, Client, BE>(
699		path: &std::path::Path,
700		config: Config,
701		client: Arc<Client>,
702		keystore: Arc<LocalKeystore>,
703		prometheus: Option<&PrometheusRegistry>,
704		task_spawner: Box<dyn SpawnNamed>,
705	) -> Result<Arc<Store>>
706	where
707		Block: BlockT,
708		Block::Hash: From<BlockHash>,
709		BE: Backend<Block> + 'static,
710		Client: HeaderBackend<Block> + StorageProvider<Block, BE> + Send + Sync + 'static,
711	{
712		let store =
713			Arc::new(Self::new(path, config, client, keystore, prometheus, task_spawner.clone())?);
714
715		// Perform periodic statement store maintenance
716		let worker_store = store.clone();
717		task_spawner.spawn(
718			"statement-store-maintenance",
719			Some("statement-store"),
720			Box::pin(async move {
721				let mut maintenance_interval = tokio::time::interval(MAINTENANCE_PERIOD);
722				let mut enforce_limits_interval = tokio::time::interval(ENFORCE_LIMITS_PERIOD);
723				loop {
724					futures::select! {
725						_ = maintenance_interval.tick().fuse() => {worker_store.maintain();}
726						_ = enforce_limits_interval.tick().fuse() => {worker_store.enforce_limits();}
727					}
728				}
729			}),
730		);
731
732		Ok(store)
733	}
734
735	/// Create a new instance.
736	/// `path` will be used to open a statement database or create a new one if it does not exist.
737	#[doc(hidden)]
738	pub fn new<Block, Client, BE>(
739		path: &std::path::Path,
740		config: Config,
741		client: Arc<Client>,
742		keystore: Arc<LocalKeystore>,
743		prometheus: Option<&PrometheusRegistry>,
744		task_spawner: Box<dyn SpawnNamed>,
745	) -> Result<Store>
746	where
747		Block: BlockT,
748		Block::Hash: From<BlockHash>,
749		BE: Backend<Block> + 'static,
750		Client: HeaderBackend<Block> + StorageProvider<Block, BE> + Send + Sync + 'static,
751	{
752		config.validate()?;
753
754		let mut path: std::path::PathBuf = path.into();
755		path.push("statements");
756
757		let mut db_config = parity_db::Options::with_columns(&path, col::COUNT);
758
759		let statement_col = &mut db_config.columns[col::STATEMENTS as usize];
760		statement_col.ref_counted = false;
761		statement_col.preimage = true;
762		statement_col.uniform = true;
763		let db = parity_db::Db::open_or_create(&db_config).map_err(|e| Error::Db(e.to_string()))?;
764		match db.get(col::META, &KEY_VERSION).map_err(|e| Error::Db(e.to_string()))? {
765			Some(version) => {
766				let version = u32::from_le_bytes(
767					version
768						.try_into()
769						.map_err(|_| Error::Db("Error reading database version".into()))?,
770				);
771				if version != CURRENT_VERSION {
772					return Err(Error::Db(format!("Unsupported database version: {version}")));
773				}
774			},
775			None => {
776				db.commit([(
777					col::META,
778					KEY_VERSION.to_vec(),
779					Some(CURRENT_VERSION.to_le_bytes().to_vec()),
780				)])
781				.map_err(|e| Error::Db(e.to_string()))?;
782			},
783		}
784
785		let storage_reader =
786			ClientWrapper { client, _block: Default::default(), _backend: Default::default() };
787		let read_allowance_fn =
788			Box::new(move |account_id: &AccountId, allowance_block: AllowanceBlock| {
789				storage_reader.read_allowance(account_id, allowance_block)
790			});
791
792		let store = Store {
793			db,
794			index: RwLock::new(Index::new(config)),
795			read_allowance_fn,
796			keystore,
797			time_override: None,
798			metrics: PrometheusMetrics::new(prometheus),
799			subscription_manager: SubscriptionsHandle::new(
800				task_spawner.clone(),
801				NUM_FILTER_WORKERS,
802			),
803		};
804		store.populate()?;
805		Ok(store)
806	}
807
808	/// Create memory index from the data.
809	// This may be moved to a background thread if it slows startup too much.
810	// This function should only be used on startup. There should be no other DB operations when
811	// iterating the index.
812	fn populate(&self) -> Result<()> {
813		{
814			let mut index = self.index.write();
815			self.db
816				.iter_column_while(col::STATEMENTS, |item| {
817					let statement = item.value;
818					if let Ok(statement) = Statement::decode(&mut statement.as_slice()) {
819						let hash = statement.hash();
820						log::trace!(
821							target: LOG_TARGET,
822							"Statement loaded {:?}",
823							HexDisplay::from(&hash)
824						);
825						if let Some(account_id) = statement.account_id() {
826							index.insert_new(hash, account_id, &statement, false);
827						} else {
828							log::debug!(
829								target: LOG_TARGET,
830								"Error decoding statement loaded from the DB: {:?}",
831								HexDisplay::from(&hash)
832							);
833						}
834					}
835					true
836				})
837				.map_err(|e| Error::Db(e.to_string()))?;
838			self.db
839				.iter_column_while(col::EXPIRED, |item| {
840					let expired_info = item.value;
841					if let Ok((hash, timestamp)) =
842						<(Hash, u64)>::decode(&mut expired_info.as_slice())
843					{
844						log::trace!(
845							target: LOG_TARGET,
846							"Statement loaded (expired): {:?}",
847							HexDisplay::from(&hash)
848						);
849						index.insert_expired(hash, timestamp);
850					}
851					true
852				})
853				.map_err(|e| Error::Db(e.to_string()))?;
854		}
855
856		self.maintain();
857		Ok(())
858	}
859
860	fn collect_statements_locked<R>(
861		&self,
862		key: Option<DecryptionKey>,
863		topic_filter: &OptimizedTopicFilter,
864		index: &Index,
865		result: &mut Vec<R>,
866		mut f: impl FnMut(Statement) -> Option<R>,
867	) -> Result<()> {
868		index.iterate_with(key, topic_filter, |hash| {
869			match self.db.get(col::STATEMENTS, hash).map_err(|e| Error::Db(e.to_string()))? {
870				Some(entry) => {
871					if let Ok(statement) = Statement::decode(&mut entry.as_slice()) {
872						if let Some(data) = f(statement) {
873							result.push(data);
874						}
875					} else {
876						// DB inconsistency
877						log::warn!(
878							target: LOG_TARGET,
879							"Corrupt statement {:?}",
880							HexDisplay::from(hash)
881						);
882					}
883				},
884				None => {
885					// DB inconsistency
886					log::warn!(
887						target: LOG_TARGET,
888						"Missing statement {:?}",
889						HexDisplay::from(hash)
890					);
891				},
892			}
893			Ok(())
894		})?;
895		Ok(())
896	}
897
898	fn collect_statements<R>(
899		&self,
900		key: Option<DecryptionKey>,
901		topic_filter: &OptimizedTopicFilter,
902		f: impl FnMut(Statement) -> Option<R>,
903	) -> Result<Vec<R>> {
904		let mut result = Vec::new();
905		let index = self.index.read();
906		self.collect_statements_locked(key, topic_filter, &index, &mut result, f)?;
907		Ok(result)
908	}
909
910	// Collects expired and over-allowance statement hashes for a single account.
911	fn collect_evictions(
912		&self,
913		account: &AccountId,
914		account_rec: &StatementsForAccount,
915		current_time: u64,
916	) -> Vec<Hash> {
917		let mut to_evict = Vec::new();
918		let mut expired_count = 0usize;
919		let mut expired_size = 0usize;
920		for (key, (_, len)) in account_rec.expired_by_iter(current_time) {
921			to_evict.push(key.hash);
922			expired_count += 1;
923			expired_size += len;
924		}
925
926		// Enforce allowances for remaining (non-expired) statements, we use the finalized block to
927		// make sure we enforce allowances based on the correct chain state.
928		let allowance = match (self.read_allowance_fn)(account, AllowanceBlock::Finalized) {
929			Ok(Some(allowance)) => allowance,
930			Ok(None) => {
931				log::debug!(
932					target: LOG_TARGET,
933					"No allowance found for account {:?}, treating as zero allowance",
934					HexDisplay::from(account)
935				);
936				StatementAllowance { max_count: 0, max_size: 0 }
937			},
938			Err(e) => {
939				log::error!(target: LOG_TARGET, "Error reading allowance: {:?}", e);
940				// Skip allowance enforcement for this account on error
941				return to_evict;
942			},
943		};
944
945		// Calculate remaining count and size after expiring statements
946		let mut remaining_count = account_rec.by_priority.len() - expired_count;
947		let mut remaining_size = account_rec.data_size - expired_size;
948
949		// Evict lowest priority statements that exceed allowance
950		if remaining_count > allowance.max_count as usize ||
951			remaining_size > allowance.max_size as usize
952		{
953			log::debug!(
954				target: LOG_TARGET,
955				"Account {:?} exceeds allowance: count={}/{}, size={}/{}",
956				HexDisplay::from(account),
957				remaining_count,
958				allowance.max_count,
959				remaining_size,
960				allowance.max_size
961			);
962
963			// Skip expired statements (they're at the beginning due to BTreeMap ordering)
964			for (key, (_, len)) in account_rec.by_priority.iter().skip(expired_count) {
965				if remaining_count <= allowance.max_count as usize &&
966					remaining_size <= allowance.max_size as usize
967				{
968					break;
969				}
970				to_evict.push(key.hash);
971				remaining_count -= 1;
972				remaining_size -= len;
973				log::debug!(
974					target: LOG_TARGET,
975					"Evicting statement {:?} due to allowance enforcement",
976					HexDisplay::from(&key.hash)
977				);
978			}
979		}
980
981		to_evict
982	}
983
984	// Checks for expired statements and enforces allowances, marking violating statements
985	// as expired in the index.
986	//
987	// This function performs incremental checking to avoid blocking the store for too long.
988	// It processes accounts in batches and stops when any of these limits are reached:
989	// - `MAX_EXPIRY_STATEMENTS_PER_ITERATION` statements found to expire/evict
990	// - `MAX_EXPIRY_ACCOUNTS_PER_ITERATION` accounts checked
991	// - `MAX_EXPIRY_TIME_MS_PER_ITERATION` milliseconds elapsed
992	//
993	// The function maintains a list of accounts to check (`accounts_to_check_for_expiry_stmts`).
994	// When this list is empty, it repopulates it with all current accounts and returns early,
995	// deferring the actual check to the next call. This ensures the process eventually covers
996	// all accounts across multiple invocations.
997	//
998	// Statements are considered expired when their priority (which encodes the expiration
999	// timestamp in the upper 32 bits) is less than the current timestamp.
1000	fn enforce_limits(&self) {
1001		let _start_check_expiration_timer = self.metrics.start_check_expiration_timer();
1002		let current_time = self.timestamp();
1003
1004		let (to_evict, num_accounts_checked) = {
1005			let index = self.index.upgradable_read();
1006			if index.accounts_to_check_for_expiry_stmts.is_empty() {
1007				let existing_accounts = index.accounts.keys().cloned().collect::<Vec<_>>();
1008				let mut index = RwLockUpgradableReadGuard::upgrade(index);
1009				index.accounts_to_check_for_expiry_stmts = existing_accounts;
1010				return;
1011			}
1012
1013			let mut to_evict = Vec::new();
1014			let mut num_accounts_checked = 0;
1015			let start = Instant::now();
1016
1017			for account in index.accounts_to_check_for_expiry_stmts.iter().rev() {
1018				num_accounts_checked += 1;
1019				if let Some(account_rec) = index.accounts.get(account) {
1020					to_evict.extend(self.collect_evictions(account, account_rec, current_time));
1021				}
1022
1023				if to_evict.len() >= MAX_EXPIRY_STATEMENTS_PER_ITERATION ||
1024					num_accounts_checked >= MAX_EXPIRY_ACCOUNTS_PER_ITERATION ||
1025					start.elapsed() >= MAX_EXPIRY_TIME_PER_ITERATION
1026				{
1027					break;
1028				}
1029			}
1030
1031			(to_evict, num_accounts_checked)
1032		};
1033
1034		let mut expired = 0;
1035
1036		for hash in to_evict {
1037			if let Err(e) = self.remove(&hash) {
1038				log::debug!(
1039					target: LOG_TARGET,
1040					"Error marking statement {:?} as expired: {:?}",
1041					HexDisplay::from(&hash),
1042					e
1043				);
1044			} else {
1045				expired += 1;
1046				log::trace!(
1047					target: LOG_TARGET,
1048					"Marked statement {:?} as expired",
1049					HexDisplay::from(&hash)
1050				);
1051			}
1052		}
1053
1054		let mut index = self.index.write();
1055		let new_len = index
1056			.accounts_to_check_for_expiry_stmts
1057			.len()
1058			.saturating_sub(num_accounts_checked);
1059		index.accounts_to_check_for_expiry_stmts.truncate(new_len);
1060
1061		drop(_start_check_expiration_timer);
1062
1063		self.metrics.report(|metrics| {
1064			metrics.statements_expired_total.inc_by(expired);
1065		});
1066	}
1067
1068	/// Perform periodic store maintenance
1069	pub fn maintain(&self) {
1070		log::trace!(target: LOG_TARGET, "Started store maintenance");
1071		let (
1072			deleted,
1073			active_count,
1074			expired_count,
1075			total_size,
1076			accounts_count,
1077			capacity_statements,
1078			capacity_bytes,
1079		): (Vec<_>, usize, usize, usize, usize, usize, usize) = {
1080			let mut index = self.index.write();
1081			let deleted = index.maintain(self.timestamp());
1082			(
1083				deleted,
1084				index.entries.len(),
1085				index.evicted.len(),
1086				index.total_size,
1087				index.accounts.len(),
1088				index.config.max_total_statements,
1089				index.config.max_total_size,
1090			)
1091		};
1092		let deleted: Vec<_> =
1093			deleted.into_iter().map(|hash| (col::EXPIRED, hash.to_vec(), None)).collect();
1094		let deleted_count = deleted.len() as u64;
1095		if let Err(e) = self.db.commit(deleted) {
1096			log::warn!(target: LOG_TARGET, "Error writing to the statement database: {:?}", e);
1097		} else {
1098			self.metrics.report(|metrics| metrics.statements_pruned.inc_by(deleted_count));
1099		}
1100
1101		self.metrics.report(|metrics| {
1102			metrics.statements_total.set(active_count as u64);
1103			metrics.bytes_total.set(total_size as u64);
1104			metrics.accounts_total.set(accounts_count as u64);
1105			metrics.expired_total.set(expired_count as u64);
1106			metrics.capacity_statements.set(capacity_statements as u64);
1107			metrics.capacity_bytes.set(capacity_bytes as u64);
1108		});
1109
1110		log::trace!(
1111			target: LOG_TARGET,
1112			"Completed store maintenance. Purged: {}, Active: {}, Expired: {}",
1113			deleted_count,
1114			active_count,
1115			expired_count
1116		);
1117	}
1118
1119	fn timestamp(&self) -> u64 {
1120		self.time_override.unwrap_or_else(|| {
1121			std::time::SystemTime::now()
1122				.duration_since(std::time::UNIX_EPOCH)
1123				.unwrap_or_default()
1124				.as_secs()
1125		})
1126	}
1127
1128	#[cfg(test)]
1129	fn set_time(&mut self, time: u64) {
1130		self.time_override = Some(time);
1131	}
1132
1133	/// Returns `self` as [`StatementStoreExt`].
1134	pub fn as_statement_store_ext(self: Arc<Self>) -> StatementStoreExt {
1135		StatementStoreExt::new(self)
1136	}
1137
1138	/// Return information of all known statements whose decryption key is identified as
1139	/// `dest`. The key must be available to the client.
1140	fn posted_clear_inner<R>(
1141		&self,
1142		match_all_topics: &[Topic],
1143		dest: [u8; 32],
1144		// Map the statement and the decrypted data to the desired result.
1145		mut map_f: impl FnMut(Statement, Vec<u8>) -> R,
1146	) -> Result<Vec<R>> {
1147		self.collect_statements(
1148			Some(dest),
1149			&OptimizedTopicFilter::MatchAll(match_all_topics.iter().cloned().collect()),
1150			|statement| {
1151				if let (Some(key), Some(_)) = (statement.decryption_key(), statement.data()) {
1152					let public: sp_core::ed25519::Public = UncheckedFrom::unchecked_from(key);
1153					let public: sp_statement_store::ed25519::Public = public.into();
1154					match self.keystore.key_pair::<sp_statement_store::ed25519::Pair>(&public) {
1155						Err(e) => {
1156							log::debug!(
1157								target: LOG_TARGET,
1158								"Keystore error: {:?}, for statement {:?}",
1159								e,
1160								HexDisplay::from(&statement.hash())
1161							);
1162							None
1163						},
1164						Ok(None) => {
1165							log::debug!(
1166								target: LOG_TARGET,
1167								"Keystore is missing key for statement {:?}",
1168								HexDisplay::from(&statement.hash())
1169							);
1170							None
1171						},
1172						Ok(Some(pair)) => match statement.decrypt_private(&pair.into_inner()) {
1173							Ok(r) => r.map(|data| map_f(statement, data)),
1174							Err(e) => {
1175								log::debug!(
1176									target: LOG_TARGET,
1177									"Decryption error: {:?}, for statement {:?}",
1178									e,
1179									HexDisplay::from(&statement.hash())
1180								);
1181								None
1182							},
1183						},
1184					}
1185				} else {
1186					None
1187				}
1188			},
1189		)
1190	}
1191}
1192
1193impl StatementStore for Store {
1194	/// Return all statements.
1195	fn statements(&self) -> Result<Vec<(Hash, Statement)>> {
1196		let index = self.index.read();
1197		let mut result = Vec::with_capacity(index.entries.len());
1198		for hash in index.entries.keys().cloned() {
1199			let Some(encoded) =
1200				self.db.get(col::STATEMENTS, &hash).map_err(|e| Error::Db(e.to_string()))?
1201			else {
1202				continue;
1203			};
1204			if let Ok(statement) = Statement::decode(&mut encoded.as_slice()) {
1205				result.push((hash, statement));
1206			}
1207		}
1208		Ok(result)
1209	}
1210
1211	fn take_recent_statements(&self) -> Result<Vec<(Hash, Statement)>> {
1212		let mut index = self.index.write();
1213		let recent = index.take_recent();
1214		let mut result = Vec::with_capacity(recent.len());
1215		for hash in recent {
1216			let Some(encoded) =
1217				self.db.get(col::STATEMENTS, &hash).map_err(|e| Error::Db(e.to_string()))?
1218			else {
1219				continue;
1220			};
1221			if let Ok(statement) = Statement::decode(&mut encoded.as_slice()) {
1222				result.push((hash, statement));
1223			}
1224		}
1225		Ok(result)
1226	}
1227
1228	/// Returns a statement by hash.
1229	fn statement(&self, hash: &Hash) -> Result<Option<Statement>> {
1230		Ok(
1231			match self
1232				.db
1233				.get(col::STATEMENTS, hash.as_slice())
1234				.map_err(|e| Error::Db(e.to_string()))?
1235			{
1236				Some(entry) => {
1237					log::trace!(
1238						target: LOG_TARGET,
1239						"Queried statement {:?}",
1240						HexDisplay::from(hash)
1241					);
1242					Some(
1243						Statement::decode(&mut entry.as_slice())
1244							.map_err(|e| Error::Decode(e.to_string()))?,
1245					)
1246				},
1247				None => {
1248					log::trace!(
1249						target: LOG_TARGET,
1250						"Queried missing statement {:?}",
1251						HexDisplay::from(hash)
1252					);
1253					None
1254				},
1255			},
1256		)
1257	}
1258
1259	fn has_statement(&self, hash: &Hash) -> bool {
1260		self.index.read().entries.contains_key(hash)
1261	}
1262
1263	fn statement_hashes(&self) -> Vec<Hash> {
1264		self.index.read().entries.keys().cloned().collect()
1265	}
1266
1267	fn statements_by_hashes(
1268		&self,
1269		hashes: &[Hash],
1270		filter: &mut dyn FnMut(&Hash, &[u8], &Statement) -> FilterDecision,
1271	) -> Result<(Vec<(Hash, Statement)>, usize)> {
1272		let mut result = Vec::new();
1273		let mut processed = 0;
1274		for hash in hashes {
1275			processed += 1;
1276			let Some(encoded) =
1277				self.db.get(col::STATEMENTS, hash).map_err(|e| Error::Db(e.to_string()))?
1278			else {
1279				continue;
1280			};
1281			let Ok(statement) = Statement::decode(&mut encoded.as_slice()) else { continue };
1282			match filter(hash, &encoded, &statement) {
1283				FilterDecision::Skip => {},
1284				FilterDecision::Take => {
1285					result.push((*hash, statement));
1286				},
1287				FilterDecision::Abort => {
1288					// We did not process it :)
1289					processed -= 1;
1290					break;
1291				},
1292			}
1293		}
1294
1295		Ok((result, processed))
1296	}
1297
1298	/// Return the data of all known statements which include all topics and have no `DecryptionKey`
1299	/// field.
1300	fn broadcasts(&self, match_all_topics: &[Topic]) -> Result<Vec<Vec<u8>>> {
1301		self.collect_statements(
1302			None,
1303			&OptimizedTopicFilter::MatchAll(match_all_topics.iter().cloned().collect()),
1304			|statement| statement.into_data(),
1305		)
1306	}
1307
1308	/// Return the data of all known statements whose decryption key is identified as `dest` (this
1309	/// will generally be the public key or a hash thereof for symmetric ciphers, or a hash of the
1310	/// private key for symmetric ciphers).
1311	fn posted(&self, match_all_topics: &[Topic], dest: [u8; 32]) -> Result<Vec<Vec<u8>>> {
1312		self.collect_statements(
1313			Some(dest),
1314			&OptimizedTopicFilter::MatchAll(match_all_topics.iter().cloned().collect()),
1315			|statement| statement.into_data(),
1316		)
1317	}
1318
1319	/// Return the decrypted data of all known statements whose decryption key is identified as
1320	/// `dest`. The key must be available to the client.
1321	fn posted_clear(&self, match_all_topics: &[Topic], dest: [u8; 32]) -> Result<Vec<Vec<u8>>> {
1322		self.posted_clear_inner(match_all_topics, dest, |_statement, data| data)
1323	}
1324
1325	/// Return all known statements which include all topics and have no `DecryptionKey`
1326	/// field.
1327	fn broadcasts_stmt(&self, match_all_topics: &[Topic]) -> Result<Vec<Vec<u8>>> {
1328		self.collect_statements(
1329			None,
1330			&OptimizedTopicFilter::MatchAll(match_all_topics.iter().cloned().collect()),
1331			|statement| Some(statement.encode()),
1332		)
1333	}
1334
1335	/// Return all known statements whose decryption key is identified as `dest` (this
1336	/// will generally be the public key or a hash thereof for symmetric ciphers, or a hash of the
1337	/// private key for symmetric ciphers).
1338	fn posted_stmt(&self, match_all_topics: &[Topic], dest: [u8; 32]) -> Result<Vec<Vec<u8>>> {
1339		self.collect_statements(
1340			Some(dest),
1341			&OptimizedTopicFilter::MatchAll(match_all_topics.iter().cloned().collect()),
1342			|statement| Some(statement.encode()),
1343		)
1344	}
1345
1346	/// Return the statement and the decrypted data of all known statements whose decryption key is
1347	/// identified as `dest`. The key must be available to the client.
1348	fn posted_clear_stmt(
1349		&self,
1350		match_all_topics: &[Topic],
1351		dest: [u8; 32],
1352	) -> Result<Vec<Vec<u8>>> {
1353		self.posted_clear_inner(match_all_topics, dest, |statement, data| {
1354			let mut res = Vec::with_capacity(statement.size_hint() + data.len());
1355			statement.encode_to(&mut res);
1356			res.extend_from_slice(&data);
1357			res
1358		})
1359	}
1360
1361	/// Submit a statement to the store. Validates the statement and returns validation result.
1362	fn submit(&self, statement: Statement, source: StatementSource) -> SubmitResult {
1363		let _histogram_submit_start_timer = self.metrics.start_submit_timer();
1364		let hash = statement.hash();
1365		// Get unix timestamp
1366		if self.timestamp() >= statement.get_expiration_timestamp_secs().into() {
1367			log::debug!(
1368				target: LOG_TARGET,
1369				"Statement is already expired: {:?}",
1370				HexDisplay::from(&hash),
1371			);
1372			let reason = InvalidReason::AlreadyExpired;
1373			self.metrics.report(|metrics| {
1374				metrics.validations_invalid.with_label_values(&[reason.label()]).inc();
1375			});
1376			return SubmitResult::Invalid(reason);
1377		}
1378		let encoded_size = statement.encoded_size();
1379		if encoded_size > MAX_STATEMENT_SIZE {
1380			log::debug!(
1381				target: LOG_TARGET,
1382				"Statement is too big for propogation: {:?} ({}/{} bytes)",
1383				HexDisplay::from(&hash),
1384				statement.encoded_size(),
1385				MAX_STATEMENT_SIZE
1386			);
1387			let reason = InvalidReason::EncodingTooLarge {
1388				submitted_size: encoded_size,
1389				max_size: MAX_STATEMENT_SIZE,
1390			};
1391			self.metrics.report(|metrics| {
1392				metrics.validations_invalid.with_label_values(&[reason.label()]).inc();
1393			});
1394			return SubmitResult::Invalid(reason);
1395		}
1396
1397		match self.index.read().query(&hash) {
1398			IndexQuery::Expired => {
1399				if !source.can_be_resubmitted() {
1400					self.metrics.report(|metrics| {
1401						metrics.known_statements.with_label_values(&["known_expired"]).inc();
1402					});
1403					return SubmitResult::KnownExpired;
1404				}
1405			},
1406			IndexQuery::Exists => {
1407				if !source.can_be_resubmitted() {
1408					self.metrics.report(|metrics| {
1409						metrics.known_statements.with_label_values(&["known"]).inc();
1410					});
1411					return SubmitResult::Known;
1412				}
1413			},
1414			IndexQuery::Unknown => {},
1415		}
1416
1417		let Some(account_id) = statement.account_id() else {
1418			log::debug!(
1419				target: LOG_TARGET,
1420				"Statement validation failed: Missing proof ({:?})",
1421				HexDisplay::from(&hash),
1422			);
1423			let reason = InvalidReason::NoProof;
1424			self.metrics.report(|metrics| {
1425				metrics.validations_invalid.with_label_values(&[reason.label()]).inc();
1426			});
1427			return SubmitResult::Invalid(reason);
1428		};
1429
1430		match statement.verify_signature() {
1431			SignatureVerificationResult::Valid(_) => {},
1432			SignatureVerificationResult::Invalid => {
1433				log::debug!(
1434					target: LOG_TARGET,
1435					"Statement validation failed: BadProof, {:?}",
1436					HexDisplay::from(&hash),
1437				);
1438				let reason = InvalidReason::BadProof;
1439				self.metrics.report(|metrics| {
1440					metrics.validations_invalid.with_label_values(&[reason.label()]).inc();
1441				});
1442				return SubmitResult::Invalid(reason);
1443			},
1444			SignatureVerificationResult::NoSignature => {
1445				if let Some(Proof::OnChain { .. }) = statement.proof() {
1446					log::debug!(
1447						target: LOG_TARGET,
1448						"Statement with OnChain proof accepted: {:?}",
1449						HexDisplay::from(&hash),
1450					);
1451				} else {
1452					log::debug!(
1453						target: LOG_TARGET,
1454						"Statement validation failed: NoProof, {:?}",
1455						HexDisplay::from(&hash),
1456					);
1457					let reason = InvalidReason::NoProof;
1458					self.metrics.report(|metrics| {
1459						metrics.validations_invalid.with_label_values(&[reason.label()]).inc();
1460					});
1461					return SubmitResult::Invalid(reason);
1462				}
1463			},
1464		};
1465
1466		// Check statement allowance for the account and evict statements if necessary to make room
1467		// for the new statement. We use the best block for allowance checks to allow for more
1468		// up-to-date allowances. This means that in some cases, a statement may be accepted but
1469		// then later evicted when we enforce limits based on the finalized block, if the best_hash
1470		// does not make it into the finalized chain, but this is an acceptable tradeoff for
1471		// better responsiveness to allowance changes.
1472		let validation = match (self.read_allowance_fn)(
1473			&account_id,
1474			match statement.proof() {
1475				Some(Proof::OnChain { block_hash, .. }) => AllowanceBlock::Block(*block_hash),
1476				_ => AllowanceBlock::Best,
1477			},
1478		) {
1479			Ok(Some(allowance)) => allowance,
1480			Ok(None) => {
1481				log::debug!(
1482					target: LOG_TARGET,
1483					"Account {} has no statement allowance set",
1484					HexDisplay::from(&account_id),
1485				);
1486				let reason = RejectionReason::NoAllowance;
1487				self.metrics.report(|metrics| {
1488					metrics.rejections.with_label_values(&[reason.label()]).inc();
1489				});
1490				return SubmitResult::Rejected(reason);
1491			},
1492			Err(e) => {
1493				log::debug!(
1494					target: LOG_TARGET,
1495					"Reading statement allowance for account {} failed",
1496					HexDisplay::from(&account_id),
1497				);
1498				self.metrics.report(|metrics| {
1499					metrics.internal_errors.with_label_values(&["read_allowance"]).inc();
1500				});
1501				return SubmitResult::InternalError(e);
1502			},
1503		};
1504
1505		let current_time = self.timestamp();
1506		let mut commit = Vec::new();
1507		{
1508			let mut index = self.index.write();
1509
1510			let evicted =
1511				match index.insert(hash, &statement, &account_id, &validation, current_time) {
1512					Ok(evicted) => evicted,
1513					Err(reason) => {
1514						self.metrics.report(|metrics| {
1515							metrics.rejections.with_label_values(&[reason.label()]).inc();
1516						});
1517						return SubmitResult::Rejected(reason);
1518					},
1519				};
1520
1521			commit.push((col::STATEMENTS, hash.to_vec(), Some(statement.encode())));
1522			for hash in evicted {
1523				commit.push((col::STATEMENTS, hash.to_vec(), None));
1524				if index.evicted.contains(&hash) {
1525					commit.push((col::EXPIRED, hash.to_vec(), Some((hash, current_time).encode())));
1526				}
1527			}
1528			if let Err(e) = self.db.commit(commit) {
1529				log::debug!(
1530					target: LOG_TARGET,
1531					"Statement validation failed: database error {}, {:?}",
1532					e,
1533					statement
1534				);
1535				self.metrics.report(|metrics| {
1536					metrics.internal_errors.with_label_values(&["db_commit"]).inc();
1537				});
1538				return SubmitResult::InternalError(Error::Db(e.to_string()));
1539			}
1540			self.subscription_manager.notify(statement);
1541		} // Release index lock
1542		self.metrics.report(|metrics| metrics.submitted_statements.inc());
1543		log::trace!(target: LOG_TARGET, "Statement submitted: {:?}", HexDisplay::from(&hash));
1544		SubmitResult::New
1545	}
1546
1547	/// Remove a statement by hash.
1548	fn remove(&self, hash: &Hash) -> Result<()> {
1549		let current_time = self.timestamp();
1550		{
1551			let mut index = self.index.write();
1552			if index.make_expired(hash, current_time) {
1553				let mut commit = vec![(col::STATEMENTS, hash.to_vec(), None)];
1554				if index.evicted.contains(hash) {
1555					commit.push((col::EXPIRED, hash.to_vec(), Some((hash, current_time).encode())));
1556				}
1557				if let Err(e) = self.db.commit(commit) {
1558					log::debug!(
1559						target: LOG_TARGET,
1560						"Error removing statement: database error {}, {:?}",
1561						e,
1562						HexDisplay::from(hash),
1563					);
1564					return Err(Error::Db(e.to_string()));
1565				}
1566			}
1567		}
1568		Ok(())
1569	}
1570
1571	/// Remove all statements by an account.
1572	fn remove_by(&self, who: [u8; 32]) -> Result<()> {
1573		let mut index = self.index.write();
1574		let mut evicted = Vec::new();
1575		if let Some(account_rec) = index.accounts.get(&who) {
1576			evicted.extend(account_rec.by_priority.keys().map(|k| k.hash));
1577		}
1578
1579		let current_time = self.timestamp();
1580		let mut commit = Vec::new();
1581		for hash in evicted {
1582			index.make_expired(&hash, current_time);
1583			commit.push((col::STATEMENTS, hash.to_vec(), None));
1584			if index.evicted.contains(&hash) {
1585				commit.push((col::EXPIRED, hash.to_vec(), Some((hash, current_time).encode())));
1586			}
1587		}
1588		self.db.commit(commit).map_err(|e| {
1589			log::debug!(
1590				target: LOG_TARGET,
1591				"Error removing statement: database error {}, remove by {:?}",
1592				e,
1593				HexDisplay::from(&who),
1594			);
1595
1596			Error::Db(e.to_string())
1597		})
1598	}
1599}
1600
1601impl StatementStoreSubscriptionApi for Store {
1602	fn subscribe_statement(
1603		&self,
1604		topic_filter: OptimizedTopicFilter,
1605	) -> Result<(Vec<Vec<u8>>, async_channel::Sender<StatementEvent>, SubscriptionStatementsStream)>
1606	{
1607		// Keep the index read lock until after we have subscribed to avoid missing statements.
1608		let mut existing_statements = Vec::new();
1609		let index = self.index.read();
1610		self.collect_statements_locked(
1611			None,
1612			&topic_filter,
1613			&index,
1614			&mut existing_statements,
1615			|statement| Some(statement.encode()),
1616		)?;
1617		let (subscription_sender, subscription_stream) =
1618			self.subscription_manager.subscribe(topic_filter);
1619		if existing_statements.is_empty() {
1620			subscription_sender
1621				.send_blocking(StatementEvent::NewStatements {
1622					statements: vec![],
1623					remaining: Some(0),
1624				})
1625				.ok();
1626		}
1627		Ok((existing_statements, subscription_sender, subscription_stream))
1628	}
1629}
1630
1631#[cfg(test)]
1632mod tests {
1633
1634	use crate::{col, Store};
1635	use sc_keystore::Keystore;
1636	use sp_core::{Decode, Encode, Pair};
1637	use sp_statement_store::{
1638		AccountId, Channel, DecryptionKey, InvalidReason, Proof, RejectionReason, Statement,
1639		StatementSource, StatementStore, SubmitResult, Topic,
1640	};
1641
1642	type Extrinsic = sp_runtime::OpaqueExtrinsic;
1643	type Hash = sp_core::H256;
1644	type Hashing = sp_runtime::traits::BlakeTwo256;
1645	type BlockNumber = u64;
1646	type Header = sp_runtime::generic::Header<BlockNumber, Hashing>;
1647	type Block = sp_runtime::generic::Block<Header, Extrinsic>;
1648
1649	const CORRECT_BLOCK_HASH: [u8; 32] = [1u8; 32];
1650
1651	#[derive(Clone)]
1652	pub(crate) struct TestClient;
1653
1654	pub(crate) type TestBackend = sc_client_api::in_mem::Backend<Block>;
1655
1656	impl sc_client_api::StorageProvider<Block, TestBackend> for TestClient {
1657		fn storage(
1658			&self,
1659			_hash: Hash,
1660			key: &sc_client_api::StorageKey,
1661		) -> sp_blockchain::Result<Option<sc_client_api::StorageData>> {
1662			use sp_statement_store::StatementAllowance;
1663
1664			assert_eq!(&key.0[0..21], b":statement_allowance:" as &[u8],);
1665
1666			// Extract account ID (32 bytes) from the storage key
1667			let account_bytes = &key.0[21..53];
1668			let account_id: u64 = u64::from_le_bytes(account_bytes[0..8].try_into().unwrap());
1669			let allowance = match account_id {
1670				// Account 0 has no allowance (used to test eviction of all statements)
1671				0 => return Ok(None),
1672				1 => StatementAllowance::new(1, 1000),
1673				2 => StatementAllowance::new(2, 1000),
1674				3 => StatementAllowance::new(3, 1000),
1675				4 => StatementAllowance::new(4, 1000),
1676				42 => StatementAllowance::new(42, (42 * crate::MAX_STATEMENT_SIZE) as u32),
1677				_ => StatementAllowance::new(100, 1000),
1678			};
1679			Ok(Some(sc_client_api::StorageData(allowance.encode())))
1680		}
1681
1682		fn storage_hash(
1683			&self,
1684			_hash: Hash,
1685			_key: &sc_client_api::StorageKey,
1686		) -> sp_blockchain::Result<Option<Hash>> {
1687			unimplemented!()
1688		}
1689
1690		fn storage_keys(
1691			&self,
1692			_hash: Hash,
1693			_prefix: Option<&sc_client_api::StorageKey>,
1694			_start_key: Option<&sc_client_api::StorageKey>,
1695		) -> sp_blockchain::Result<
1696			sc_client_api::backend::KeysIter<
1697				<TestBackend as sc_client_api::Backend<Block>>::State,
1698				Block,
1699			>,
1700		> {
1701			unimplemented!()
1702		}
1703
1704		fn storage_pairs(
1705			&self,
1706			_hash: Hash,
1707			_prefix: Option<&sc_client_api::StorageKey>,
1708			_start_key: Option<&sc_client_api::StorageKey>,
1709		) -> sp_blockchain::Result<
1710			sc_client_api::backend::PairsIter<
1711				<TestBackend as sc_client_api::Backend<Block>>::State,
1712				Block,
1713			>,
1714		> {
1715			unimplemented!()
1716		}
1717
1718		fn child_storage(
1719			&self,
1720			_hash: Hash,
1721			_child_info: &sc_client_api::ChildInfo,
1722			_key: &sc_client_api::StorageKey,
1723		) -> sp_blockchain::Result<Option<sc_client_api::StorageData>> {
1724			unimplemented!()
1725		}
1726
1727		fn child_storage_keys(
1728			&self,
1729			_hash: Hash,
1730			_child_info: sc_client_api::ChildInfo,
1731			_prefix: Option<&sc_client_api::StorageKey>,
1732			_start_key: Option<&sc_client_api::StorageKey>,
1733		) -> sp_blockchain::Result<
1734			sc_client_api::backend::KeysIter<
1735				<TestBackend as sc_client_api::Backend<Block>>::State,
1736				Block,
1737			>,
1738		> {
1739			unimplemented!()
1740		}
1741
1742		fn child_storage_hash(
1743			&self,
1744			_hash: Hash,
1745			_child_info: &sc_client_api::ChildInfo,
1746			_key: &sc_client_api::StorageKey,
1747		) -> sp_blockchain::Result<Option<Hash>> {
1748			unimplemented!()
1749		}
1750
1751		fn closest_merkle_value(
1752			&self,
1753			_hash: Hash,
1754			_key: &sc_client_api::StorageKey,
1755		) -> sp_blockchain::Result<Option<sc_client_api::MerkleValue<Hash>>> {
1756			unimplemented!()
1757		}
1758
1759		fn child_closest_merkle_value(
1760			&self,
1761			_hash: Hash,
1762			_child_info: &sc_client_api::ChildInfo,
1763			_key: &sc_client_api::StorageKey,
1764		) -> sp_blockchain::Result<Option<sc_client_api::MerkleValue<Hash>>> {
1765			unimplemented!()
1766		}
1767	}
1768
1769	impl sp_blockchain::HeaderBackend<Block> for TestClient {
1770		fn header(&self, _hash: Hash) -> sp_blockchain::Result<Option<Header>> {
1771			unimplemented!()
1772		}
1773		fn info(&self) -> sp_blockchain::Info<Block> {
1774			sp_blockchain::Info {
1775				best_hash: CORRECT_BLOCK_HASH.into(),
1776				best_number: 0,
1777				genesis_hash: Default::default(),
1778				finalized_hash: CORRECT_BLOCK_HASH.into(),
1779				finalized_number: 1,
1780				finalized_state: None,
1781				number_leaves: 0,
1782				block_gap: None,
1783			}
1784		}
1785		fn status(&self, _hash: Hash) -> sp_blockchain::Result<sp_blockchain::BlockStatus> {
1786			unimplemented!()
1787		}
1788		fn number(&self, _hash: Hash) -> sp_blockchain::Result<Option<BlockNumber>> {
1789			unimplemented!()
1790		}
1791		fn hash(&self, _number: BlockNumber) -> sp_blockchain::Result<Option<Hash>> {
1792			unimplemented!()
1793		}
1794	}
1795
1796	fn test_store() -> (Store, tempfile::TempDir) {
1797		sp_tracing::init_for_tests();
1798		let temp_dir = tempfile::Builder::new().tempdir().expect("Error creating test dir");
1799
1800		let client = std::sync::Arc::new(TestClient);
1801		let mut path: std::path::PathBuf = temp_dir.path().into();
1802		path.push("db");
1803		let keystore = std::sync::Arc::new(sc_keystore::LocalKeystore::in_memory());
1804		let store = Store::new::<Block, TestClient, TestBackend>(
1805			&path,
1806			Default::default(),
1807			client,
1808			keystore,
1809			None,
1810			Box::new(sp_core::testing::TaskExecutor::new()),
1811		)
1812		.unwrap();
1813		(store, temp_dir) // return order is important. Store must be dropped before TempDir
1814	}
1815
1816	pub fn signed_statement(data: u8) -> Statement {
1817		signed_statement_with_topics(data, &[], None)
1818	}
1819
1820	fn signed_statement_with_topics(
1821		data: u8,
1822		topics: &[Topic],
1823		dec_key: Option<DecryptionKey>,
1824	) -> Statement {
1825		let mut statement = Statement::new();
1826		statement.set_plain_data(vec![data]);
1827		statement.set_expiry(u64::MAX);
1828
1829		for i in 0..topics.len() {
1830			statement.set_topic(i, topics[i]);
1831		}
1832		if let Some(key) = dec_key {
1833			statement.set_decryption_key(key);
1834		}
1835		let kp = sp_core::ed25519::Pair::from_string("//Alice", None).unwrap();
1836		statement.sign_ed25519_private(&kp);
1837		statement
1838	}
1839
1840	fn topic(data: u64) -> Topic {
1841		let mut bytes = [0u8; 32];
1842		bytes[0..8].copy_from_slice(&data.to_le_bytes());
1843		Topic::from(bytes)
1844	}
1845
1846	fn dec_key(data: u64) -> DecryptionKey {
1847		let mut dec_key: DecryptionKey = Default::default();
1848		dec_key[0..8].copy_from_slice(&data.to_le_bytes());
1849		dec_key
1850	}
1851
1852	fn account(id: u64) -> AccountId {
1853		let mut account: AccountId = Default::default();
1854		account[0..8].copy_from_slice(&id.to_le_bytes());
1855		account
1856	}
1857
1858	fn channel(id: u64) -> Channel {
1859		let mut channel: Channel = Default::default();
1860		channel[0..8].copy_from_slice(&id.to_le_bytes());
1861		channel
1862	}
1863
1864	fn statement(account_id: u64, priority: u32, c: Option<u64>, data_len: usize) -> Statement {
1865		let mut statement = Statement::new();
1866		let mut data = Vec::new();
1867		data.resize(data_len, 0);
1868		statement.set_plain_data(data);
1869		statement.set_expiry_from_parts(u32::MAX, priority);
1870		if let Some(c) = c {
1871			statement.set_channel(channel(c));
1872		}
1873		statement.set_proof(Proof::OnChain {
1874			block_hash: CORRECT_BLOCK_HASH,
1875			who: account(account_id),
1876			event_index: 0,
1877		});
1878		statement
1879	}
1880
1881	#[test]
1882	fn submit_one() {
1883		let (store, _temp) = test_store();
1884		let statement0 = signed_statement(0);
1885		assert_eq!(store.submit(statement0, StatementSource::Network), SubmitResult::New);
1886		let unsigned = statement(1, 1, None, 0);
1887		assert_eq!(store.submit(unsigned, StatementSource::Network), SubmitResult::New);
1888	}
1889
1890	#[test]
1891	fn save_and_load_statements() {
1892		let (store, temp) = test_store();
1893		let statement0 = signed_statement(0);
1894		let statement1 = signed_statement(1);
1895		let statement2 = signed_statement(2);
1896		assert_eq!(store.submit(statement0.clone(), StatementSource::Network), SubmitResult::New);
1897		assert_eq!(store.submit(statement1.clone(), StatementSource::Network), SubmitResult::New);
1898		assert_eq!(store.submit(statement2.clone(), StatementSource::Network), SubmitResult::New);
1899		assert_eq!(store.statements().unwrap().len(), 3);
1900		assert_eq!(store.broadcasts(&[]).unwrap().len(), 3);
1901		assert_eq!(store.statement(&statement1.hash()).unwrap(), Some(statement1.clone()));
1902		let keystore = store.keystore.clone();
1903		drop(store);
1904
1905		let client = std::sync::Arc::new(TestClient);
1906		let mut path: std::path::PathBuf = temp.path().into();
1907		path.push("db");
1908		let store = Store::new::<Block, TestClient, TestBackend>(
1909			&path,
1910			Default::default(),
1911			client,
1912			keystore,
1913			None,
1914			Box::new(sp_core::testing::TaskExecutor::new()),
1915		)
1916		.unwrap();
1917		assert_eq!(store.statements().unwrap().len(), 3);
1918		assert_eq!(store.broadcasts(&[]).unwrap().len(), 3);
1919		assert_eq!(store.statement(&statement1.hash()).unwrap(), Some(statement1));
1920	}
1921
1922	#[test]
1923	fn take_recent_statements_clears_index() {
1924		let (store, _temp) = test_store();
1925		let statement0 = signed_statement(0);
1926		let statement1 = signed_statement(1);
1927		let statement2 = signed_statement(2);
1928		let statement3 = signed_statement(3);
1929
1930		let _ = store.submit(statement0.clone(), StatementSource::Local);
1931		let _ = store.submit(statement1.clone(), StatementSource::Local);
1932		let _ = store.submit(statement2.clone(), StatementSource::Local);
1933
1934		let recent1 = store.take_recent_statements().unwrap();
1935		let (recent1_hashes, recent1_statements): (Vec<_>, Vec<_>) = recent1.into_iter().unzip();
1936		let expected1 = vec![statement0, statement1, statement2];
1937		assert!(expected1.iter().all(|s| recent1_hashes.contains(&s.hash())));
1938		assert!(expected1.iter().all(|s| recent1_statements.contains(s)));
1939
1940		// Recent statements are cleared.
1941		let recent2 = store.take_recent_statements().unwrap();
1942		assert_eq!(recent2.len(), 0);
1943
1944		store.submit(statement3.clone(), StatementSource::Network);
1945
1946		let recent3 = store.take_recent_statements().unwrap();
1947		let (recent3_hashes, recent3_statements): (Vec<_>, Vec<_>) = recent3.into_iter().unzip();
1948		let expected3 = vec![statement3];
1949		assert!(expected3.iter().all(|s| recent3_hashes.contains(&s.hash())));
1950		assert!(expected3.iter().all(|s| recent3_statements.contains(s)));
1951
1952		// Recent statements are cleared, but statements remain in the store.
1953		assert_eq!(store.statements().unwrap().len(), 4);
1954	}
1955
1956	#[test]
1957	fn search_by_topic_and_key() {
1958		let (store, _temp) = test_store();
1959		let statement0 = signed_statement(0);
1960		let statement1 = signed_statement_with_topics(1, &[topic(0)], None);
1961		let statement2 = signed_statement_with_topics(2, &[topic(0), topic(1)], Some(dec_key(2)));
1962		let statement3 = signed_statement_with_topics(3, &[topic(0), topic(1), topic(2)], None);
1963		let statement4 =
1964			signed_statement_with_topics(4, &[topic(0), topic(42), topic(2), topic(3)], None);
1965		let statements = vec![statement0, statement1, statement2, statement3, statement4];
1966		for s in &statements {
1967			store.submit(s.clone(), StatementSource::Network);
1968		}
1969
1970		let assert_topics = |topics: &[u64], key: Option<u64>, expected: &[u8]| {
1971			let key = key.map(dec_key);
1972			let topics: Vec<_> = topics.iter().map(|t| topic(*t)).collect();
1973			let mut got_vals: Vec<_> = if let Some(key) = key {
1974				store.posted(&topics, key).unwrap().into_iter().map(|d| d[0]).collect()
1975			} else {
1976				store.broadcasts(&topics).unwrap().into_iter().map(|d| d[0]).collect()
1977			};
1978			got_vals.sort();
1979			assert_eq!(expected.to_vec(), got_vals);
1980		};
1981
1982		assert_topics(&[], None, &[0, 1, 3, 4]);
1983		assert_topics(&[], Some(2), &[2]);
1984		assert_topics(&[0], None, &[1, 3, 4]);
1985		assert_topics(&[1], None, &[3]);
1986		assert_topics(&[2], None, &[3, 4]);
1987		assert_topics(&[3], None, &[4]);
1988		assert_topics(&[42], None, &[4]);
1989
1990		assert_topics(&[0, 1], None, &[3]);
1991		assert_topics(&[0, 1], Some(2), &[2]);
1992		assert_topics(&[0, 1, 99], Some(2), &[]);
1993		assert_topics(&[1, 2], None, &[3]);
1994		assert_topics(&[99], None, &[]);
1995		assert_topics(&[0, 99], None, &[]);
1996		assert_topics(&[0, 1, 2, 3, 42], None, &[]);
1997	}
1998
1999	#[test]
2000	fn constraints() {
2001		let (store, _temp) = test_store();
2002
2003		store.index.write().config.max_total_size = 3000;
2004		let source = StatementSource::Network;
2005		let ok = SubmitResult::New;
2006
2007		// Account 1 (limit = 1 msg, 1000 bytes)
2008
2009		// Oversized statement is not allowed. Limit for account 1 is 1 msg, 1000 bytes
2010		assert!(matches!(
2011			store.submit(statement(1, 1, Some(1), 2000), source),
2012			SubmitResult::Rejected(_)
2013		));
2014		assert_eq!(store.submit(statement(1, 1, Some(1), 500), source), ok);
2015		// Would not replace channel message with same priority
2016		assert!(matches!(
2017			store.submit(statement(1, 1, Some(1), 200), source),
2018			SubmitResult::Rejected(_)
2019		));
2020		assert_eq!(store.submit(statement(1, 2, Some(1), 600), source), ok);
2021		// Submit another message to another channel with lower priority. Should not be allowed
2022		// because msg count limit is 1
2023		assert!(matches!(
2024			store.submit(statement(1, 1, Some(2), 100), source),
2025			SubmitResult::Rejected(_)
2026		));
2027		assert_eq!(store.index.read().evicted.len(), 1);
2028
2029		// Account 2 (limit = 2 msg, 1000 bytes)
2030
2031		let s2_prio1 = statement(2, 1, None, 500);
2032		let s2_prio2 = statement(2, 2, None, 100);
2033		assert_eq!(store.submit(s2_prio1.clone(), source), ok);
2034		assert_eq!(store.submit(s2_prio2.clone(), source), ok);
2035		// Equal priority to lowest should be rejected
2036		assert!(matches!(
2037			store.submit(statement(2, 1, None, 50), source),
2038			SubmitResult::Rejected(RejectionReason::AccountFull { .. })
2039		));
2040		// Should evict priority 1
2041		let s2_prio3 = statement(2, 3, None, 500);
2042		assert_eq!(store.submit(s2_prio3.clone(), source), ok);
2043		assert_eq!(store.index.read().evicted.len(), 2);
2044		assert!(store.index.read().evicted.contains(&s2_prio1.hash()));
2045		assert!(store.statement(&s2_prio1.hash()).unwrap().is_none());
2046		// Should evict all
2047		assert_eq!(store.submit(statement(2, 4, None, 1000), source), ok);
2048		assert_eq!(store.index.read().evicted.len(), 4);
2049		assert!(store.index.read().evicted.contains(&s2_prio2.hash()));
2050		assert!(store.index.read().evicted.contains(&s2_prio3.hash()));
2051
2052		// Account 3 (limit = 3 msg, 1000 bytes)
2053
2054		let s3_prio2 = statement(3, 2, Some(1), 300);
2055		let s3_prio3 = statement(3, 3, Some(2), 300);
2056		assert_eq!(store.submit(s3_prio2.clone(), source), ok);
2057		assert_eq!(store.submit(s3_prio3.clone(), source), ok);
2058		assert_eq!(store.submit(statement(3, 4, Some(3), 300), source), ok);
2059		// Should evict 2 and 3
2060		assert_eq!(store.submit(statement(3, 5, None, 500), source), ok);
2061		assert_eq!(store.index.read().evicted.len(), 6);
2062		assert!(store.index.read().evicted.contains(&s3_prio2.hash()));
2063		assert!(store.index.read().evicted.contains(&s3_prio3.hash()));
2064
2065		assert_eq!(store.index.read().total_size, 2400);
2066		assert_eq!(store.index.read().entries.len(), 4);
2067
2068		// Should be over the global size limit
2069		assert!(matches!(
2070			store.submit(statement(1, 1, None, 700), source),
2071			SubmitResult::Rejected(_)
2072		));
2073		// Should be over the global count limit
2074		store.index.write().config.max_total_statements = 4;
2075		assert!(matches!(
2076			store.submit(statement(1, 1, None, 100), source),
2077			SubmitResult::Rejected(_)
2078		));
2079
2080		let mut expected_statements = vec![
2081			statement(1, 2, Some(1), 600).hash(),
2082			statement(2, 4, None, 1000).hash(),
2083			statement(3, 4, Some(3), 300).hash(),
2084			statement(3, 5, None, 500).hash(),
2085		];
2086		expected_statements.sort();
2087		let mut statements: Vec<_> =
2088			store.statements().unwrap().into_iter().map(|(hash, _)| hash).collect();
2089		statements.sort();
2090		assert_eq!(expected_statements, statements);
2091	}
2092
2093	#[test]
2094	fn max_statement_size_for_gossiping() {
2095		let (store, _temp) = test_store();
2096		store.index.write().config.max_total_size = 42 * crate::MAX_STATEMENT_SIZE;
2097
2098		assert_eq!(
2099			store.submit(
2100				statement(42, 1, Some(1), crate::MAX_STATEMENT_SIZE - 500),
2101				StatementSource::Local
2102			),
2103			SubmitResult::New
2104		);
2105
2106		assert!(matches!(
2107			store.submit(
2108				statement(42, 2, Some(1), 2 * crate::MAX_STATEMENT_SIZE),
2109				StatementSource::Local
2110			),
2111			SubmitResult::Invalid(_)
2112		));
2113	}
2114
2115	#[test]
2116	fn expired_statements_are_purged() {
2117		use super::DEFAULT_PURGE_AFTER_SEC;
2118		let (mut store, temp) = test_store();
2119		let mut statement = statement(1, 1, Some(3), 100);
2120		store.set_time(0);
2121		statement.set_topic(0, topic(4));
2122		store.submit(statement.clone(), StatementSource::Network);
2123		assert_eq!(store.index.read().entries.len(), 1);
2124		store.remove(&statement.hash()).unwrap();
2125		assert_eq!(store.index.read().entries.len(), 0);
2126		assert_eq!(store.index.read().accounts.len(), 0);
2127		store.set_time(DEFAULT_PURGE_AFTER_SEC + 1);
2128		store.maintain();
2129		assert_eq!(store.index.read().evicted.len(), 0);
2130		let keystore = store.keystore.clone();
2131		drop(store);
2132
2133		let client = std::sync::Arc::new(TestClient);
2134		let mut path: std::path::PathBuf = temp.path().into();
2135		path.push("db");
2136		let store = Store::new::<Block, TestClient, TestBackend>(
2137			&path,
2138			Default::default(),
2139			client,
2140			keystore,
2141			None,
2142			Box::new(sp_core::testing::TaskExecutor::new()),
2143		)
2144		.unwrap();
2145		assert_eq!(store.statements().unwrap().len(), 0);
2146		assert_eq!(store.index.read().evicted.len(), 0);
2147	}
2148
2149	#[test]
2150	fn posted_clear_decrypts() {
2151		let (store, _temp) = test_store();
2152		let public = store
2153			.keystore
2154			.ed25519_generate_new(sp_core::crypto::key_types::STATEMENT, None)
2155			.unwrap();
2156		let statement1 = statement(1, 1, None, 100);
2157		let mut statement2 = statement(1, 2, None, 0);
2158		let plain = b"The most valuable secret".to_vec();
2159		statement2.encrypt(&plain, &public).unwrap();
2160		store.submit(statement1, StatementSource::Network);
2161		store.submit(statement2, StatementSource::Network);
2162		let posted_clear = store.posted_clear(&[], public.into()).unwrap();
2163		assert_eq!(posted_clear, vec![plain]);
2164	}
2165
2166	#[test]
2167	fn broadcasts_stmt_returns_encoded_statements() {
2168		let (store, _tmp) = test_store();
2169
2170		// no key, no topic
2171		let s0 = signed_statement_with_topics(0, &[], None);
2172		// same, but with a topic = 42
2173		let s1 = signed_statement_with_topics(1, &[topic(42)], None);
2174		// has a decryption key -> must NOT be returned by broadcasts_stmt
2175		let s2 = signed_statement_with_topics(2, &[topic(42)], Some(dec_key(99)));
2176
2177		for s in [&s0, &s1, &s2] {
2178			store.submit(s.clone(), StatementSource::Network);
2179		}
2180
2181		// no topic filter
2182		let mut hashes: Vec<_> = store
2183			.broadcasts_stmt(&[])
2184			.unwrap()
2185			.into_iter()
2186			.map(|bytes| Statement::decode(&mut &bytes[..]).unwrap().hash())
2187			.collect();
2188		hashes.sort();
2189		let expected_hashes = {
2190			let mut e = vec![s0.hash(), s1.hash()];
2191			e.sort();
2192			e
2193		};
2194		assert_eq!(hashes, expected_hashes);
2195
2196		// filter on topic 42
2197		let got = store.broadcasts_stmt(&[topic(42)]).unwrap();
2198		assert_eq!(got.len(), 1);
2199		let st = Statement::decode(&mut &got[0][..]).unwrap();
2200		assert_eq!(st.hash(), s1.hash());
2201	}
2202
2203	#[test]
2204	fn posted_stmt_returns_encoded_statements_for_dest() {
2205		let (store, _tmp) = test_store();
2206
2207		let public1 = store
2208			.keystore
2209			.ed25519_generate_new(sp_core::crypto::key_types::STATEMENT, None)
2210			.unwrap();
2211		let dest: [u8; 32] = public1.into();
2212
2213		let public2 = store
2214			.keystore
2215			.ed25519_generate_new(sp_core::crypto::key_types::STATEMENT, None)
2216			.unwrap();
2217
2218		// A statement that does have dec_key = dest
2219		let mut s_with_key = statement(1, 1, None, 0);
2220		let plain1 = b"The most valuable secret".to_vec();
2221		s_with_key.encrypt(&plain1, &public1).unwrap();
2222
2223		// A statement with a different dec_key
2224		let mut s_other_key = statement(2, 2, None, 0);
2225		let plain2 = b"The second most valuable secret".to_vec();
2226		s_other_key.encrypt(&plain2, &public2).unwrap();
2227
2228		// Submit them all
2229		for s in [&s_with_key, &s_other_key] {
2230			store.submit(s.clone(), StatementSource::Network);
2231		}
2232
2233		// posted_stmt should only return the one with dec_key = dest
2234		let retrieved = store.posted_stmt(&[], dest).unwrap();
2235		assert_eq!(retrieved.len(), 1, "Only one statement has dec_key=dest");
2236
2237		// Re-decode that returned statement to confirm it is correct
2238		let returned_stmt = Statement::decode(&mut &retrieved[0][..]).unwrap();
2239		assert_eq!(
2240			returned_stmt.hash(),
2241			s_with_key.hash(),
2242			"Returned statement must match s_with_key"
2243		);
2244	}
2245
2246	#[test]
2247	fn posted_clear_stmt_returns_statement_followed_by_plain_data() {
2248		let (store, _tmp) = test_store();
2249
2250		let public1 = store
2251			.keystore
2252			.ed25519_generate_new(sp_core::crypto::key_types::STATEMENT, None)
2253			.unwrap();
2254		let dest: [u8; 32] = public1.into();
2255
2256		let public2 = store
2257			.keystore
2258			.ed25519_generate_new(sp_core::crypto::key_types::STATEMENT, None)
2259			.unwrap();
2260
2261		// A statement that does have dec_key = dest
2262		let mut s_with_key = statement(1, 1, None, 0);
2263		let plain1 = b"The most valuable secret".to_vec();
2264		s_with_key.encrypt(&plain1, &public1).unwrap();
2265
2266		// A statement with a different dec_key
2267		let mut s_other_key = statement(2, 2, None, 0);
2268		let plain2 = b"The second most valuable secret".to_vec();
2269		s_other_key.encrypt(&plain2, &public2).unwrap();
2270
2271		// Submit them all
2272		for s in [&s_with_key, &s_other_key] {
2273			store.submit(s.clone(), StatementSource::Network);
2274		}
2275
2276		// posted_stmt should only return the one with dec_key = dest
2277		let retrieved = store.posted_clear_stmt(&[], dest).unwrap();
2278		assert_eq!(retrieved.len(), 1, "Only one statement has dec_key=dest");
2279
2280		// We expect: [ encoded Statement ] + [ the decrypted bytes ]
2281		let encoded_stmt = s_with_key.encode();
2282		let stmt_len = encoded_stmt.len();
2283
2284		// 1) statement is first
2285		assert_eq!(&retrieved[0][..stmt_len], &encoded_stmt[..]);
2286
2287		// 2) followed by the decrypted payload
2288		let trailing = &retrieved[0][stmt_len..];
2289		assert_eq!(trailing, &plain1[..]);
2290	}
2291
2292	#[test]
2293	fn posted_clear_returns_plain_data_for_dest_and_topics() {
2294		let (store, _tmp) = test_store();
2295
2296		// prepare two key-pairs
2297		let public_dest = store
2298			.keystore
2299			.ed25519_generate_new(sp_core::crypto::key_types::STATEMENT, None)
2300			.unwrap();
2301		let dest: [u8; 32] = public_dest.into();
2302
2303		let public_other = store
2304			.keystore
2305			.ed25519_generate_new(sp_core::crypto::key_types::STATEMENT, None)
2306			.unwrap();
2307
2308		// statement that SHOULD be returned (matches dest & topic 42)
2309		let mut s_good = statement(1, 1, None, 0);
2310		let plaintext_good = b"The most valuable secret".to_vec();
2311		s_good.encrypt(&plaintext_good, &public_dest).unwrap();
2312		s_good.set_topic(0, topic(42));
2313
2314		// statement that should NOT be returned (same dest but different topic)
2315		let mut s_wrong_topic = statement(2, 2, None, 0);
2316		s_wrong_topic.encrypt(b"Wrong topic", &public_dest).unwrap();
2317		s_wrong_topic.set_topic(0, topic(99));
2318
2319		// statement that should NOT be returned (different dest)
2320		let mut s_other_dest = statement(3, 3, None, 0);
2321		s_other_dest.encrypt(b"Other dest", &public_other).unwrap();
2322		s_other_dest.set_topic(0, topic(42));
2323
2324		// submit all
2325		for s in [&s_good, &s_wrong_topic, &s_other_dest] {
2326			store.submit(s.clone(), StatementSource::Network);
2327		}
2328
2329		// call posted_clear with the topic filter and dest
2330		let retrieved = store.posted_clear(&[topic(42)], dest).unwrap();
2331
2332		// exactly one element, equal to the expected plaintext
2333		assert_eq!(retrieved, vec![plaintext_good]);
2334	}
2335
2336	#[test]
2337	fn already_expired_statement_is_rejected() {
2338		let (mut store, _temp) = test_store();
2339
2340		// Set current time to 1000 seconds
2341		store.set_time(1000);
2342
2343		// Create a statement that has already expired (expiration at 500 seconds, before current
2344		// time)
2345		let mut expired_statement = statement(1, 1, None, 100);
2346		// set_expiry_from_parts: first arg is expiration timestamp in seconds, second is priority
2347		expired_statement.set_expiry_from_parts(500, 1);
2348
2349		// Submit should fail with AlreadyExpired
2350		assert_eq!(
2351			store.submit(expired_statement, StatementSource::Network),
2352			SubmitResult::Invalid(InvalidReason::AlreadyExpired)
2353		);
2354
2355		// Verify the statement was not added
2356		assert_eq!(store.statements().unwrap().len(), 0);
2357
2358		// Now create a statement that is not expired (expiration at 2000 seconds, after current
2359		// time)
2360		let mut valid_statement = statement(1, 1, None, 100);
2361		valid_statement.set_expiry_from_parts(2000, 1);
2362
2363		// Submit should succeed
2364		assert_eq!(store.submit(valid_statement, StatementSource::Network), SubmitResult::New);
2365		assert_eq!(store.statements().unwrap().len(), 1);
2366	}
2367
2368	#[test]
2369	fn remove_by_covers_various_situations() {
2370		use sp_statement_store::{StatementSource, StatementStore, SubmitResult};
2371
2372		// Use a fresh store and fixed time so we can control purging.
2373		let (mut store, _temp) = test_store();
2374		store.set_time(0);
2375
2376		// Reuse helpers from this module.
2377		let t42 = topic(42);
2378		let k7 = dec_key(7);
2379
2380		// Account A = 4 (has per-account limits (4, 1000) in the mock runtime)
2381		// - Mix of topic, decryption-key and channel to exercise every index.
2382		let mut s_a1 = statement(4, 10, Some(100), 100);
2383		s_a1.set_topic(0, t42);
2384		let h_a1 = s_a1.hash();
2385
2386		let mut s_a2 = statement(4, 20, Some(200), 150);
2387		s_a2.set_decryption_key(k7);
2388		let h_a2 = s_a2.hash();
2389
2390		let s_a3 = statement(4, 30, None, 50);
2391		let h_a3 = s_a3.hash();
2392
2393		// Account B = 3 (control group that must remain untouched).
2394		let s_b1 = statement(3, 10, None, 100);
2395		let h_b1 = s_b1.hash();
2396
2397		let mut s_b2 = statement(3, 15, Some(300), 100);
2398		s_b2.set_topic(0, t42);
2399		s_b2.set_decryption_key(k7);
2400		let h_b2 = s_b2.hash();
2401
2402		// Submit all statements.
2403		for s in [&s_a1, &s_a2, &s_a3, &s_b1, &s_b2] {
2404			assert_eq!(store.submit(s.clone(), StatementSource::Network), SubmitResult::New);
2405		}
2406
2407		// --- Pre-conditions: everything is indexed as expected.
2408		{
2409			let idx = store.index.read();
2410			assert_eq!(idx.entries.len(), 5, "all 5 should be present");
2411			assert!(idx.accounts.contains_key(&account(4)));
2412			assert!(idx.accounts.contains_key(&account(3)));
2413			assert_eq!(idx.total_size, 100 + 150 + 50 + 100 + 100);
2414
2415			// Topic and key sets contain both A & B entries.
2416			let set_t = idx.by_topic.get(&t42).expect("topic set exists");
2417			assert!(set_t.contains(&h_a1) && set_t.contains(&h_b2));
2418
2419			let set_k = idx.by_dec_key.get(&Some(k7)).expect("key set exists");
2420			assert!(set_k.contains(&h_a2) && set_k.contains(&h_b2));
2421		}
2422
2423		// --- Action: remove all statements by Account A.
2424		store.remove_by(account(4)).expect("remove_by should succeed");
2425
2426		// --- Post-conditions: A's statements are gone and marked expired; B's remain.
2427		{
2428			// A's statements removed from DB view.
2429			for h in [h_a1, h_a2, h_a3] {
2430				assert!(store.statement(&h).unwrap().is_none(), "A's statement should be removed");
2431			}
2432
2433			// B's statements still present.
2434			for h in [h_b1, h_b2] {
2435				assert!(store.statement(&h).unwrap().is_some(), "B's statement should remain");
2436			}
2437
2438			let idx = store.index.read();
2439
2440			// Account map updated.
2441			assert!(!idx.accounts.contains_key(&account(4)), "Account A must be gone");
2442			assert!(idx.accounts.contains_key(&account(3)), "Account B must remain");
2443
2444			// Removed statements are marked expired.
2445			assert!(idx.evicted.contains(&h_a1));
2446			assert!(idx.evicted.contains(&h_a2));
2447			assert!(idx.evicted.contains(&h_a3));
2448			assert_eq!(idx.evicted.len(), 3);
2449
2450			// Entry count & total_size reflect only B's data.
2451			assert_eq!(idx.entries.len(), 2);
2452			assert_eq!(idx.total_size, 100 + 100);
2453
2454			// Topic index: only B2 remains for topic 42.
2455			let set_t = idx.by_topic.get(&t42).expect("topic set exists");
2456			assert!(set_t.contains(&h_b2));
2457			assert!(!set_t.contains(&h_a1));
2458
2459			// Decryption-key index: only B2 remains for key 7.
2460			let set_k = idx.by_dec_key.get(&Some(k7)).expect("key set exists");
2461			assert!(set_k.contains(&h_b2));
2462			assert!(!set_k.contains(&h_a2));
2463		}
2464
2465		// --- Idempotency: removing again is a no-op and should not error.
2466		store.remove_by(account(4)).expect("second remove_by should be a no-op");
2467
2468		// --- Purge: advance time beyond TTL and run maintenance; expired entries disappear.
2469		let purge_after = store.index.read().config.purge_after_sec;
2470		store.set_time(purge_after + 1);
2471		store.maintain();
2472		assert_eq!(store.index.read().evicted.len(), 0, "expired entries should be purged");
2473
2474		// --- Reuse: Account A can submit again after purge.
2475		let s_new = statement(4, 40, None, 10);
2476		assert_eq!(store.submit(s_new, StatementSource::Network), SubmitResult::New);
2477	}
2478
2479	#[test]
2480	fn check_expiration_repopulates_account_list_when_empty() {
2481		let (mut store, _temp) = test_store();
2482		store.set_time(1000);
2483
2484		// Create statements for multiple accounts
2485		// Note: The statement() helper uses set_expiry_from_parts(u32::MAX, priority)
2486		// which creates a very large expiry value that won't trigger expiration
2487		let s1 = statement(1, 1, None, 100);
2488		let s2 = statement(2, 1, None, 100);
2489		let s3 = statement(3, 1, None, 100);
2490
2491		for s in [&s1, &s2, &s3] {
2492			store.submit(s.clone(), StatementSource::Network);
2493		}
2494
2495		// Initially, accounts_to_check_for_expiry_stmts is empty
2496		assert!(store.index.read().accounts_to_check_for_expiry_stmts.is_empty());
2497
2498		// First call to check_expiration should populate the list
2499		store.enforce_limits();
2500
2501		// Now accounts_to_check_for_expiry_stmts should contain all 3 accounts
2502		let accounts = store.index.read().accounts_to_check_for_expiry_stmts.clone();
2503		assert_eq!(accounts.len(), 3, "Should have 3 accounts to check");
2504		assert!(accounts.contains(&account(1)));
2505		assert!(accounts.contains(&account(2)));
2506		assert!(accounts.contains(&account(3)));
2507
2508		// No statements should have been expired since they're all valid
2509		assert_eq!(store.index.read().evicted.len(), 0);
2510		assert_eq!(store.index.read().entries.len(), 3);
2511	}
2512
2513	#[test]
2514	fn check_expiration_expires_statements_past_current_time() {
2515		let (mut store, _temp) = test_store();
2516
2517		// The check_expiration function compares Expiry(current_time << 32) against
2518		// Expiry(expiry) where expiry is the full 64-bit value with timestamp in high 32 bits.
2519		// Statements with expiration timestamp < current_time will be expired.
2520
2521		store.set_time(100);
2522
2523		// Create a statement that will expire at timestamp 500
2524		let mut expired_stmt = statement(1, 1, None, 100);
2525		expired_stmt.set_expiry_from_parts(500, 1);
2526		let expired_hash = expired_stmt.hash();
2527		store.submit(expired_stmt, StatementSource::Network);
2528
2529		// Create a statement that won't expire (far future expiry)
2530		let valid_stmt = statement(2, 1, None, 100); // Uses u32::MAX as timestamp
2531		let valid_hash = valid_stmt.hash();
2532		store.submit(valid_stmt, StatementSource::Network);
2533
2534		// Verify both statements are in the store
2535		assert_eq!(store.index.read().entries.len(), 2);
2536
2537		// First check_expiration populates the account list
2538		store.enforce_limits();
2539		assert!(!store.index.read().accounts_to_check_for_expiry_stmts.is_empty());
2540
2541		// Advance time past the expiry of the first statement
2542		store.set_time(1000);
2543
2544		// Second check_expiration should find and expire the statement
2545		store.enforce_limits();
2546
2547		// Naturally-expired statements are not added to the expired map (AlreadyExpired check
2548		// in submit rejects them without consulting the map)
2549		let index = store.index.read();
2550		assert!(
2551			!index.evicted.contains(&expired_hash),
2552			"Naturally expired statement must not be added to the expired map"
2553		);
2554		assert!(
2555			!index.entries.contains_key(&expired_hash),
2556			"Expired statement should be removed from entries"
2557		);
2558
2559		// The valid statement should still be in entries
2560		assert!(
2561			index.entries.contains_key(&valid_hash),
2562			"Valid statement should still be in entries"
2563		);
2564		assert!(!index.evicted.contains(&valid_hash), "Valid statement should not be expired");
2565	}
2566
2567	#[test]
2568	fn check_expiration_removes_checked_accounts_from_list_when_expiring() {
2569		let (mut store, _temp) = test_store();
2570		store.set_time(100);
2571
2572		// Create statements with expiry at timestamp 200
2573		let mut stmt1 = statement(1, 1, None, 100);
2574		stmt1.set_expiry_from_parts(200, 1);
2575		store.submit(stmt1, StatementSource::Network);
2576
2577		let mut stmt2 = statement(2, 1, None, 100);
2578		stmt2.set_expiry_from_parts(200, 1);
2579		store.submit(stmt2, StatementSource::Network);
2580
2581		let mut stmt3 = statement(3, 1, None, 100);
2582		stmt3.set_expiry_from_parts(200, 1);
2583		store.submit(stmt3, StatementSource::Network);
2584
2585		// First call populates the list
2586		store.enforce_limits();
2587		assert_eq!(
2588			store.index.read().accounts_to_check_for_expiry_stmts.len(),
2589			3,
2590			"Should have 3 accounts to check"
2591		);
2592
2593		// Advance time past expiry
2594		store.set_time(300);
2595
2596		// Second call should check accounts, expire statements, and remove checked accounts
2597		store.enforce_limits();
2598
2599		// The list should now be empty (all accounts checked and removed)
2600		assert!(
2601			store.index.read().accounts_to_check_for_expiry_stmts.is_empty(),
2602			"All accounts should have been checked and removed after expiration"
2603		);
2604
2605		// All statements were naturally expired (past their own timestamp), so they are not
2606		// added to the expired map AlreadyExpired check in submit handles re-gossip prevention
2607		assert_eq!(store.index.read().evicted.len(), 0);
2608		assert_eq!(store.index.read().entries.len(), 0);
2609	}
2610
2611	#[test]
2612	fn check_expiration_truncates_list_even_when_nothing_expires() {
2613		let (mut store, _temp) = test_store();
2614		store.set_time(1000);
2615
2616		// Create statements for multiple accounts with far future expiry (using statement helper)
2617		// The statement() helper uses set_expiry_from_parts(u32::MAX, priority) which creates
2618		// a very large expiry value that won't trigger expiration
2619		for acc_id in 1..=5u64 {
2620			let stmt = statement(acc_id, 1, None, 100);
2621			store.submit(stmt, StatementSource::Network);
2622		}
2623
2624		// First call populates the list
2625		store.enforce_limits();
2626		assert_eq!(store.index.read().accounts_to_check_for_expiry_stmts.len(), 5);
2627
2628		// Second call checks accounts and truncates the list (even though nothing expires)
2629		store.enforce_limits();
2630
2631		// The list should now be empty - accounts are removed after being checked
2632		assert!(
2633			store.index.read().accounts_to_check_for_expiry_stmts.is_empty(),
2634			"List should be empty after all accounts have been checked"
2635		);
2636
2637		// No statements should have been expired
2638		assert_eq!(store.index.read().evicted.len(), 0);
2639		assert_eq!(store.index.read().entries.len(), 5);
2640	}
2641
2642	#[test]
2643	fn check_expiration_handles_multiple_statements_per_account() {
2644		let (mut store, _temp) = test_store();
2645		store.set_time(100);
2646
2647		// Create multiple statements for the same account with different expiry timestamps
2648		// Account 42 has limit of 42 statements
2649		let mut stmt1 = statement(42, 1, Some(1), 100);
2650		stmt1.set_expiry_from_parts(200, 1); // Expires at timestamp 200
2651		let hash1 = stmt1.hash();
2652		store.submit(stmt1, StatementSource::Network);
2653
2654		let mut stmt2 = statement(42, 2, Some(2), 100);
2655		stmt2.set_expiry_from_parts(300, 2); // Expires at timestamp 300
2656		let hash2 = stmt2.hash();
2657		store.submit(stmt2, StatementSource::Network);
2658
2659		let mut stmt3 = statement(42, 3, Some(3), 100);
2660		stmt3.set_expiry_from_parts(500, 3); // Expires at timestamp 500
2661		let hash3 = stmt3.hash();
2662		store.submit(stmt3, StatementSource::Network);
2663
2664		// Verify all statements are in the store
2665		assert_eq!(store.index.read().entries.len(), 3);
2666
2667		// First check_expiration populates the account list
2668		store.enforce_limits();
2669
2670		// Advance time to 250 (stmt1 should expire since 250 > 200)
2671		store.set_time(250);
2672		store.enforce_limits();
2673
2674		{
2675			let index = store.index.read();
2676			// Naturally expired statements are not added to the expired map.
2677			assert!(!index.evicted.contains(&hash1), "stmt1 naturally expired, not in map");
2678			assert!(!index.evicted.contains(&hash2), "stmt2 should not be expired yet");
2679			assert!(!index.evicted.contains(&hash3), "stmt3 should not be expired yet");
2680			assert_eq!(index.entries.len(), 2);
2681		}
2682
2683		// Repopulate the account list for next check
2684		store.enforce_limits();
2685
2686		// Advance time to 400 (stmt2 should also expire since 400 > 300)
2687		store.set_time(400);
2688		store.enforce_limits();
2689
2690		{
2691			let index = store.index.read();
2692			assert!(!index.evicted.contains(&hash1));
2693			assert!(!index.evicted.contains(&hash2), "stmt2 naturally expired, not in map");
2694			assert!(!index.evicted.contains(&hash3), "stmt3 should not be expired yet");
2695			assert_eq!(index.entries.len(), 1);
2696		}
2697
2698		// Repopulate and check again at time 600 (stmt3 should expire since 600 > 500)
2699		store.enforce_limits();
2700		store.set_time(600);
2701		store.enforce_limits();
2702
2703		{
2704			let index = store.index.read();
2705			assert!(!index.evicted.contains(&hash1));
2706			assert!(!index.evicted.contains(&hash2));
2707			assert!(!index.evicted.contains(&hash3), "stmt3 naturally expired, not in map");
2708			assert_eq!(index.entries.len(), 0);
2709		}
2710	}
2711
2712	#[test]
2713	fn check_expiration_does_nothing_when_no_expired_statements() {
2714		let (mut store, _temp) = test_store();
2715		store.set_time(1000);
2716
2717		// Create statement with expiry far in the future
2718		// The statement() helper uses set_expiry_from_parts(u32::MAX, priority)
2719		let stmt = statement(1, 1, None, 100);
2720		let hash = stmt.hash();
2721		store.submit(stmt, StatementSource::Network);
2722
2723		// Populate the account list
2724		store.enforce_limits();
2725
2726		// Check expiration - nothing should happen
2727		store.enforce_limits();
2728
2729		// Statement should still be there
2730		let index = store.index.read();
2731		assert!(index.entries.contains_key(&hash));
2732		assert!(!index.evicted.contains(&hash));
2733		assert_eq!(index.entries.len(), 1);
2734		assert_eq!(index.evicted.len(), 0);
2735	}
2736
2737	#[test]
2738	fn check_expiration_correctly_updates_account_data() {
2739		let (mut store, _temp) = test_store();
2740		store.set_time(100);
2741
2742		// Create a statement with expiry at timestamp 200
2743		let mut stmt = statement(1, 1, Some(1), 100);
2744		stmt.set_expiry_from_parts(200, 1);
2745		let hash = stmt.hash();
2746		store.submit(stmt, StatementSource::Network);
2747
2748		// Verify account exists before expiration
2749		{
2750			let index = store.index.read();
2751			assert!(index.accounts.contains_key(&account(1)));
2752			assert_eq!(index.total_size, 100);
2753		}
2754
2755		// Populate and then expire
2756		store.enforce_limits();
2757		store.set_time(300);
2758		store.enforce_limits();
2759
2760		// Verify account is removed after its only statement expires
2761		{
2762			let index = store.index.read();
2763			assert!(
2764				!index.accounts.contains_key(&account(1)),
2765				"Account should be removed when all its statements expire"
2766			);
2767			assert_eq!(index.total_size, 0, "Total size should be zero");
2768			assert!(!index.evicted.contains(&hash), "Naturally expired, not in map");
2769		}
2770	}
2771
2772	#[test]
2773	fn check_expiration_clears_topic_and_key_indexes() {
2774		let (mut store, _temp) = test_store();
2775		store.set_time(100);
2776
2777		// Create a statement with topic and decryption key
2778		let mut stmt = statement(1, 1, Some(1), 100);
2779		stmt.set_expiry_from_parts(200, 1);
2780		stmt.set_topic(0, topic(42));
2781		stmt.set_decryption_key(dec_key(7));
2782		let hash = stmt.hash();
2783		store.submit(stmt, StatementSource::Network);
2784
2785		// Verify indexes are populated
2786		{
2787			let index = store.index.read();
2788			assert!(index.by_topic.get(&topic(42)).map_or(false, |s| s.contains(&hash)));
2789			assert!(index.by_dec_key.get(&Some(dec_key(7))).map_or(false, |s| s.contains(&hash)));
2790		}
2791
2792		// Populate and then expire
2793		store.enforce_limits();
2794		store.set_time(300);
2795		store.enforce_limits();
2796
2797		// Verify indexes are cleared
2798		{
2799			let index = store.index.read();
2800			// Topic set should be empty or removed
2801			assert!(
2802				index.by_topic.get(&topic(42)).map_or(true, |s| s.is_empty()),
2803				"Topic index should be cleared"
2804			);
2805			// Key set should be empty or removed
2806			assert!(
2807				index.by_dec_key.get(&Some(dec_key(7))).map_or(true, |s| s.is_empty()),
2808				"Decryption key index should be cleared"
2809			);
2810			assert!(!index.evicted.contains(&hash), "Naturally expired, not in map");
2811		}
2812	}
2813
2814	#[test]
2815	fn check_expiration_handles_empty_store() {
2816		let (mut store, _temp) = test_store();
2817		store.set_time(1000);
2818
2819		// With no statements, check_expiration should not panic
2820		store.enforce_limits();
2821
2822		// Second call should also work (empty repopulation)
2823		store.enforce_limits();
2824
2825		assert!(store.index.read().accounts_to_check_for_expiry_stmts.is_empty());
2826		assert_eq!(store.index.read().entries.len(), 0);
2827		assert_eq!(store.index.read().evicted.len(), 0);
2828	}
2829
2830	#[test]
2831	fn check_expiration_expires_properly_formatted_statements() {
2832		// With the fix (Expiry(current_time << 32)), check_expiration properly
2833		// compares timestamps and can expire statements submitted through normal flow.
2834
2835		let (mut store, _temp) = test_store();
2836		store.set_time(1000);
2837
2838		// Create a statement with expiration timestamp just 1 second in the future
2839		let mut stmt = statement(1, 1, None, 100);
2840		stmt.set_expiry_from_parts(1001, 1); // Expires at timestamp 1001
2841		let hash = stmt.hash();
2842		store.submit(stmt, StatementSource::Network);
2843
2844		assert_eq!(store.index.read().entries.len(), 1);
2845
2846		// Populate the accounts list
2847		store.enforce_limits();
2848
2849		// Advance time past the expiration timestamp
2850		store.set_time(2000);
2851		store.enforce_limits();
2852
2853		// Statement SHOULD be expired because check_expiration now compares
2854		// Expiry(2000 << 32) against Expiry(1001 << 32 | 1), and
2855		// (2000 << 32) > (1001 << 32 | 1)
2856		let index = store.index.read();
2857		assert!(
2858			!index.entries.contains_key(&hash),
2859			"Statement should be removed from entries after expiration"
2860		);
2861		// Naturally expired: timestamp 1001 < current_time 2000, not added to expired map.
2862		assert!(!index.evicted.contains(&hash), "Naturally expired, not in map");
2863	}
2864
2865	#[test]
2866	fn check_expiration_updates_database_columns() {
2867		// This test verifies that check_expiration properly updates the database.
2868		let (mut store, _temp) = test_store();
2869		store.set_time(100);
2870
2871		// Create a statement with expiry at timestamp 200
2872		let mut stmt = statement(1, 1, None, 100);
2873		stmt.set_expiry_from_parts(200, 1);
2874		let hash = stmt.hash();
2875		store.submit(stmt.clone(), StatementSource::Network);
2876
2877		// Verify statement is in the database
2878		let db_entry = store.db.get(col::STATEMENTS, &hash).unwrap();
2879		assert!(db_entry.is_some(), "Statement should be in col::STATEMENTS after submit");
2880
2881		// Populate the accounts list
2882		store.enforce_limits();
2883
2884		// Advance time past expiry and run check_expiration
2885		store.set_time(300);
2886		store.enforce_limits();
2887
2888		// Verify in-memory state is updated correctly
2889		{
2890			let index = store.index.read();
2891			assert!(
2892				!index.entries.contains_key(&hash),
2893				"Statement should be removed from in-memory entries"
2894			);
2895			// Naturally expired: not added to expired map, no need for suppression.
2896			assert!(
2897				!index.evicted.contains(&hash),
2898				"Naturally expired statement must not be in the expired map"
2899			);
2900		}
2901
2902		let db_entry = store.db.get(col::STATEMENTS, &hash).unwrap();
2903		assert!(
2904			db_entry.is_none(),
2905			"Statement should be removed from col::STATEMENTS after expiration"
2906		);
2907
2908		// Naturally expired statements are not written to col::EXPIRED either, so that
2909		// the optimization survives node restarts.
2910		let expired_entry = store.db.get(col::EXPIRED, &hash).unwrap();
2911		assert!(expired_entry.is_none(), "Naturally expired: not written to col::EXPIRED");
2912	}
2913
2914	#[test]
2915	fn enforce_allowances_evicts_excess_statements() {
2916		// This test verifies that check_expiration correctly evicts statements
2917		// when statements exceed the current allowance. We directly insert into
2918		// the index (bypassing submit's validation) to simulate statements that
2919		// existed before allowances were reduced.
2920		let (mut store, _temp) = test_store();
2921		store.set_time(0);
2922
2923		// Account 4 has allowance (4 statements, 1000 bytes) from TestClient
2924		let s1 = statement(4, 10, None, 100); // lowest priority - will be evicted
2925		let s2 = statement(4, 20, None, 100);
2926		let s3 = statement(4, 30, None, 100);
2927		let s4 = statement(4, 40, None, 100);
2928		let s5 = statement(4, 50, None, 100); // highest priority
2929
2930		let h1 = s1.hash();
2931		let h5 = s5.hash();
2932
2933		// Directly insert into index, bypassing `submit`'s allowance check
2934		{
2935			let mut index = store.index.write();
2936			for s in [&s1, &s2, &s3, &s4, &s5] {
2937				index.insert_new(s.hash(), account(4), s, false);
2938			}
2939		}
2940
2941		// Verify initial state - all 5 should be present
2942		assert_eq!(store.index.read().entries.len(), 5);
2943		assert_eq!(store.index.read().total_size, 500);
2944
2945		// Run check_expiration which handles both expiration and allowance enforcement
2946		// First call populates the accounts list, second call processes them
2947		// Since account 4 has max_count=4, one statement should be evicted
2948		store.enforce_limits();
2949		store.enforce_limits();
2950
2951		// Should evict the lowest priority statement (s1)
2952		let index = store.index.read();
2953		assert_eq!(index.entries.len(), 4, "Should have 4 statements after eviction");
2954		assert!(!index.entries.contains_key(&h1), "Lowest priority should be evicted");
2955		assert!(index.entries.contains_key(&h5), "Highest priority should remain");
2956		assert_eq!(index.total_size, 400);
2957
2958		// Evicted statement should be marked as expired
2959		assert!(index.evicted.contains(&h1));
2960	}
2961
2962	#[test]
2963	fn enforce_allowances_evicts_all_when_no_allowance_found() {
2964		let (mut store, _temp) = test_store();
2965		store.set_time(0);
2966
2967		// Account 0 has NO allowance in TestClient
2968		let s1 = statement(0, 10, None, 100);
2969		let s2 = statement(0, 20, None, 150);
2970
2971		let h1 = s1.hash();
2972		let h2 = s2.hash();
2973
2974		// Directly insert statements for account with no allowance
2975		{
2976			let mut index = store.index.write();
2977			index.insert_new(h1, account(0), &s1, false);
2978			index.insert_new(h2, account(0), &s2, false);
2979		}
2980
2981		assert_eq!(store.index.read().entries.len(), 2);
2982
2983		// Run check_expiration - should evict ALL statements since no allowance exists
2984		// First call populates the accounts list, second call processes them
2985		store.enforce_limits();
2986		store.enforce_limits();
2987
2988		let index = store.index.read();
2989		assert_eq!(index.entries.len(), 0, "All statements should be evicted");
2990		assert!(!index.accounts.contains_key(&account(0)), "Account should be removed");
2991		assert!(index.evicted.contains(&h1));
2992		assert!(index.evicted.contains(&h2));
2993	}
2994
2995	#[test]
2996	fn enforce_allowances_based_on_size() {
2997		// This test verifies that check_expiration evicts based on size limits.
2998		let (mut store, _temp) = test_store();
2999		store.set_time(0);
3000
3001		// Account 2 has allowance (2, 1000) from TestClient
3002		// Insert 2 statements that together exceed 1000 bytes
3003		let s1 = statement(2, 10, None, 600); // lowest priority
3004		let s2 = statement(2, 20, None, 600); // higher priority
3005
3006		let h1 = s1.hash();
3007		let h2 = s2.hash();
3008
3009		// Directly insert both statements (total 1200 bytes > 1000 limit)
3010		{
3011			let mut index = store.index.write();
3012			index.insert_new(h1, account(2), &s1, false);
3013			index.insert_new(h2, account(2), &s2, false);
3014		}
3015
3016		assert_eq!(store.index.read().total_size, 1200);
3017
3018		// Run check_expiration - should evict s1 to get under 1000 bytes
3019		// First call populates the accounts list, second call processes them
3020		store.enforce_limits();
3021		store.enforce_limits();
3022
3023		let index = store.index.read();
3024		assert_eq!(index.entries.len(), 1);
3025		assert!(index.entries.contains_key(&h2), "Higher priority should remain");
3026		assert!(!index.entries.contains_key(&h1), "Lower priority should be evicted");
3027		assert_eq!(index.total_size, 600);
3028	}
3029
3030	#[test]
3031	fn channel_replacement_only_higher_priority_succeeds() {
3032		let (store, _temp) = test_store();
3033		let source = StatementSource::Network;
3034
3035		// Account 1: max_count=1, max_size=1000
3036		// Submit channel 1 with priority 5
3037		let s1 = statement(1, 5, Some(1), 100);
3038		let h1 = s1.hash();
3039		assert_eq!(store.submit(s1, source), SubmitResult::New);
3040
3041		// Lower priority on same channel → ChannelPriorityTooLow
3042		let result = store.submit(statement(1, 3, Some(1), 100), source);
3043		assert!(
3044			matches!(result, SubmitResult::Rejected(RejectionReason::ChannelPriorityTooLow { .. })),
3045			"Lower priority should be rejected with ChannelPriorityTooLow, got: {result:?}"
3046		);
3047
3048		// Equal priority on same channel → ChannelPriorityTooLow (check is <=)
3049		// Use different data_len to get a distinct hash with same priority
3050		let result = store.submit(statement(1, 5, Some(1), 101), source);
3051		assert!(
3052			matches!(result, SubmitResult::Rejected(RejectionReason::ChannelPriorityTooLow { .. })),
3053			"Equal priority should be rejected with ChannelPriorityTooLow, got: {result:?}"
3054		);
3055
3056		// Higher priority on same channel → replaces
3057		let s2 = statement(1, 10, Some(1), 200);
3058		let h2 = s2.hash();
3059		assert_eq!(store.submit(s2, source), SubmitResult::New);
3060
3061		{
3062			let index = store.index.read();
3063			assert_eq!(index.entries.len(), 1);
3064			assert!(!index.entries.contains_key(&h1), "Old channel message should be gone");
3065			assert!(index.entries.contains_key(&h2), "New channel message should exist");
3066			assert!(index.evicted.contains(&h1), "Old should be in expired");
3067			assert_eq!(index.total_size, 200);
3068		}
3069	}
3070
3071	#[test]
3072	fn submit_rejects_malformed_statements() {
3073		let (store, _temp) = test_store();
3074
3075		let mut base = Statement::new();
3076		base.set_expiry(u64::MAX);
3077		base.set_plain_data(vec![1]);
3078
3079		let ed_kp = sp_core::ed25519::Pair::from_string("//Alice", None).unwrap();
3080		let sr_kp = sp_core::sr25519::Pair::from_string("//Alice", None).unwrap();
3081		let ecdsa_kp = sp_core::ecdsa::Pair::from_string("//Alice", None).unwrap();
3082
3083		assert_eq!(
3084			store.submit(base.clone(), StatementSource::Network),
3085			SubmitResult::Invalid(InvalidReason::NoProof)
3086		);
3087
3088		let bad_proofs = [
3089			Proof::Ed25519 { signature: [0xAB; 64], signer: ed_kp.public().0 },
3090			Proof::Sr25519 { signature: [0xCD; 64], signer: sr_kp.public().0 },
3091			Proof::Secp256k1Ecdsa { signature: [0xEF; 65], signer: ecdsa_kp.public().0 },
3092		];
3093		for proof in bad_proofs {
3094			let mut s = base.clone();
3095			s.set_proof(proof);
3096			assert_eq!(
3097				store.submit(s, StatementSource::Network),
3098				SubmitResult::Invalid(InvalidReason::BadProof)
3099			);
3100		}
3101
3102		let mut wrong_signer = base.clone();
3103		wrong_signer.sign_ed25519_private(&ed_kp);
3104		let alice_sig = match wrong_signer.proof().unwrap() {
3105			Proof::Ed25519 { signature, .. } => *signature,
3106			_ => panic!("expected Ed25519 proof after sign_ed25519_private"),
3107		};
3108		let bob_kp = sp_core::ed25519::Pair::from_string("//Bob", None).unwrap();
3109		wrong_signer.set_proof(Proof::Ed25519 { signature: alice_sig, signer: bob_kp.public().0 });
3110		assert_eq!(
3111			store.submit(wrong_signer, StatementSource::Network),
3112			SubmitResult::Invalid(InvalidReason::BadProof)
3113		);
3114	}
3115
3116	#[test]
3117	fn channel_replacement_with_size_increase_evicts_others() {
3118		let (store, _temp) = test_store();
3119		let source = StatementSource::Network;
3120
3121		// Account 3: max_count=3, max_size=1000
3122		// channel msg (200b) + two non-channel msgs (300b each) = 800b
3123		let s_ch = statement(3, 5, Some(1), 200);
3124		let s_low = statement(3, 2, None, 300);
3125		let s_mid = statement(3, 3, None, 300);
3126		let h_ch = s_ch.hash();
3127		let h_low = s_low.hash();
3128		let h_mid = s_mid.hash();
3129
3130		assert_eq!(store.submit(s_ch, source), SubmitResult::New);
3131		assert_eq!(store.submit(s_low, source), SubmitResult::New);
3132		assert_eq!(store.submit(s_mid, source), SubmitResult::New);
3133		assert_eq!(store.index.read().total_size, 800);
3134
3135		// Replace channel with 600b message (priority 10 > 5)
3136		// Must evict lowest priority non-channel statement (priority 2) to fit
3137		let s_ch_big = statement(3, 10, Some(1), 600);
3138		let h_ch_big = s_ch_big.hash();
3139		assert_eq!(store.submit(s_ch_big, source), SubmitResult::New);
3140
3141		{
3142			let index = store.index.read();
3143			assert_eq!(index.entries.len(), 2);
3144			assert!(!index.entries.contains_key(&h_ch), "Old channel message replaced");
3145			assert!(!index.entries.contains_key(&h_low), "Priority 2 evicted to fit size");
3146			assert!(index.entries.contains_key(&h_mid), "Priority 3 should remain");
3147			assert!(index.entries.contains_key(&h_ch_big), "New channel message added");
3148			assert_eq!(index.total_size, 900); // 300 (mid) + 600 (new channel)
3149		}
3150	}
3151
3152	#[test]
3153	fn subscription_reconnect_receives_current_state() {
3154		use crate::StatementStoreSubscriptionApi;
3155		use sp_statement_store::OptimizedTopicFilter;
3156
3157		let (store, _temp) = test_store();
3158		let source = StatementSource::Local;
3159
3160		// Submit 3 statements
3161		for i in 0..3u8 {
3162			let res = store.submit(signed_statement(i), source);
3163			assert_eq!(res, SubmitResult::New);
3164		}
3165
3166		// First subscribe → should get 3 existing statements
3167		let (existing, sender, stream) =
3168			store.subscribe_statement(OptimizedTopicFilter::Any).unwrap();
3169		assert_eq!(existing.len(), 3, "First subscribe should return 3 existing statements");
3170
3171		// Drop stream
3172		drop(stream);
3173		drop(sender);
3174
3175		// Submit 2 more while disconnected
3176		for i in 3..5u8 {
3177			assert_eq!(store.submit(signed_statement(i), source), SubmitResult::New);
3178		}
3179		let (existing, sender, stream) =
3180			store.subscribe_statement(OptimizedTopicFilter::Any).unwrap();
3181		assert_eq!(existing.len(), 5, "Re-subscribe should return all 5 current statements");
3182
3183		// Drop and remove one statement
3184		drop(stream);
3185		drop(sender);
3186		let hash_to_remove = signed_statement(0).hash();
3187		store.remove(&hash_to_remove).unwrap();
3188
3189		// Re-subscribe → should get 4
3190		let (existing, _sender, _stream) =
3191			store.subscribe_statement(OptimizedTopicFilter::Any).unwrap();
3192		assert_eq!(existing.len(), 4, "Re-subscribe after removal should return 4 statements");
3193	}
3194
3195	#[test]
3196	fn subscription_reconnect_with_topic_filter() {
3197		use crate::StatementStoreSubscriptionApi;
3198		use sp_statement_store::OptimizedTopicFilter;
3199
3200		let (store, _temp) = test_store();
3201		let source = StatementSource::Local;
3202		let topic_a = topic(1);
3203		let topic_b = topic(2);
3204
3205		// s1: topic A only
3206		let s1 = signed_statement_with_topics(1, &[topic_a], None);
3207		// s2: topic B only
3208		let s2 = signed_statement_with_topics(2, &[topic_b], None);
3209		// s3: topics A + B
3210		let s3 = signed_statement_with_topics(3, &[topic_a, topic_b], None);
3211
3212		assert_eq!(store.submit(s1, source), SubmitResult::New);
3213		assert_eq!(store.submit(s2, source), SubmitResult::New);
3214		assert_eq!(store.submit(s3, source), SubmitResult::New);
3215
3216		// Subscribe with MatchAll([A]) → s1, s3
3217		let filter_a = OptimizedTopicFilter::MatchAll(std::collections::HashSet::from([topic_a]));
3218		let (existing, sender, stream) = store.subscribe_statement(filter_a.clone()).unwrap();
3219		assert_eq!(existing.len(), 2, "MatchAll([A]) should match s1 and s3");
3220
3221		// Drop and add s4 with topic A
3222		drop(sender);
3223		drop(stream);
3224		let s4 = signed_statement_with_topics(4, &[topic_a], None);
3225		assert_eq!(store.submit(s4, source), SubmitResult::New);
3226		// Re-subscribe with same filter → s1, s3, s4
3227		let (existing, sender, stream) = store.subscribe_statement(filter_a).unwrap();
3228		assert_eq!(existing.len(), 3, "Re-subscribe MatchAll([A]) should return s1, s3, s4");
3229
3230		// Drop and re-subscribe with different filter MatchAll([B]) → s2, s3
3231		drop(sender);
3232		drop(stream);
3233		let filter_b = OptimizedTopicFilter::MatchAll(std::collections::HashSet::from([topic_b]));
3234		let (existing, _sender, _stream) = store.subscribe_statement(filter_b).unwrap();
3235		assert_eq!(existing.len(), 2, "Re-subscribe MatchAll([B]) should return s2 and s3");
3236	}
3237}