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