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