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