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