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