1#![doc = include_str!("../docs/overview.md")]
20#![doc = include_str!("../docs/usage.md")]
21#![warn(missing_docs)]
49#![warn(unused_extern_crates)]
50
51mod metrics;
52mod subscription;
53
54#[cfg(feature = "test-helpers")]
55pub mod subxt_client;
56#[cfg(feature = "test-helpers")]
57pub mod test_utils;
58
59use crate::subscription::{SubscriptionStatementsStream, SubscriptionsHandle};
60use futures::FutureExt;
61use metrics::MetricsLink as PrometheusMetrics;
62use parking_lot::RwLock;
63use prometheus_endpoint::Registry as PrometheusRegistry;
64use sc_client_api::{backend::StorageProvider, Backend, StorageKey};
65use sc_keystore::LocalKeystore;
66use schnellru::{ByLength, LruMap};
67use sp_blockchain::HeaderBackend;
68use sp_core::{crypto::UncheckedFrom, hexdisplay::HexDisplay, traits::SpawnNamed, Decode, Encode};
69use sp_runtime::traits::Block as BlockT;
70use sp_statement_store::{
71 runtime_api::{StatementSource, StatementStoreExt},
72 AccountId, AdmittedBatch, BlockHash, Channel, DecryptionKey, FilterDecision, Hash,
73 InvalidReason, OptimizedTopicFilter, RejectionReason, Result, SignatureVerificationResult,
74 Statement, StatementAllowance, StatementEvent, SubmitResult, Topic,
75};
76pub use sp_statement_store::{Error, StatementStore, MAX_TOPICS};
77use std::{
78 collections::{BTreeMap, HashMap, HashSet},
79 sync::{
80 atomic::{AtomicUsize, Ordering as AtomicOrdering},
81 Arc, Weak,
82 },
83 time::{Duration, Instant},
84};
85pub use subscription::{
86 AddFilterError, MultiFilterEventStream, MultiFilterSubscriptionApi,
87 MultiFilterSubscriptionEvent, StatementStoreSubscriptionApi, SubscriptionHandle,
88 MAX_FILTERS_PER_SUBSCRIPTION,
89};
90use subscription::{ReplayBatch, ReplaySnapshotProvider, REPLAY_CHUNK_RAW_BYTES};
91
92const KEY_VERSION: &[u8] = b"version".as_slice();
93const CURRENT_VERSION: u32 = 2;
94
95const KEY_COUNTERS: &[u8] = b"counters".as_slice();
100
101const MIGRATION_COMMIT_CHUNK: usize = 100_000;
102
103struct DbOperation {
106 column: u8,
107 key: Vec<u8>,
108 value: Option<Vec<u8>>,
109}
110
111impl From<(u8, Vec<u8>, Option<Vec<u8>>)> for DbOperation {
112 fn from((column, key, value): (u8, Vec<u8>, Option<Vec<u8>>)) -> Self {
113 Self { column, key, value }
114 }
115}
116
117struct MigrationBatch<'a> {
118 db: &'a parity_db::Db,
119 operations: Vec<DbOperation>,
120 committed: usize,
121}
122
123impl<'a> MigrationBatch<'a> {
124 fn new(db: &'a parity_db::Db) -> Self {
125 Self { db, operations: Vec::new(), committed: 0 }
126 }
127
128 fn push(&mut self, operation: DbOperation) -> Result<()> {
129 self.operations.push(operation);
130 self.flush_if_full()
131 }
132
133 fn extend(&mut self, operations: impl IntoIterator<Item = DbOperation>) -> Result<()> {
134 self.operations.extend(operations);
135 self.flush_if_full()
136 }
137
138 fn flush_if_full(&mut self) -> Result<()> {
139 if self.operations.len() >= MIGRATION_COMMIT_CHUNK {
140 self.flush()?;
141 }
142 Ok(())
143 }
144
145 fn flush(&mut self) -> Result<()> {
146 if self.operations.is_empty() {
147 return Ok(());
148 }
149 let operations = std::mem::take(&mut self.operations);
150 let count = operations.len();
151 self.db
152 .commit(
153 operations
154 .into_iter()
155 .map(|operation| (operation.column, operation.key, operation.value)),
156 )
157 .map_err(|error| Error::Db(error.to_string()))?;
158 self.committed += count;
159 Ok(())
160 }
161
162 fn finish(mut self) -> Result<usize> {
163 self.flush()?;
164 Ok(self.committed)
165 }
166}
167
168const LOG_TARGET: &str = "statement-store";
169
170pub 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 =
180 sc_network_statement::config::MAX_STATEMENT_NOTIFICATION_SIZE as usize - 1;
181
182const MAX_EXPIRY_STATEMENTS_PER_ITERATION: usize = 10_000;
184const MAX_EXPIRY_ACCOUNTS_PER_ITERATION: usize = 10_000;
186const MAX_EXPIRY_TIME_PER_ITERATION: Duration = Duration::from_millis(100);
188
189const NUM_FILTER_WORKERS: usize = 1;
191
192const MAINTENANCE_PERIOD: std::time::Duration = std::time::Duration::from_secs(29);
193
194enum AllowanceBlock {
196 Best,
198 Finalized,
200}
201
202const ENFORCE_LIMITS_PERIOD: std::time::Duration = std::time::Duration::from_secs(31);
206
207mod col {
208 pub const META: u8 = 0;
209 pub const STATEMENTS: u8 = 1;
210 pub const EXPIRED: u8 = 2;
211 pub const INDEX_BY_TOPIC: u8 = 3;
212 pub const INDEX_BY_DEC_KEY: u8 = 4;
213 pub const INDEX_EVICTED: u8 = 5;
214 pub const ADMISSION_SEQ: u8 = 6;
215 pub const INDEX_BY_ACCOUNT: u8 = 7;
216 pub const INDEX_BY_EXPIRY: u8 = 8;
217
218 pub const COUNT: u8 = 9;
219}
220
221const BTREE_COLUMNS: [u8; 6] = [
223 col::INDEX_BY_TOPIC,
224 col::INDEX_BY_DEC_KEY,
225 col::INDEX_EVICTED,
226 col::ADMISSION_SEQ,
227 col::INDEX_BY_ACCOUNT,
228 col::INDEX_BY_EXPIRY,
229];
230
231#[cfg(not(test))]
235const DETAILS_CACHE_BUDGET: usize = 65_536;
236#[cfg(test)]
237const DETAILS_CACHE_BUDGET: usize = 8;
238
239#[cfg(not(test))]
241const SUMMARY_CACHE_ACCOUNTS: u32 = 131_072;
242#[cfg(test)]
243const SUMMARY_CACHE_ACCOUNTS: u32 = 4;
244
245const INDEX_EMPTY_VALUE: &[u8] = &[];
246const DEC_KEY_TAG_NONE: u8 = 0;
247const DEC_KEY_TAG_SOME: u8 = 1;
248
249fn topic_index_key(topic: &Topic, hash: &Hash) -> Vec<u8> {
250 let mut key = Vec::with_capacity(topic.len() + hash.len());
251 key.extend_from_slice(&topic[..]);
252 key.extend_from_slice(&hash[..]);
253 key
254}
255
256fn dec_key_index_prefix(dec_key: &Option<DecryptionKey>) -> Vec<u8> {
257 match dec_key {
258 None => vec![DEC_KEY_TAG_NONE],
259 Some(dec_key) => {
260 let mut prefix = Vec::with_capacity(1 + dec_key.len());
261 prefix.push(DEC_KEY_TAG_SOME);
262 prefix.extend_from_slice(&dec_key[..]);
263 prefix
264 },
265 }
266}
267
268fn dec_key_index_key(dec_key: &Option<DecryptionKey>, hash: &Hash) -> Vec<u8> {
269 let mut key = dec_key_index_prefix(dec_key);
270 key.extend_from_slice(&hash[..]);
271 key
272}
273
274fn parse_dec_key_index_prefix(prefix: &[u8]) -> Option<Option<DecryptionKey>> {
276 match prefix.split_first() {
277 Some((&DEC_KEY_TAG_NONE, [])) => Some(None),
278 Some((&DEC_KEY_TAG_SOME, dec_key)) if dec_key.len() == size_of::<DecryptionKey>() => {
279 dec_key.try_into().ok().map(Some)
280 },
281 _ => {
282 log::error!(
283 target: LOG_TARGET,
284 "Corrupt decryption-key index prefix: {:?}",
285 HexDisplay::from(&prefix)
286 );
287 None
288 },
289 }
290}
291
292fn evicted_index_key(purge_at: u64, hash: &Hash) -> Vec<u8> {
293 let mut key = Vec::with_capacity(8 + hash.len());
294 key.extend_from_slice(&purge_at.to_be_bytes());
295 key.extend_from_slice(&hash[..]);
296 key
297}
298
299fn account_index_key(account: &AccountId, expiry: Expiry, hash: &Hash) -> Vec<u8> {
301 let mut key = Vec::with_capacity(account.len() + size_of::<u64>() + hash.len());
302 key.extend_from_slice(&account[..]);
303 key.extend_from_slice(&expiry.0.to_be_bytes());
304 key.extend_from_slice(&hash[..]);
305 key
306}
307
308fn parse_account_index_key(key: &[u8]) -> Option<(AccountId, Expiry, Hash)> {
310 if key.len() != size_of::<AccountId>() + size_of::<u64>() + size_of::<Hash>() {
311 log::error!(
312 target: LOG_TARGET,
313 "Corrupt account index key: {:?}",
314 HexDisplay::from(&key)
315 );
316 return None;
317 }
318 let (account, rest) = key.split_at(size_of::<AccountId>());
319 let (expiry, hash) = rest.split_at(size_of::<u64>());
320 Some((
321 account.try_into().ok()?,
322 Expiry(u64::from_be_bytes(expiry.try_into().ok()?)),
323 hash.try_into().ok()?,
324 ))
325}
326
327fn expiry_index_key(expiry: Expiry, hash: &Hash) -> Vec<u8> {
330 let mut key = Vec::with_capacity(size_of::<u64>() + hash.len());
331 key.extend_from_slice(&expiry.0.to_be_bytes());
332 key.extend_from_slice(&hash[..]);
333 key
334}
335
336fn parse_time_index_key(key: &[u8]) -> Option<(u64, Hash)> {
339 if key.len() != size_of::<u64>() + size_of::<Hash>() {
340 log::error!(target: LOG_TARGET, "Corrupt time index key: {:?}", HexDisplay::from(&key));
341 return None;
342 }
343 let (time, hash) = key.split_at(size_of::<u64>());
344 Some((u64::from_be_bytes(time.try_into().ok()?), hash.try_into().ok()?))
345}
346
347#[derive(Clone, Copy, PartialEq, Eq, Debug)]
350struct EntryDetails {
351 channel: Option<Channel>,
352 data_len: usize,
353 admission_seq: u64,
354}
355
356impl Encode for EntryDetails {
357 fn size_hint(&self) -> usize {
358 self.channel.size_hint() +
359 (self.data_len as u32).size_hint() +
360 self.admission_seq.size_hint()
361 }
362
363 fn encode_to<T: codec::Output + ?Sized>(&self, dest: &mut T) {
364 self.channel.encode_to(dest);
365 (self.data_len as u32).encode_to(dest);
366 self.admission_seq.encode_to(dest);
367 }
368}
369
370impl Decode for EntryDetails {
371 fn decode<I: codec::Input>(input: &mut I) -> std::result::Result<Self, codec::Error> {
372 let (channel, data_len, admission_seq) = <(Option<Channel>, u32, u64)>::decode(input)?;
373 Ok(EntryDetails { channel, data_len: data_len as usize, admission_seq })
374 }
375}
376
377fn account_index_ops(
380 account: &AccountId,
381 expiry: Expiry,
382 hash: &Hash,
383 details: Option<&EntryDetails>,
384) -> [(u8, Vec<u8>, Option<Vec<u8>>); 2] {
385 [
386 (
387 col::INDEX_BY_ACCOUNT,
388 account_index_key(account, expiry, hash),
389 details.map(EntryDetails::encode),
390 ),
391 (
392 col::INDEX_BY_EXPIRY,
393 expiry_index_key(expiry, hash),
394 details.map(|_| INDEX_EMPTY_VALUE.to_vec()),
395 ),
396 ]
397}
398
399fn next_account_id(mut account: AccountId) -> Option<AccountId> {
402 for byte in account.iter_mut().rev() {
403 match byte.checked_add(1) {
404 Some(incremented) => {
405 *byte = incremented;
406 return Some(account);
407 },
408 None => *byte = 0,
409 }
410 }
411 None
412}
413
414fn hash_from_index_key(key: &[u8]) -> Option<Hash> {
416 let Some(prefix_len) = key.len().checked_sub(size_of::<Hash>()) else {
417 log::error!(target: LOG_TARGET, "Corrupt index key: {:?}", HexDisplay::from(&key));
418 return None;
419 };
420 key[prefix_len..].try_into().ok()
421}
422
423fn prefix_from_index_key(key: &[u8]) -> Option<&[u8]> {
426 let Some(prefix_len) = key.len().checked_sub(size_of::<Hash>()) else {
427 log::error!(target: LOG_TARGET, "Corrupt index key: {:?}", HexDisplay::from(&key));
428 return None;
429 };
430 Some(&key[..prefix_len])
431}
432
433fn statement_index_ops(
437 hash: &Hash,
438 statement: &Statement,
439 insert: bool,
440) -> Vec<(u8, Vec<u8>, Option<Vec<u8>>)> {
441 let value = insert.then(|| INDEX_EMPTY_VALUE.to_vec());
442 let mut ops = Vec::new();
443 let mut nt = 0;
444 while let Some(topic) = statement.topic(nt) {
445 ops.push((col::INDEX_BY_TOPIC, topic_index_key(&topic, hash), value.clone()));
446 nt += 1;
447 }
448 let dec_key = statement.decryption_key();
449 ops.push((col::INDEX_BY_DEC_KEY, dec_key_index_key(&dec_key, hash), value));
450 ops
451}
452
453#[derive(Eq, PartialEq, Debug, Ord, PartialOrd, Clone, Copy)]
454struct Expiry(u64);
455
456impl Expiry {
457 fn get_expiration_timestamp_secs(self) -> u64 {
459 self.0 >> 32
460 }
461}
462
463#[derive(PartialEq, Eq, Clone, Copy)]
464struct PriorityKey {
465 hash: Hash,
466 expiry: Expiry,
467}
468
469impl PartialOrd for PriorityKey {
470 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
471 Some(self.cmp(other))
472 }
473}
474
475impl Ord for PriorityKey {
476 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
477 self.expiry.cmp(&other.expiry).then_with(|| self.hash.cmp(&other.hash))
478 }
479}
480
481#[derive(PartialEq, Eq)]
482struct ChannelEntry {
483 hash: Hash,
484 expiry: Expiry,
485}
486
487#[derive(Default)]
488struct StatementsForAccount {
489 by_priority: BTreeMap<PriorityKey, EntryDetails>,
491 channels: HashMap<Channel, ChannelEntry>,
493 data_size: usize,
495}
496
497impl StatementsForAccount {
498 fn insert_entry(&mut self, hash: Hash, expiry: Expiry, details: EntryDetails) {
499 self.data_size += details.data_len;
500 if let Some(channel) = details.channel {
501 self.channels.insert(channel, ChannelEntry { hash, expiry });
502 }
503 self.by_priority.insert(PriorityKey { hash, expiry }, details);
504 }
505
506 fn remove_entry(&mut self, key: &PriorityKey) -> Option<EntryDetails> {
507 let details = self.by_priority.remove(key)?;
508 self.data_size -= details.data_len;
509 if let Some(channel) = details.channel {
510 self.channels.remove(&channel);
511 }
512 Some(details)
513 }
514}
515
516pub const DEFAULT_NETWORK_WORKERS: usize = 1;
518
519pub use sc_network_statement::config::DEFAULT_STATEMENTS_PER_SECOND as DEFAULT_RATE_LIMIT;
521
522#[derive(Debug, Clone, Copy)]
524pub struct Config {
525 pub max_total_statements: usize,
528 pub max_total_size: usize,
531 pub purge_after_sec: u64,
533 pub network_workers: usize,
535 pub rate_limit: u32,
537}
538
539impl Config {
540 pub fn validate(&self) -> Result<()> {
542 if self.max_total_statements == 0 {
543 return Err(Error::InvalidConfig(
544 "max_total_statements must be greater than zero".into(),
545 ));
546 }
547 if self.max_total_size == 0 {
548 return Err(Error::InvalidConfig("max_total_size must be greater than zero".into()));
549 }
550 if self.network_workers == 0 {
551 return Err(Error::InvalidConfig("network_workers must be greater than zero".into()));
552 }
553 Ok(())
554 }
555}
556
557impl Default for Config {
558 fn default() -> Self {
559 Config {
560 max_total_statements: DEFAULT_MAX_TOTAL_STATEMENTS,
561 max_total_size: DEFAULT_MAX_TOTAL_SIZE,
562 purge_after_sec: DEFAULT_PURGE_AFTER_SEC,
563 network_workers: DEFAULT_NETWORK_WORKERS,
564 rate_limit: DEFAULT_RATE_LIMIT,
565 }
566 }
567}
568
569struct QueryIndex {
571 topic_counts: HashMap<Topic, usize>,
573 dec_key_counts: HashMap<Option<DecryptionKey>, usize>,
574 recent: HashMap<Hash, u64>,
575}
576
577impl QueryIndex {
578 fn new() -> Self {
579 QueryIndex {
580 topic_counts: HashMap::new(),
581 dec_key_counts: HashMap::new(),
582 recent: HashMap::new(),
583 }
584 }
585
586 fn note_insert(&mut self, hash: Hash, statement: &Statement, seq: u64) {
589 let mut nt = 0;
590 while let Some(topic) = statement.topic(nt) {
591 *self.topic_counts.entry(topic).or_insert(0) += 1;
592 nt += 1;
593 }
594 let dec_key = statement.decryption_key();
595 *self.dec_key_counts.entry(dec_key).or_insert(0) += 1;
596 self.recent.insert(hash, seq);
597 }
598
599 fn note_remove(&mut self, hash: &Hash, statement: &Statement) {
601 let mut nt = 0;
602 while let Some(topic) = statement.topic(nt) {
603 if let Some(count) = self.topic_counts.get_mut(&topic) {
604 *count = count.saturating_sub(1);
605 if *count == 0 {
606 self.topic_counts.remove(&topic);
607 }
608 }
609 nt += 1;
610 }
611 let dec_key = statement.decryption_key();
612 if let Some(count) = self.dec_key_counts.get_mut(&dec_key) {
613 *count = count.saturating_sub(1);
614 if *count == 0 {
615 self.dec_key_counts.remove(&dec_key);
616 }
617 }
618 self.recent.remove(hash);
619 }
620
621 fn take_recent(&mut self) -> HashMap<Hash, u64> {
623 std::mem::take(&mut self.recent)
624 }
625}
626
627#[derive(Clone, Copy)]
629struct AccountSummary {
630 count: usize,
631 data_size: usize,
632}
633
634struct SubmitIndex {
640 account_statements: LruMap<AccountId, StatementsForAccount>,
643 cached_statement_count: usize,
645 summaries: LruMap<AccountId, AccountSummary>,
649 allowance_cursor: Option<AccountId>,
652 allowance_cycle_seen: usize,
654 config: Config,
656 statement_count: usize,
658 total_size: usize,
660 evicted_count: usize,
661 next_seq: u64,
663 recent_seqs: HashMap<Hash, u64>,
666 active_scan_floors: BTreeMap<u64, usize>,
668}
669
670struct ClientWrapper<Block, Client, BE> {
671 client: Arc<Client>,
672 _block: std::marker::PhantomData<Block>,
673 _backend: std::marker::PhantomData<BE>,
674}
675
676impl<Block, Client, BE> ClientWrapper<Block, Client, BE>
677where
678 Block: BlockT,
679 Block::Hash: From<BlockHash>,
680 BE: Backend<Block> + 'static,
681 Client: HeaderBackend<Block> + StorageProvider<Block, BE> + Send + Sync + 'static,
682{
683 fn read_allowance(
684 &self,
685 account_id: &AccountId,
686 allowance_block: AllowanceBlock,
687 ) -> Result<Option<StatementAllowance>> {
688 use sp_statement_store::{statement_allowance_key, StatementAllowance};
689
690 let block_hash = match allowance_block {
691 AllowanceBlock::Best => self.client.info().best_hash,
692 AllowanceBlock::Finalized => self.client.info().finalized_hash,
693 };
694 let key = statement_allowance_key(account_id);
695 let storage_key = StorageKey(key);
696 self.client
697 .storage(block_hash, &storage_key)
698 .map_err(|e| Error::Storage(format!("Failed to read allowance: {:?}", e)))?
699 .map(|value| {
700 StatementAllowance::decode(&mut &value.0[..])
701 .map_err(|e| Error::Decode(format!("Failed to decode allowance: {:?}", e)))
702 })
703 .transpose()
704 }
705}
706
707pub struct Store {
709 db: parity_db::Db,
710 submit_index: RwLock<SubmitIndex>,
711 query_index: RwLock<QueryIndex>,
712 read_allowance_fn:
713 Box<dyn Fn(&AccountId, AllowanceBlock) -> Result<Option<StatementAllowance>> + Send + Sync>,
714 subscription_manager: SubscriptionsHandle,
715 keystore: Arc<LocalKeystore>,
716 known_accounts_count: AtomicUsize,
719 time_override: Option<u64>,
721 metrics: PrometheusMetrics,
722}
723
724impl ReplaySnapshotProvider for Weak<Store> {
725 fn register_replay(&self, enqueue: &mut dyn FnMut(u64) -> bool) -> Result<Option<u64>> {
726 let Some(store) = self.upgrade() else {
727 return Err(Error::InvalidConfig("statement store is closed".into()));
728 };
729 store.register_replay(enqueue)
730 }
731
732 fn replay_batch(
733 &self,
734 filter: &OptimizedTopicFilter,
735 cursor: u64,
736 watermark: u64,
737 ) -> Result<ReplayBatch> {
738 let Some(store) = self.upgrade() else {
739 return Err(Error::InvalidConfig("statement store is closed".into()));
740 };
741 store.replay_batch(filter, cursor, watermark)
742 }
743}
744
745struct InsertPlan {
748 seq: u64,
750 evicted: Vec<(PriorityKey, EntryDetails)>,
753 banned: Vec<(Hash, u64)>,
756}
757
758#[derive(Clone)]
761enum IndexSet {
762 Topic(Topic),
763 DecKey(Option<DecryptionKey>),
764}
765
766impl IndexSet {
767 fn column(&self) -> u8 {
768 match self {
769 IndexSet::Topic(_) => col::INDEX_BY_TOPIC,
770 IndexSet::DecKey(_) => col::INDEX_BY_DEC_KEY,
771 }
772 }
773
774 fn prefix(&self) -> Vec<u8> {
776 match self {
777 IndexSet::Topic(t) => t[..].to_vec(),
778 IndexSet::DecKey(k) => dec_key_index_prefix(k),
779 }
780 }
781
782 fn member_key(&self, hash: &Hash) -> Vec<u8> {
784 match self {
785 IndexSet::Topic(t) => topic_index_key(t, hash),
786 IndexSet::DecKey(k) => dec_key_index_key(k, hash),
787 }
788 }
789
790 fn len(&self, read_index: &QueryIndex) -> usize {
792 match self {
793 IndexSet::Topic(t) => read_index.topic_counts.get(t).copied().unwrap_or(0),
794 IndexSet::DecKey(k) => read_index.dec_key_counts.get(k).copied().unwrap_or(0),
795 }
796 }
797}
798
799impl SubmitIndex {
800 fn new(config: Config) -> SubmitIndex {
801 SubmitIndex {
802 account_statements: LruMap::new(ByLength::new(u32::MAX)),
807 summaries: LruMap::new(ByLength::new(SUMMARY_CACHE_ACCOUNTS)),
808 config,
809 statement_count: 0,
810 total_size: 0,
811 next_seq: 0,
812 cached_statement_count: 0,
813 allowance_cursor: None,
814 allowance_cycle_seen: 0,
815 evicted_count: 0,
816 recent_seqs: HashMap::new(),
817 active_scan_floors: BTreeMap::new(),
818 }
819 }
820
821 fn counters_op(
824 statement_count: usize,
825 total_size: usize,
826 next_seq: u64,
827 ) -> (u8, Vec<u8>, Option<Vec<u8>>) {
828 (
829 col::META,
830 KEY_COUNTERS.to_vec(),
831 Some((statement_count as u64, total_size as u64, next_seq).encode()),
832 )
833 }
834
835 fn uncache_record(&mut self, account: &AccountId) -> Option<StatementsForAccount> {
837 let record = self.account_statements.remove(account)?;
838 self.cached_statement_count -= record.by_priority.len();
839 Some(record)
840 }
841
842 fn cache_record(&mut self, account: AccountId, record: StatementsForAccount) {
846 if record.by_priority.is_empty() {
847 return;
848 }
849 self.cached_statement_count += record.by_priority.len();
850 self.account_statements.insert(account, record);
851 while self.cached_statement_count > DETAILS_CACHE_BUDGET &&
852 self.account_statements.len() > 1
853 {
854 match self.account_statements.pop_oldest() {
855 Some((_, evicted)) => self.cached_statement_count -= evicted.by_priority.len(),
856 None => break,
857 }
858 }
859 }
860
861 fn cache_record_with_summary(&mut self, account: AccountId, record: StatementsForAccount) {
865 if !record.by_priority.is_empty() {
866 self.summaries.insert(
867 account,
868 AccountSummary { count: record.by_priority.len(), data_size: record.data_size },
869 );
870 }
871 self.cache_record(account, record);
872 }
873
874 fn note_seq(&mut self, hash: Hash, seq: u64) {
877 if !self.active_scan_floors.is_empty() {
878 self.recent_seqs.insert(hash, seq);
879 }
880 }
881
882 fn begin_scan(&mut self) -> u64 {
884 let watermark = self.next_seq;
885 *self.active_scan_floors.entry(watermark).or_insert(0) += 1;
886 watermark
887 }
888
889 fn end_scan(&mut self, watermark: u64) {
892 if let Some(count) = self.active_scan_floors.get_mut(&watermark) {
893 *count -= 1;
894 if *count == 0 {
895 self.active_scan_floors.remove(&watermark);
896 }
897 }
898 match self.active_scan_floors.keys().next() {
899 Some(&floor) => self.recent_seqs.retain(|_, seq| *seq >= floor),
900 None => self.recent_seqs.clear(),
901 }
902 }
903
904 fn seq_covered_by_snapshot(&self, hash: &Hash, watermark: u64) -> bool {
906 match self.recent_seqs.get(hash) {
907 Some(&seq) => seq < watermark,
908 None => true,
909 }
910 }
911
912 fn plan_insert(
918 &self,
919 record: &StatementsForAccount,
920 hash: Hash,
921 statement: &Statement,
922 account: &AccountId,
923 validation: &StatementAllowance,
924 current_time: u64,
925 ) -> std::result::Result<InsertPlan, RejectionReason> {
926 let statement_len = statement.data_len();
927 if statement_len > validation.max_size as usize {
928 log::debug!(
929 target: LOG_TARGET,
930 "Ignored oversize message from account {}: {:?} ({} bytes)",
931 HexDisplay::from(account),
932 HexDisplay::from(&hash),
933 statement_len,
934 );
935 return Err(RejectionReason::DataTooLarge {
936 submitted_size: statement_len,
937 available_size: validation.max_size as usize,
938 });
939 }
940
941 let mut evicted: Vec<(PriorityKey, EntryDetails)> = Vec::new();
942 let mut evicted_hashes = HashSet::new();
943 let mut would_free_size = 0;
944 let expiry = Expiry(statement.expiry());
945 let (max_size, max_count) = (validation.max_size as usize, validation.max_count as usize);
946 if let Some(channel) = statement.channel() {
950 if let Some(channel_record) = record.channels.get(&channel) {
951 if expiry <= channel_record.expiry {
952 log::debug!(
954 target: LOG_TARGET,
955 "Ignored lower priority channel message from account {}: {:?} {:?} <= {:?}",
956 HexDisplay::from(account),
957 HexDisplay::from(&hash),
958 expiry,
959 channel_record.expiry,
960 );
961 return Err(RejectionReason::ChannelPriorityTooLow {
962 submitted_expiry: expiry.0,
963 min_expiry: channel_record.expiry.0,
964 });
965 } else {
966 log::debug!(
969 target: LOG_TARGET,
970 "Replacing higher priority channel message from account {}: {:?} ({:?}) > {:?} ({:?})",
971 HexDisplay::from(account),
972 HexDisplay::from(&hash),
973 expiry,
974 HexDisplay::from(&channel_record.hash),
975 channel_record.expiry,
976 );
977 let key =
978 PriorityKey { hash: channel_record.hash, expiry: channel_record.expiry };
979 if let Some(details) = record.by_priority.get(&key) {
980 would_free_size += details.data_len;
981 evicted_hashes.insert(key.hash);
982 evicted.push((key, *details));
983 }
984 }
985 }
986 }
987 for (entry, details) in record.by_priority.iter() {
989 if (record.data_size - would_free_size + statement_len <= max_size) &&
990 record.by_priority.len() + 1 - evicted_hashes.len() <= max_count
991 {
992 break;
994 }
995 if evicted_hashes.contains(&entry.hash) {
996 continue;
998 }
999 if entry.expiry >= expiry {
1000 log::debug!(
1001 target: LOG_TARGET,
1002 "Ignored message from account {} due to constraints {:?} {:?} < {:?}",
1003 HexDisplay::from(account),
1004 HexDisplay::from(&hash),
1005 expiry,
1006 entry.expiry,
1007 );
1008 let retained_size = record.data_size - would_free_size;
1009 if retained_size + statement_len > max_size {
1010 return Err(RejectionReason::DataTooLarge {
1011 submitted_size: statement_len,
1012 available_size: max_size.saturating_sub(retained_size),
1013 });
1014 }
1015 return Err(RejectionReason::AccountFull {
1016 submitted_expiry: expiry.0,
1017 min_expiry: entry.expiry.0,
1018 });
1019 }
1020 evicted_hashes.insert(entry.hash);
1021 would_free_size += details.data_len;
1022 evicted.push((*entry, *details));
1023 }
1024 if !((self.total_size - would_free_size + statement_len <= self.config.max_total_size) &&
1026 self.statement_count + 1 - evicted.len() <= self.config.max_total_statements)
1027 {
1028 log::debug!(
1029 target: LOG_TARGET,
1030 "Ignored statement {} from account {} because the store is full (size={}, count={})",
1031 HexDisplay::from(&hash),
1032 HexDisplay::from(account),
1033 self.total_size,
1034 self.statement_count,
1035 );
1036 return Err(RejectionReason::StoreFull);
1037 }
1038
1039 let banned = evicted
1040 .iter()
1041 .filter_map(|(key, _)| {
1042 let expiry_ts = key.expiry.get_expiration_timestamp_secs();
1043 (current_time < expiry_ts).then(|| {
1044 (
1045 key.hash,
1046 expiry_ts.min(current_time.saturating_add(self.config.purge_after_sec)),
1047 )
1048 })
1049 })
1050 .collect();
1051 Ok(InsertPlan { seq: self.next_seq, evicted, banned })
1052 }
1053
1054 fn apply_insert(
1059 &mut self,
1060 account: &AccountId,
1061 loaded_record: Option<StatementsForAccount>,
1062 hash: Hash,
1063 statement: &Statement,
1064 plan: &InsertPlan,
1065 ) {
1066 let statement_len = statement.data_len();
1067 let freed: usize = plan.evicted.iter().map(|(_, details)| details.data_len).sum();
1068 self.statement_count = self.statement_count + 1 - plan.evicted.len();
1069 self.total_size = self.total_size + statement_len - freed;
1070 self.evicted_count += plan.banned.len();
1071 self.next_seq = plan.seq.saturating_add(1);
1072 self.note_seq(hash, plan.seq);
1073 for (key, _) in &plan.evicted {
1074 log::trace!(target: LOG_TARGET, "Expired statement {:?}", HexDisplay::from(&key.hash));
1075 }
1076 let details = EntryDetails {
1077 channel: statement.channel(),
1078 data_len: statement_len,
1079 admission_seq: plan.seq,
1080 };
1081 match loaded_record.or_else(|| self.uncache_record(account)) {
1082 Some(mut record) => {
1083 for (key, _) in &plan.evicted {
1084 record.remove_entry(key);
1085 }
1086 record.insert_entry(hash, Expiry(statement.expiry()), details);
1087 self.cache_record_with_summary(*account, record);
1088 },
1089 None => {
1090 assert!(plan.evicted.is_empty());
1092 if let Some(summary) = self.summaries.get(account) {
1093 summary.count += 1;
1094 summary.data_size += statement_len;
1095 }
1096 },
1097 }
1098 }
1099
1100 fn apply_removal(
1102 &mut self,
1103 account: &AccountId,
1104 key: &PriorityKey,
1105 data_len: usize,
1106 banned: bool,
1107 ) {
1108 self.statement_count = self.statement_count.saturating_sub(1);
1109 self.total_size = self.total_size.saturating_sub(data_len);
1110 if banned {
1111 self.evicted_count += 1;
1112 }
1113 if let Some(record) = self.account_statements.peek_mut(account) {
1114 if record.remove_entry(key).is_some() {
1115 self.cached_statement_count -= 1;
1116 }
1117 if record.by_priority.is_empty() {
1118 self.account_statements.remove(account);
1119 }
1120 }
1121 if let Some(summary) = self.summaries.peek_mut(account) {
1122 summary.count = summary.count.saturating_sub(1);
1123 summary.data_size = summary.data_size.saturating_sub(data_len);
1124 if summary.count == 0 {
1125 self.summaries.remove(account);
1126 }
1127 }
1128 }
1129
1130 fn apply_account_removal(
1132 &mut self,
1133 account: &AccountId,
1134 removed_count: usize,
1135 freed_size: usize,
1136 banned_count: usize,
1137 ) {
1138 self.statement_count = self.statement_count.saturating_sub(removed_count);
1139 self.total_size = self.total_size.saturating_sub(freed_size);
1140 self.evicted_count += banned_count;
1141 if let Some(record) = self.account_statements.remove(account) {
1142 self.cached_statement_count -= record.by_priority.len();
1143 }
1144 self.summaries.remove(account);
1145 }
1146}
1147
1148impl Store {
1149 pub fn new_shared<Block, Client, BE>(
1152 path: &std::path::Path,
1153 config: Config,
1154 client: Arc<Client>,
1155 keystore: Arc<LocalKeystore>,
1156 prometheus: Option<&PrometheusRegistry>,
1157 task_spawner: Box<dyn SpawnNamed>,
1158 ) -> Result<Arc<Store>>
1159 where
1160 Block: BlockT,
1161 Block::Hash: From<BlockHash>,
1162 BE: Backend<Block> + 'static,
1163 Client: HeaderBackend<Block> + StorageProvider<Block, BE> + Send + Sync + 'static,
1164 {
1165 let store =
1166 Arc::new(Self::new(path, config, client, keystore, prometheus, task_spawner.clone())?);
1167
1168 let worker_store = store.clone();
1170 task_spawner.spawn(
1171 "statement-store-maintenance",
1172 Some("statement-store"),
1173 Box::pin(async move {
1174 let mut maintenance_interval = tokio::time::interval(MAINTENANCE_PERIOD);
1175 let mut enforce_limits_interval = tokio::time::interval(ENFORCE_LIMITS_PERIOD);
1176 loop {
1177 futures::select! {
1178 _ = maintenance_interval.tick().fuse() => {worker_store.maintain();}
1179 _ = enforce_limits_interval.tick().fuse() => {worker_store.enforce_limits();}
1180 }
1181 }
1182 }),
1183 );
1184
1185 Ok(store)
1186 }
1187
1188 #[doc(hidden)]
1191 pub fn new<Block, Client, BE>(
1192 path: &std::path::Path,
1193 config: Config,
1194 client: Arc<Client>,
1195 keystore: Arc<LocalKeystore>,
1196 prometheus: Option<&PrometheusRegistry>,
1197 task_spawner: Box<dyn SpawnNamed>,
1198 ) -> Result<Store>
1199 where
1200 Block: BlockT,
1201 Block::Hash: From<BlockHash>,
1202 BE: Backend<Block> + 'static,
1203 Client: HeaderBackend<Block> + StorageProvider<Block, BE> + Send + Sync + 'static,
1204 {
1205 config.validate()?;
1206
1207 let mut path: std::path::PathBuf = path.into();
1208 path.push("statements");
1209
1210 Self::migrate_columns(&path)?;
1211 let db = Self::open_db(&path)?;
1212 let needs_index_migration = Self::check_db_version(&db)?;
1213
1214 let storage_reader =
1215 ClientWrapper { client, _block: Default::default(), _backend: Default::default() };
1216 let read_allowance_fn =
1217 Box::new(move |account_id: &AccountId, allowance_block: AllowanceBlock| {
1218 storage_reader.read_allowance(account_id, allowance_block)
1219 });
1220
1221 let store = Store {
1222 db,
1223 submit_index: RwLock::new(SubmitIndex::new(config)),
1224 query_index: RwLock::new(QueryIndex::new()),
1225 read_allowance_fn,
1226 keystore,
1227 known_accounts_count: AtomicUsize::new(0),
1228 time_override: None,
1229 metrics: PrometheusMetrics::new(prometheus),
1230 subscription_manager: SubscriptionsHandle::new(
1231 task_spawner.clone(),
1232 NUM_FILTER_WORKERS,
1233 ),
1234 };
1235 store.populate(needs_index_migration)?;
1236 Ok(store)
1237 }
1238
1239 fn migrate_columns(path: &std::path::Path) -> Result<()> {
1241 let Some(metadata) =
1242 parity_db::Options::load_metadata(path).map_err(|e| Error::Db(e.to_string()))?
1243 else {
1244 return Ok(());
1245 };
1246 if metadata.columns.len() >= col::COUNT as usize {
1247 return Ok(());
1248 }
1249 let mut migrate_config = parity_db::Options::with_columns(path, 0);
1250 migrate_config.columns = metadata.columns;
1251 while migrate_config.columns.len() < col::COUNT as usize {
1252 let mut new_column_options = parity_db::ColumnOptions::default();
1254 let column = migrate_config.columns.len() as u8;
1255 new_column_options.btree_index = BTREE_COLUMNS.contains(&column);
1256 parity_db::Db::add_column(&mut migrate_config, new_column_options)
1257 .map_err(|e| Error::Db(e.to_string()))?;
1258 }
1259 Ok(())
1260 }
1261
1262 fn open_db(path: &std::path::Path) -> Result<parity_db::Db> {
1265 let mut db_config = parity_db::Options::with_columns(path, col::COUNT);
1266 let statement_col = &mut db_config.columns[col::STATEMENTS as usize];
1267 statement_col.ref_counted = false;
1268 statement_col.preimage = true;
1269 statement_col.uniform = true;
1270 for c in BTREE_COLUMNS {
1271 db_config.columns[c as usize].btree_index = true;
1272 }
1273 parity_db::Db::open_or_create(&db_config).map_err(|e| Error::Db(e.to_string()))
1274 }
1275
1276 fn check_db_version(db: &parity_db::Db) -> Result<bool> {
1282 match db.get(col::META, &KEY_VERSION).map_err(|e| Error::Db(e.to_string()))? {
1283 Some(version) => {
1284 let version = u32::from_le_bytes(
1285 version
1286 .try_into()
1287 .map_err(|_| Error::Db("Error reading database version".into()))?,
1288 );
1289 if version > CURRENT_VERSION {
1290 return Err(Error::Db(format!("Unsupported database version: {version}")));
1291 }
1292 Ok(version < CURRENT_VERSION)
1293 },
1294 None => {
1295 db.commit([(
1297 col::META,
1298 KEY_VERSION.to_vec(),
1299 Some(CURRENT_VERSION.to_le_bytes().to_vec()),
1300 )])
1301 .map_err(|e| Error::Db(e.to_string()))?;
1302 Ok(false)
1303 },
1304 }
1305 }
1306
1307 fn populate(&self, migrate_index: bool) -> Result<()> {
1314 if migrate_index {
1315 self.migrate_database()?;
1316 }
1317
1318 {
1319 let mut submit_index = self.submit_index.write();
1320 let mut query_index = self.query_index.write();
1321
1322 if let Some(counters) =
1323 self.db.get(col::META, KEY_COUNTERS).map_err(|e| Error::Db(e.to_string()))?
1324 {
1325 let (statement_count, total_size, next_seq) =
1326 <(u64, u64, u64)>::decode(&mut counters.as_slice())
1327 .map_err(|_| Error::Db("Error reading the store counters".into()))?;
1328 submit_index.statement_count = statement_count as usize;
1329 submit_index.total_size = total_size as usize;
1330 submit_index.next_seq = next_seq;
1331 }
1332 let mut iter =
1336 self.db.iter(col::ADMISSION_SEQ).map_err(|e| Error::Db(e.to_string()))?;
1337 iter.seek_to_last().map_err(|e| Error::Db(e.to_string()))?;
1338 if let Some((key, _)) = iter.prev().map_err(|e| Error::Db(e.to_string()))? {
1339 let seq = u64::from_be_bytes(
1340 key.try_into()
1341 .map_err(|_| Error::Db("Invalid admission sequence key".into()))?,
1342 );
1343 if submit_index.next_seq <= seq {
1344 log::error!(
1345 target: LOG_TARGET,
1346 "The store counters lag the admission journal (next_seq {} <= last \
1347 admission {}); recovering. This must be a bug, please report.",
1348 submit_index.next_seq,
1349 seq,
1350 );
1351 submit_index.next_seq = seq.saturating_add(1);
1352 }
1353 }
1354
1355 let mut evicted_count = 0usize;
1356 self.db
1357 .iter_column_while(col::EXPIRED, |_| {
1358 evicted_count += 1;
1359 true
1360 })
1361 .map_err(|e| Error::Db(e.to_string()))?;
1362 submit_index.evicted_count = evicted_count;
1363
1364 for (prefix, count) in self.count_index_prefixes(col::INDEX_BY_TOPIC)? {
1366 let Ok(topic) = <[u8; 32]>::try_from(prefix.as_slice()).map(Topic::from) else {
1367 log::error!(
1368 target: LOG_TARGET,
1369 "Corrupt topic index prefix: {:?}",
1370 HexDisplay::from(&prefix)
1371 );
1372 continue;
1373 };
1374 query_index.topic_counts.insert(topic, count);
1375 }
1376 for (prefix, count) in self.count_index_prefixes(col::INDEX_BY_DEC_KEY)? {
1377 let Some(dec_key) = parse_dec_key_index_prefix(&prefix) else { continue };
1378 query_index.dec_key_counts.insert(dec_key, count);
1379 }
1380 }
1381
1382 self.known_accounts_count.store(self.count_accounts()?, AtomicOrdering::Relaxed);
1383 self.maintain();
1384 Ok(())
1385 }
1386
1387 fn migrate_database(&self) -> Result<()> {
1392 let purge_after_sec = self.submit_index.read().config.purge_after_sec;
1393 let mut admission_seqs = HashMap::new();
1396 let mut next_seq = 0u64;
1397 {
1398 let mut iter =
1399 self.db.iter(col::ADMISSION_SEQ).map_err(|e| Error::Db(e.to_string()))?;
1400 iter.seek_to_first().map_err(|e| Error::Db(e.to_string()))?;
1401 while let Some((key, value)) = iter.next().map_err(|e| Error::Db(e.to_string()))? {
1402 let seq = u64::from_be_bytes(
1403 key.try_into()
1404 .map_err(|_| Error::Db("Invalid admission sequence key".into()))?,
1405 );
1406 let hash: Hash = value
1407 .as_slice()
1408 .try_into()
1409 .map_err(|_| Error::Db("Invalid admission sequence hash".into()))?;
1410 admission_seqs.insert(hash, seq);
1411 next_seq = next_seq.max(seq.saturating_add(1));
1412 }
1413 }
1414
1415 let mut migration = MigrationBatch::new(&self.db);
1416 let mut migration_error = None;
1417 let mut statement_count = 0usize;
1418 let mut total_size = 0usize;
1419 self.db
1420 .iter_column_while(col::STATEMENTS, |item| {
1421 let Ok(statement) = Statement::decode(&mut item.value.as_slice()) else {
1422 log::error!(
1423 target: LOG_TARGET,
1424 "Corrupt statement {:?}",
1425 HexDisplay::from(&sp_statement_store::hash_encoded(&item.value))
1426 );
1427 return true;
1428 };
1429 let hash = statement.hash();
1430 let Some(account) = statement.account_id() else {
1431 log::error!(
1432 target: LOG_TARGET,
1433 "Statement without an account id loaded from the DB: {:?}",
1434 HexDisplay::from(&hash)
1435 );
1436 return true;
1437 };
1438 log::trace!(target: LOG_TARGET, "Statement loaded {:?}", HexDisplay::from(&hash));
1439 let persisted_seq = admission_seqs.remove(&hash);
1440 let seq = persisted_seq.unwrap_or_else(|| {
1441 let seq = next_seq;
1442 next_seq = seq.saturating_add(1);
1443 seq
1444 });
1445 statement_count += 1;
1446 total_size += statement.data_len();
1447 let details = EntryDetails {
1448 channel: statement.channel(),
1449 data_len: statement.data_len(),
1450 admission_seq: seq,
1451 };
1452 let admission_op = persisted_seq.is_none().then(|| DbOperation {
1453 column: col::ADMISSION_SEQ,
1454 key: seq.to_be_bytes().to_vec(),
1455 value: Some(hash.to_vec()),
1456 });
1457 let operations = statement_index_ops(&hash, &statement, true)
1458 .into_iter()
1459 .chain(account_index_ops(
1460 &account,
1461 Expiry(statement.expiry()),
1462 &hash,
1463 Some(&details),
1464 ))
1465 .map(DbOperation::from)
1466 .chain(admission_op);
1467 if let Err(error) = migration.extend(operations) {
1468 migration_error = Some(error);
1469 return false;
1470 }
1471 true
1472 })
1473 .map_err(|e| Error::Db(e.to_string()))?;
1474 if let Some(error) = migration_error.take() {
1475 return Err(error);
1476 }
1477
1478 self.db
1479 .iter_column_while(col::EXPIRED, |item| {
1480 if let Ok((hash, timestamp)) = <(Hash, u64)>::decode(&mut item.value.as_slice()) {
1481 log::trace!(
1482 target: LOG_TARGET,
1483 "Statement loaded (expired): {:?}",
1484 HexDisplay::from(&hash)
1485 );
1486 let purge_at = timestamp.saturating_add(purge_after_sec);
1487 let operation = DbOperation {
1488 column: col::INDEX_EVICTED,
1489 key: evicted_index_key(purge_at, &hash),
1490 value: Some(INDEX_EMPTY_VALUE.to_vec()),
1491 };
1492 if let Err(error) = migration.push(operation) {
1493 migration_error = Some(error);
1494 return false;
1495 }
1496 }
1497 true
1498 })
1499 .map_err(|e| Error::Db(e.to_string()))?;
1500 if let Some(error) = migration_error {
1501 return Err(error);
1502 }
1503
1504 migration.push(SubmitIndex::counters_op(statement_count, total_size, next_seq).into())?;
1505 let migrated_entries = migration.finish()?;
1506 self.db
1507 .commit([(
1508 col::META,
1509 KEY_VERSION.to_vec(),
1510 Some(CURRENT_VERSION.to_le_bytes().to_vec()),
1511 )])
1512 .map_err(|e| Error::Db(e.to_string()))?;
1513 log::info!(
1514 target: LOG_TARGET,
1515 "Migrated the statement store index to the on-disk format ({} rows)",
1516 migrated_entries
1517 );
1518 Ok(())
1519 }
1520
1521 fn count_index_prefixes(&self, column: u8) -> Result<Vec<(Vec<u8>, usize)>> {
1525 let mut counts: Vec<(Vec<u8>, usize)> = Vec::new();
1526 let mut iter = self.db.iter(column).map_err(|e| Error::Db(e.to_string()))?;
1527 iter.seek_to_first().map_err(|e| Error::Db(e.to_string()))?;
1528 while let Some((key, _)) = iter.next().map_err(|e| Error::Db(e.to_string()))? {
1529 let Some(prefix) = prefix_from_index_key(&key) else { continue };
1530 match counts.last_mut() {
1531 Some((last, count)) if last.as_slice() == prefix => *count += 1,
1532 _ => counts.push((prefix.to_vec(), 1)),
1533 }
1534 }
1535 Ok(counts)
1536 }
1537
1538 fn next_account_from(&self, from: &AccountId) -> Result<Option<AccountId>> {
1542 let mut iter = self.db.iter(col::INDEX_BY_ACCOUNT).map_err(|e| Error::Db(e.to_string()))?;
1543 iter.seek(&from[..]).map_err(|e| Error::Db(e.to_string()))?;
1544 while let Some((key, _)) = iter.next().map_err(|e| Error::Db(e.to_string()))? {
1545 if let Some((account, _, _)) = parse_account_index_key(&key) {
1546 return Ok(Some(account));
1547 }
1548 }
1549 Ok(None)
1550 }
1551
1552 fn count_accounts(&self) -> Result<usize> {
1555 let mut count = 0usize;
1556 let mut cursor = [0u8; 32];
1557 while let Some(account) = self.next_account_from(&cursor)? {
1558 count += 1;
1559 match next_account_id(account) {
1560 Some(next) => cursor = next,
1561 None => break,
1562 }
1563 }
1564 Ok(count)
1565 }
1566
1567 fn load_account_entries(
1569 &self,
1570 account: &AccountId,
1571 ) -> Result<Vec<(PriorityKey, EntryDetails)>> {
1572 let mut entries = Vec::new();
1573 let mut iter = self.db.iter(col::INDEX_BY_ACCOUNT).map_err(|e| Error::Db(e.to_string()))?;
1574 iter.seek(&account[..]).map_err(|e| Error::Db(e.to_string()))?;
1575 while let Some((key, value)) = iter.next().map_err(|e| Error::Db(e.to_string()))? {
1576 if !key.starts_with(&account[..]) {
1577 break;
1578 }
1579 let Some((_, expiry, hash)) = parse_account_index_key(&key) else { continue };
1580 let Ok(details) = EntryDetails::decode(&mut value.as_slice()) else {
1581 log::error!(
1582 target: LOG_TARGET,
1583 "Corrupt account index row for statement {:?}",
1584 HexDisplay::from(&hash)
1585 );
1586 continue;
1587 };
1588 entries.push((PriorityKey { hash, expiry }, details));
1589 }
1590 Ok(entries)
1591 }
1592
1593 fn load_account_record(&self, account: &AccountId) -> Result<StatementsForAccount> {
1595 let mut record = StatementsForAccount::default();
1596 for (key, details) in self.load_account_entries(account)? {
1597 record.insert_entry(key.hash, key.expiry, details);
1598 }
1599 Ok(record)
1600 }
1601
1602 fn scan_index_prefix(&self, column: u8, prefix: &[u8]) -> Result<HashSet<Hash>> {
1604 let mut set = HashSet::new();
1605 let mut iter = self.db.iter(column).map_err(|e| Error::Db(e.to_string()))?;
1606 iter.seek(prefix).map_err(|e| Error::Db(e.to_string()))?;
1607 while let Some((key, _)) = iter.next().map_err(|e| Error::Db(e.to_string()))? {
1608 if !key.starts_with(prefix) {
1609 break;
1610 }
1611 if let Some(hash) = hash_from_index_key(&key) {
1612 set.insert(hash);
1613 }
1614 }
1615 Ok(set)
1616 }
1617
1618 fn enumerate_hashes(&self) -> Result<Vec<Hash>> {
1622 let mut hashes = Vec::new();
1623 let mut iter = self.db.iter(col::INDEX_BY_DEC_KEY).map_err(|e| Error::Db(e.to_string()))?;
1624 iter.seek_to_first().map_err(|e| Error::Db(e.to_string()))?;
1625 while let Some((key, _)) = iter.next().map_err(|e| Error::Db(e.to_string()))? {
1626 if let Some(hash) = hash_from_index_key(&key) {
1627 hashes.push(hash);
1628 }
1629 }
1630 Ok(hashes)
1631 }
1632
1633 fn index_set_contains(&self, set: &IndexSet, hash: &Hash) -> Result<bool> {
1635 Ok(self
1636 .db
1637 .get_size(set.column(), &set.member_key(hash))
1638 .map_err(|e| Error::Db(e.to_string()))?
1639 .is_some())
1640 }
1641
1642 fn hash_in_all_sets(&self, hash: &Hash, sets: &[IndexSet]) -> Result<bool> {
1645 sets.iter().try_fold(true, |acc, set| {
1646 Ok::<bool, Error>(acc && self.index_set_contains(set, hash)?)
1647 })
1648 }
1649
1650 fn iterate_with(
1653 &self,
1654 key: Option<DecryptionKey>,
1655 topic_filter: &OptimizedTopicFilter,
1656 f: impl FnMut(&Hash) -> Result<()>,
1657 ) -> Result<()> {
1658 match topic_filter {
1659 OptimizedTopicFilter::Any => self.iterate_with_any(key, f),
1660 OptimizedTopicFilter::MatchAll(topics) => self.iterate_with_match_all(key, topics, f),
1661 OptimizedTopicFilter::MatchAny(topics) => self.iterate_with_match_any(key, topics, f),
1662 }
1663 }
1664
1665 fn iterate_with_any(
1667 &self,
1668 key: Option<DecryptionKey>,
1669 mut f: impl FnMut(&Hash) -> Result<()>,
1670 ) -> Result<()> {
1671 let prefix = IndexSet::DecKey(key).prefix();
1672 let mut iter = self.db.iter(col::INDEX_BY_DEC_KEY).map_err(|e| Error::Db(e.to_string()))?;
1673 iter.seek(&prefix).map_err(|e| Error::Db(e.to_string()))?;
1674 while let Some((k, _)) = iter.next().map_err(|e| Error::Db(e.to_string()))? {
1675 if !k.starts_with(&prefix) {
1676 break;
1677 }
1678 if let Some(hash) = hash_from_index_key(&k) {
1679 f(&hash)?;
1680 }
1681 }
1682 Ok(())
1683 }
1684
1685 fn iterate_with_match_any(
1689 &self,
1690 key: Option<DecryptionKey>,
1691 topics: &HashSet<Topic>,
1692 mut f: impl FnMut(&Hash) -> Result<()>,
1693 ) -> Result<()> {
1694 let key_set = IndexSet::DecKey(key);
1695 for topic in topics {
1696 let prefix = topic[..].to_vec();
1697 let mut iter =
1698 self.db.iter(col::INDEX_BY_TOPIC).map_err(|e| Error::Db(e.to_string()))?;
1699 iter.seek(&prefix).map_err(|e| Error::Db(e.to_string()))?;
1700 while let Some((k, _)) = iter.next().map_err(|e| Error::Db(e.to_string()))? {
1701 if !k.starts_with(&prefix) {
1702 break;
1703 }
1704 if let Some(hash) = hash_from_index_key(&k) {
1705 if self.index_set_contains(&key_set, &hash)? {
1706 f(&hash)?;
1707 }
1708 }
1709 }
1710 }
1711 Ok(())
1712 }
1713
1714 fn iterate_with_match_all(
1720 &self,
1721 key: Option<DecryptionKey>,
1722 topics: &HashSet<Topic>,
1723 mut f: impl FnMut(&Hash) -> Result<()>,
1724 ) -> Result<()> {
1725 if topics.len() > MAX_TOPICS {
1726 return Ok(());
1727 }
1728 let mut sets = Vec::with_capacity(topics.len() + 1);
1729 sets.push(IndexSet::DecKey(key));
1730 for topic in topics {
1731 sets.push(IndexSet::Topic(*topic));
1732 }
1733 {
1734 let query_index = self.query_index.read();
1736 sets.sort_by_key(|s| s.len(&query_index));
1737 }
1738 let smallest = self.scan_index_prefix(sets[0].column(), &sets[0].prefix())?;
1739 let others = &sets[1..];
1740 for hash in &smallest {
1741 if self.hash_in_all_sets(hash, others)? {
1742 log::trace!(
1743 target: LOG_TARGET,
1744 "Iterating by topic/key: statement {:?}",
1745 HexDisplay::from(hash)
1746 );
1747 f(hash)?;
1748 }
1749 }
1750 Ok(())
1751 }
1752
1753 fn read_statement_encoded(&self, hash: &Hash) -> Result<Option<Vec<u8>>> {
1757 match self.db.get(col::STATEMENTS, hash).map_err(|e| Error::Db(e.to_string()))? {
1758 Some(entry) => Ok(Some(entry)),
1759 None => {
1760 log::debug!(target: LOG_TARGET, "Missing statement {:?}", HexDisplay::from(hash));
1761 Ok(None)
1762 },
1763 }
1764 }
1765
1766 fn read_statement(&self, hash: &Hash) -> Result<Option<Statement>> {
1769 let Some(entry) = self.read_statement_encoded(hash)? else { return Ok(None) };
1770 match Statement::decode(&mut entry.as_slice()) {
1771 Ok(statement) => Ok(Some(statement)),
1772 Err(_) => {
1773 log::error!(target: LOG_TARGET, "Corrupt statement {:?}", HexDisplay::from(hash));
1774 Ok(None)
1775 },
1776 }
1777 }
1778
1779 fn collect_statements<R>(
1783 &self,
1784 key: Option<DecryptionKey>,
1785 topic_filter: &OptimizedTopicFilter,
1786 mut f: impl FnMut(Statement) -> Option<R>,
1787 ) -> Result<Vec<R>> {
1788 let mut result = Vec::new();
1789 self.iterate_with(key, topic_filter, |hash| {
1790 if let Some(statement) = self.read_statement(hash)? {
1791 if let Some(data) = f(statement) {
1792 result.push(data);
1793 }
1794 }
1795 Ok(())
1796 })?;
1797 Ok(result)
1798 }
1799
1800 fn enforce_account_allowance(
1804 &self,
1805 account: &AccountId,
1806 current_time: u64,
1807 budget: &mut usize,
1808 ) {
1809 let allowance = match (self.read_allowance_fn)(account, AllowanceBlock::Finalized) {
1810 Ok(Some(allowance)) => allowance,
1811 Ok(None) => {
1812 log::debug!(
1813 target: LOG_TARGET,
1814 "No allowance found for account {:?}, treating as zero allowance",
1815 HexDisplay::from(account)
1816 );
1817 StatementAllowance { max_count: 0, max_size: 0 }
1818 },
1819 Err(e) => {
1820 log::error!(target: LOG_TARGET, "Error reading allowance: {:?}", e);
1821 return;
1823 },
1824 };
1825 let (max_count, max_size) = (allowance.max_count as usize, allowance.max_size as usize);
1826
1827 if let Some(summary) = self.submit_index.read().summaries.peek(account) {
1831 if summary.count <= max_count && summary.data_size <= max_size {
1832 return;
1833 }
1834 }
1835
1836 let entries = match self.load_account_entries(account) {
1837 Ok(entries) => entries,
1838 Err(e) => {
1839 log::warn!(target: LOG_TARGET, "Error reading the account index: {:?}", e);
1840 return;
1841 },
1842 };
1843 let expiry_bound = Expiry(current_time << 32);
1844 let mut remaining_count = 0usize;
1845 let mut remaining_size = 0usize;
1846 for (key, details) in &entries {
1847 if key.expiry >= expiry_bound {
1848 remaining_count += 1;
1849 remaining_size += details.data_len;
1850 }
1851 }
1852 if remaining_count <= max_count && remaining_size <= max_size {
1853 return;
1854 }
1855 log::debug!(
1856 target: LOG_TARGET,
1857 "Account {:?} exceeds allowance: count={}/{}, size={}/{}",
1858 HexDisplay::from(account),
1859 remaining_count,
1860 allowance.max_count,
1861 remaining_size,
1862 allowance.max_size
1863 );
1864
1865 for (key, details) in &entries {
1867 if (remaining_count <= max_count && remaining_size <= max_size) || *budget == 0 {
1868 break;
1869 }
1870 if key.expiry < expiry_bound {
1871 continue;
1872 }
1873 log::debug!(
1874 target: LOG_TARGET,
1875 "Evicting statement {:?} due to allowance enforcement",
1876 HexDisplay::from(&key.hash)
1877 );
1878 if let Err(e) = self.remove(&key.hash) {
1879 log::debug!(
1880 target: LOG_TARGET,
1881 "Error evicting statement {:?}: {:?}",
1882 HexDisplay::from(&key.hash),
1883 e
1884 );
1885 } else {
1886 *budget -= 1;
1887 remaining_count -= 1;
1888 remaining_size -= details.data_len;
1889 }
1890 }
1891 }
1892
1893 #[doc(hidden)]
1913 pub fn enforce_limits(&self) {
1914 self.enforce_limits_bounded(
1915 MAX_EXPIRY_STATEMENTS_PER_ITERATION,
1916 MAX_EXPIRY_ACCOUNTS_PER_ITERATION,
1917 MAX_EXPIRY_TIME_PER_ITERATION,
1918 )
1919 }
1920
1921 fn enforce_limits_bounded(
1925 &self,
1926 statement_budget: usize,
1927 account_budget: usize,
1928 time_budget: Duration,
1929 ) {
1930 let _start_check_expiration_timer = self.metrics.start_check_expiration_timer();
1931 let current_time = self.timestamp();
1932 let start = Instant::now();
1933 let mut expired = 0u64;
1934
1935 let mut due = Vec::new();
1937 let scan = (|| -> Result<()> {
1938 let mut iter =
1939 self.db.iter(col::INDEX_BY_EXPIRY).map_err(|e| Error::Db(e.to_string()))?;
1940 iter.seek_to_first().map_err(|e| Error::Db(e.to_string()))?;
1941 while let Some((key, _)) = iter.next().map_err(|e| Error::Db(e.to_string()))? {
1942 let Some((expiry, hash)) = parse_time_index_key(&key) else { continue };
1943 if expiry >= (current_time << 32) {
1944 break;
1946 }
1947 due.push((expiry, hash));
1948 if due.len() >= statement_budget || start.elapsed() >= time_budget {
1949 break;
1950 }
1951 }
1952 Ok(())
1953 })();
1954 if let Err(e) = scan {
1955 log::warn!(target: LOG_TARGET, "Error scanning the expiry index: {:?}", e);
1956 }
1957 for (expiry, hash) in due {
1958 match self.remove_statement(&hash) {
1959 Ok(true) => {
1960 expired += 1;
1961 log::trace!(
1962 target: LOG_TARGET,
1963 "Marked statement {:?} as expired",
1964 HexDisplay::from(&hash)
1965 );
1966 },
1967 Ok(false) => self.report_orphan_expiry_row(expiry, &hash),
1970 Err(e) => {
1971 log::debug!(
1972 target: LOG_TARGET,
1973 "Error marking statement {:?} as expired: {:?}",
1974 HexDisplay::from(&hash),
1975 e
1976 );
1977 },
1978 }
1979 }
1980
1981 let allowance_start = Instant::now();
1986 let mut allowance_budget = statement_budget;
1987 let mut cursor = self.submit_index.read().allowance_cursor;
1988 let mut checked = 0usize;
1989 let mut wrapped = false;
1990 while checked < account_budget &&
1991 allowance_budget > 0 &&
1992 allowance_start.elapsed() < time_budget
1993 {
1994 let from = cursor.unwrap_or([0u8; 32]);
1995 let account = match self.next_account_from(&from) {
1996 Ok(Some(account)) => account,
1997 Ok(None) => {
1998 wrapped = true;
1999 break;
2000 },
2001 Err(e) => {
2002 log::warn!(target: LOG_TARGET, "Error reading the account index: {:?}", e);
2003 break;
2004 },
2005 };
2006 checked += 1;
2007 let budget_before = allowance_budget;
2008 self.enforce_account_allowance(&account, current_time, &mut allowance_budget);
2009 expired += (budget_before - allowance_budget) as u64;
2010 cursor = match next_account_id(account) {
2011 next @ Some(_) => next,
2012 None => {
2013 wrapped = true;
2014 break;
2015 },
2016 };
2017 }
2018 {
2019 let mut submit_index = self.submit_index.write();
2020 submit_index.allowance_cycle_seen += checked;
2021 if wrapped {
2022 self.known_accounts_count
2025 .store(submit_index.allowance_cycle_seen, AtomicOrdering::Relaxed);
2026 submit_index.allowance_cycle_seen = 0;
2027 submit_index.allowance_cursor = None;
2028 } else {
2029 submit_index.allowance_cursor = cursor;
2030 }
2031 }
2032
2033 drop(_start_check_expiration_timer);
2034
2035 self.metrics.report(|metrics| {
2036 metrics.statements_expired_total.inc_by(expired);
2037 });
2038 }
2039
2040 fn drain_due_evicted(&self, current_time: u64) -> Result<usize> {
2043 let mut commit: Vec<(u8, Vec<u8>, Option<Vec<u8>>)> = Vec::new();
2044 let mut drained = 0usize;
2045 {
2046 let mut iter =
2047 self.db.iter(col::INDEX_EVICTED).map_err(|e| Error::Db(e.to_string()))?;
2048 iter.seek_to_first().map_err(|e| Error::Db(e.to_string()))?;
2049 loop {
2050 let Some((key, _)) = iter.next().map_err(|e| Error::Db(e.to_string()))? else {
2051 break;
2052 };
2053 let Some((purge_at, hash)) = parse_time_index_key(&key) else { continue };
2054 if purge_at > current_time {
2055 break;
2057 }
2058 commit.push((col::EXPIRED, hash.to_vec(), None));
2059 commit.push((col::INDEX_EVICTED, key, None));
2060 drained += 1;
2061 }
2062 }
2063 if !commit.is_empty() {
2064 self.db.commit(commit).map_err(|e| Error::Db(e.to_string()))?;
2065 }
2066 Ok(drained)
2067 }
2068
2069 pub fn maintain(&self) {
2082 log::trace!(target: LOG_TARGET, "Started store maintenance");
2083 let current_time = self.timestamp();
2084 let deleted_count = match self.drain_due_evicted(current_time) {
2085 Ok(count) => count as u64,
2086 Err(e) => {
2087 log::warn!(target: LOG_TARGET, "Error writing to the statement database: {:?}", e);
2088 0
2089 },
2090 };
2091
2092 let (active_count, expired_count, total_size, capacity_statements, capacity_bytes) = {
2093 let mut submit_index = self.submit_index.write();
2094 submit_index.evicted_count =
2095 submit_index.evicted_count.saturating_sub(deleted_count as usize);
2096 (
2097 submit_index.statement_count,
2098 submit_index.evicted_count,
2099 submit_index.total_size,
2100 submit_index.config.max_total_statements,
2101 submit_index.config.max_total_size,
2102 )
2103 };
2104 let accounts_count = self.known_accounts_count.load(AtomicOrdering::Relaxed);
2105
2106 if deleted_count > 0 {
2107 self.metrics.report(|metrics| metrics.statements_pruned.inc_by(deleted_count));
2108 }
2109
2110 self.metrics.report(|metrics| {
2111 metrics.statements_total.set(active_count as u64);
2112 metrics.bytes_total.set(total_size as u64);
2113 metrics.accounts_total.set(accounts_count as u64);
2114 metrics.expired_total.set(expired_count as u64);
2115 metrics.capacity_statements.set(capacity_statements as u64);
2116 metrics.capacity_bytes.set(capacity_bytes as u64);
2117 });
2118
2119 log::trace!(
2120 target: LOG_TARGET,
2121 "Completed store maintenance. Purged: {}, Active: {}, Expired: {}",
2122 deleted_count,
2123 active_count,
2124 expired_count
2125 );
2126 }
2127
2128 fn timestamp(&self) -> u64 {
2129 self.time_override.unwrap_or_else(|| {
2130 std::time::SystemTime::now()
2131 .duration_since(std::time::UNIX_EPOCH)
2132 .unwrap_or_default()
2133 .as_secs()
2134 })
2135 }
2136
2137 #[cfg(test)]
2138 fn set_time(&mut self, time: u64) {
2139 self.time_override = Some(time);
2140 }
2141
2142 pub fn as_statement_store_ext(self: Arc<Self>) -> StatementStoreExt {
2144 StatementStoreExt::new(self)
2145 }
2146
2147 fn posted_clear_inner<R>(
2150 &self,
2151 match_all_topics: &[Topic],
2152 dest: [u8; 32],
2153 mut map_f: impl FnMut(Statement, Vec<u8>) -> R,
2155 ) -> Result<Vec<R>> {
2156 self.collect_statements(
2157 Some(dest),
2158 &OptimizedTopicFilter::MatchAll(match_all_topics.iter().cloned().collect()),
2159 |statement| {
2160 if let (Some(key), Some(_)) = (statement.decryption_key(), statement.data()) {
2161 let public: sp_core::ed25519::Public = UncheckedFrom::unchecked_from(key);
2162 let public: sp_statement_store::ed25519::Public = public.into();
2163 match self.keystore.key_pair::<sp_statement_store::ed25519::Pair>(&public) {
2164 Err(e) => {
2165 log::debug!(
2166 target: LOG_TARGET,
2167 "Keystore error: {:?}, for statement {:?}",
2168 e,
2169 HexDisplay::from(&statement.hash())
2170 );
2171 None
2172 },
2173 Ok(None) => {
2174 log::debug!(
2175 target: LOG_TARGET,
2176 "Keystore is missing key for statement {:?}",
2177 HexDisplay::from(&statement.hash())
2178 );
2179 None
2180 },
2181 Ok(Some(pair)) => match statement.decrypt_private(&pair.into_inner()) {
2182 Ok(r) => r.map(|data| map_f(statement, data)),
2183 Err(e) => {
2184 log::debug!(
2185 target: LOG_TARGET,
2186 "Decryption error: {:?}, for statement {:?}",
2187 e,
2188 HexDisplay::from(&statement.hash())
2189 );
2190 None
2191 },
2192 },
2193 }
2194 } else {
2195 None
2196 }
2197 },
2198 )
2199 }
2200}
2201
2202impl StatementStore for Store {
2203 fn statements(&self) -> Result<Vec<(Hash, Statement)>> {
2209 let hashes = self.enumerate_hashes()?;
2210 let mut result = Vec::with_capacity(hashes.len());
2211 for hash in hashes {
2212 let Some(encoded) =
2213 self.db.get(col::STATEMENTS, &hash).map_err(|e| Error::Db(e.to_string()))?
2214 else {
2215 continue;
2216 };
2217 match Statement::decode(&mut encoded.as_slice()) {
2218 Ok(statement) => result.push((hash, statement)),
2219 Err(_) => log::error!(
2220 target: LOG_TARGET,
2221 "Corrupt statement {:?}",
2222 HexDisplay::from(&hash)
2223 ),
2224 }
2225 }
2226 Ok(result)
2227 }
2228
2229 fn take_recent_statements(&self) -> Result<Vec<(u64, Hash, Statement)>> {
2230 let recent = self.query_index.write().take_recent();
2231 let mut result = Vec::with_capacity(recent.len());
2232 for (hash, seq) in recent {
2233 let Some(encoded) =
2234 self.db.get(col::STATEMENTS, &hash).map_err(|e| Error::Db(e.to_string()))?
2235 else {
2236 continue;
2237 };
2238 match Statement::decode(&mut encoded.as_slice()) {
2239 Ok(statement) => result.push((seq, hash, statement)),
2240 Err(_) => log::error!(
2241 target: LOG_TARGET,
2242 "Corrupt statement {:?}",
2243 HexDisplay::from(&hash)
2244 ),
2245 }
2246 }
2247 result.sort_unstable_by_key(|(seq, ..)| *seq);
2248 Ok(result)
2249 }
2250
2251 fn admission_watermark(&self) -> Result<u64> {
2252 Ok(self.submit_index.read().next_seq)
2256 }
2257
2258 fn admitted_statements(
2259 &self,
2260 mut cursor: u64,
2261 watermark: u64,
2262 scan_limit: usize,
2263 filter: &mut dyn FnMut(&Hash, &[u8], &Statement) -> FilterDecision,
2264 ) -> Result<AdmittedBatch> {
2265 debug_assert!(
2268 cursor <= watermark,
2269 "admission cursor {cursor} must not exceed the watermark {watermark}"
2270 );
2271 debug_assert!(scan_limit > 0, "a zero scan limit cannot advance the cursor");
2272 let mut statements = Vec::new();
2273 let mut aborted = false;
2274 let mut scanned = 0usize;
2275 let mut iter = self.db.iter(col::ADMISSION_SEQ).map_err(|e| Error::Db(e.to_string()))?;
2276 iter.seek(&cursor.to_be_bytes()).map_err(|e| Error::Db(e.to_string()))?;
2277
2278 while let Some((key, value)) = iter.next().map_err(|e| Error::Db(e.to_string()))? {
2279 let seq = u64::from_be_bytes(
2280 key.try_into().map_err(|_| Error::Db("Invalid admission sequence key".into()))?,
2281 );
2282 if seq >= watermark {
2283 cursor = watermark;
2284 break;
2285 }
2286 if scanned == scan_limit {
2287 aborted = true;
2288 break;
2289 }
2290 scanned += 1;
2291 let hash: Hash = value
2292 .as_slice()
2293 .try_into()
2294 .map_err(|_| Error::Db("Invalid admission sequence hash".into()))?;
2295 let next_cursor = seq.saturating_add(1);
2296 let Some(encoded) =
2297 self.db.get(col::STATEMENTS, &hash).map_err(|e| Error::Db(e.to_string()))?
2298 else {
2299 cursor = next_cursor;
2300 continue;
2301 };
2302 let is_current = self
2306 .db
2307 .get(col::ADMISSION_SEQ, &seq.to_be_bytes())
2308 .map_err(|e| Error::Db(e.to_string()))?
2309 .is_some_and(|current| current.as_slice() == hash.as_slice());
2310 if !is_current {
2311 cursor = next_cursor;
2312 continue;
2313 }
2314 let statement = match Statement::decode(&mut encoded.as_slice()) {
2315 Ok(statement) => statement,
2316 Err(e) => {
2317 log::error!(
2318 target: LOG_TARGET,
2319 "Could not decode statement {:?} while walking the admission journal: {:?}",
2320 HexDisplay::from(&hash),
2321 e
2322 );
2323 cursor = next_cursor;
2324 continue;
2325 },
2326 };
2327 match filter(&hash, &encoded, &statement) {
2328 FilterDecision::Skip => cursor = next_cursor,
2329 FilterDecision::Take => {
2330 statements.push((hash, statement));
2331 cursor = next_cursor;
2332 },
2333 FilterDecision::Abort => {
2334 aborted = true;
2335 break;
2336 },
2337 }
2338 }
2339
2340 if !aborted && cursor < watermark {
2343 cursor = watermark;
2344 }
2345 Ok(AdmittedBatch { statements, cursor, done: cursor >= watermark })
2346 }
2347
2348 fn statement(&self, hash: &Hash) -> Result<Option<Statement>> {
2351 Ok(
2352 match self
2353 .db
2354 .get(col::STATEMENTS, hash.as_slice())
2355 .map_err(|e| Error::Db(e.to_string()))?
2356 {
2357 Some(entry) => {
2358 log::trace!(
2359 target: LOG_TARGET,
2360 "Queried statement {:?}",
2361 HexDisplay::from(hash)
2362 );
2363 Some(Statement::decode(&mut entry.as_slice()).map_err(|e| {
2364 log::error!(
2365 target: LOG_TARGET,
2366 "Corrupt statement {:?}",
2367 HexDisplay::from(hash)
2368 );
2369 Error::Decode(e.to_string())
2370 })?)
2371 },
2372 None => {
2373 log::trace!(
2374 target: LOG_TARGET,
2375 "Queried missing statement {:?}",
2376 HexDisplay::from(hash)
2377 );
2378 None
2379 },
2380 },
2381 )
2382 }
2383
2384 fn has_statement(&self, hash: &Hash) -> bool {
2385 match self.db.get_size(col::STATEMENTS, hash.as_slice()) {
2386 Ok(size) => size.is_some(),
2387 Err(e) => {
2388 log::debug!(
2389 target: LOG_TARGET,
2390 "Error checking statement presence {:?}: {:?}",
2391 HexDisplay::from(hash),
2392 e
2393 );
2394 false
2395 },
2396 }
2397 }
2398
2399 fn statements_by_hashes(
2400 &self,
2401 hashes: &[Hash],
2402 filter: &mut dyn FnMut(&Hash, &[u8], &Statement) -> FilterDecision,
2403 ) -> Result<(Vec<(Hash, Statement)>, usize)> {
2404 let mut result = Vec::new();
2405 let mut processed = 0;
2406 for hash in hashes {
2407 processed += 1;
2408 let Some(encoded) =
2409 self.db.get(col::STATEMENTS, hash).map_err(|e| Error::Db(e.to_string()))?
2410 else {
2411 continue;
2412 };
2413 let Ok(statement) = Statement::decode(&mut encoded.as_slice()) else {
2414 log::error!(target: LOG_TARGET, "Corrupt statement {:?}", HexDisplay::from(hash));
2415 continue;
2416 };
2417 match filter(hash, &encoded, &statement) {
2418 FilterDecision::Skip => {},
2419 FilterDecision::Take => {
2420 result.push((*hash, statement));
2421 },
2422 FilterDecision::Abort => {
2423 processed -= 1;
2425 break;
2426 },
2427 }
2428 }
2429
2430 Ok((result, processed))
2431 }
2432
2433 fn broadcasts(&self, match_all_topics: &[Topic]) -> Result<Vec<Vec<u8>>> {
2440 self.collect_statements(
2441 None,
2442 &OptimizedTopicFilter::MatchAll(match_all_topics.iter().cloned().collect()),
2443 |statement| statement.into_data(),
2444 )
2445 }
2446
2447 fn posted(&self, match_all_topics: &[Topic], dest: [u8; 32]) -> Result<Vec<Vec<u8>>> {
2453 self.collect_statements(
2454 Some(dest),
2455 &OptimizedTopicFilter::MatchAll(match_all_topics.iter().cloned().collect()),
2456 |statement| statement.into_data(),
2457 )
2458 }
2459
2460 fn posted_clear(&self, match_all_topics: &[Topic], dest: [u8; 32]) -> Result<Vec<Vec<u8>>> {
2465 self.posted_clear_inner(match_all_topics, dest, |_statement, data| data)
2466 }
2467
2468 fn broadcasts_stmt(&self, match_all_topics: &[Topic]) -> Result<Vec<Vec<u8>>> {
2477 self.collect_statements(
2478 None,
2479 &OptimizedTopicFilter::MatchAll(match_all_topics.iter().cloned().collect()),
2480 |statement| Some(statement.encode()),
2481 )
2482 }
2483
2484 fn posted_stmt(&self, match_all_topics: &[Topic], dest: [u8; 32]) -> Result<Vec<Vec<u8>>> {
2493 self.collect_statements(
2494 Some(dest),
2495 &OptimizedTopicFilter::MatchAll(match_all_topics.iter().cloned().collect()),
2496 |statement| Some(statement.encode()),
2497 )
2498 }
2499
2500 fn posted_clear_stmt(
2508 &self,
2509 match_all_topics: &[Topic],
2510 dest: [u8; 32],
2511 ) -> Result<Vec<Vec<u8>>> {
2512 self.posted_clear_inner(match_all_topics, dest, |statement, data| {
2513 let mut res = Vec::with_capacity(statement.size_hint() + data.len());
2514 statement.encode_to(&mut res);
2515 res.extend_from_slice(&data);
2516 res
2517 })
2518 }
2519
2520 fn submit(&self, statement: Statement, source: StatementSource) -> SubmitResult {
2548 let _histogram_submit_start_timer = self.metrics.start_submit_timer();
2549 let hash = statement.hash();
2550 if statement.is_expired(self.timestamp()) {
2552 log::debug!(
2553 target: LOG_TARGET,
2554 "Statement is already expired: {:?}",
2555 HexDisplay::from(&hash),
2556 );
2557 let reason = InvalidReason::AlreadyExpired;
2558 self.metrics.report(|metrics| {
2559 metrics.validations_invalid.with_label_values(&[reason.label()]).inc();
2560 });
2561 return SubmitResult::Invalid(reason);
2562 }
2563 let encoded_size = statement.encoded_size();
2564 if encoded_size > MAX_STATEMENT_SIZE {
2565 log::debug!(
2566 target: LOG_TARGET,
2567 "Statement is too big for propogation: {:?} ({}/{} bytes)",
2568 HexDisplay::from(&hash),
2569 statement.encoded_size(),
2570 MAX_STATEMENT_SIZE
2571 );
2572 let reason = InvalidReason::EncodingTooLarge {
2573 submitted_size: encoded_size,
2574 max_size: MAX_STATEMENT_SIZE,
2575 };
2576 self.metrics.report(|metrics| {
2577 metrics.validations_invalid.with_label_values(&[reason.label()]).inc();
2578 });
2579 return SubmitResult::Invalid(reason);
2580 }
2581
2582 if self.db.get_size(col::STATEMENTS, hash.as_slice()).ok().flatten().is_some() {
2586 if !source.can_be_resubmitted() {
2587 self.metrics.report(|metrics| {
2588 metrics.known_statements.with_label_values(&["known"]).inc();
2589 });
2590 return SubmitResult::Known;
2591 }
2592 } else if self.db.get_size(col::EXPIRED, hash.as_slice()).ok().flatten().is_some() {
2593 if !source.can_be_resubmitted() {
2594 self.metrics.report(|metrics| {
2595 metrics.known_statements.with_label_values(&["known_expired"]).inc();
2596 });
2597 return SubmitResult::KnownExpired;
2598 }
2599 }
2600
2601 let Some(account_id) = statement.account_id() else {
2602 log::debug!(
2603 target: LOG_TARGET,
2604 "Statement validation failed: Missing proof ({:?})",
2605 HexDisplay::from(&hash),
2606 );
2607 let reason = InvalidReason::NoProof;
2608 self.metrics.report(|metrics| {
2609 metrics.validations_invalid.with_label_values(&[reason.label()]).inc();
2610 });
2611 return SubmitResult::Invalid(reason);
2612 };
2613
2614 match statement.verify_signature() {
2615 SignatureVerificationResult::Valid(_) => {},
2616 SignatureVerificationResult::Invalid => {
2617 log::debug!(
2618 target: LOG_TARGET,
2619 "Statement validation failed: BadProof, {:?}",
2620 HexDisplay::from(&hash),
2621 );
2622 let reason = InvalidReason::BadProof;
2623 self.metrics.report(|metrics| {
2624 metrics.validations_invalid.with_label_values(&[reason.label()]).inc();
2625 });
2626 return SubmitResult::Invalid(reason);
2627 },
2628 SignatureVerificationResult::NoSignature => {
2629 log::debug!(
2630 target: LOG_TARGET,
2631 "Statement validation failed: NoProof, {:?}",
2632 HexDisplay::from(&hash),
2633 );
2634 let reason = InvalidReason::NoProof;
2635 self.metrics.report(|metrics| {
2636 metrics.validations_invalid.with_label_values(&[reason.label()]).inc();
2637 });
2638 return SubmitResult::Invalid(reason);
2639 },
2640 };
2641
2642 let validation = match (self.read_allowance_fn)(&account_id, AllowanceBlock::Best) {
2649 Ok(Some(allowance)) if !allowance.is_depleted() => allowance,
2650 Ok(Some(_)) | Ok(None) => {
2651 log::debug!(
2652 target: LOG_TARGET,
2653 "Account {} has no usable statement allowance",
2654 HexDisplay::from(&account_id),
2655 );
2656 let reason = RejectionReason::NoAllowance;
2657 self.metrics.report(|metrics| {
2658 metrics.rejections.with_label_values(&[reason.label()]).inc();
2659 });
2660 return SubmitResult::Rejected(reason);
2661 },
2662 Err(e) => {
2663 log::debug!(
2664 target: LOG_TARGET,
2665 "Reading statement allowance for account {} failed",
2666 HexDisplay::from(&account_id),
2667 );
2668 self.metrics.report(|metrics| {
2669 metrics.internal_errors.with_label_values(&["read_allowance"]).inc();
2670 });
2671 return SubmitResult::InternalError(e);
2672 },
2673 };
2674
2675 let current_time = self.timestamp();
2676 let seq = {
2677 let mut submit_index = self.submit_index.write();
2678
2679 match self.db.get_size(col::STATEMENTS, hash.as_slice()) {
2683 Ok(Some(_)) => {
2684 self.metrics.report(|metrics| {
2685 metrics.known_statements.with_label_values(&["known"]).inc();
2686 });
2687 return SubmitResult::Known;
2688 },
2689 Ok(None) => {},
2690 Err(e) => {
2691 self.metrics.report(|metrics| {
2692 metrics.internal_errors.with_label_values(&["db_read"]).inc();
2693 });
2694 return SubmitResult::InternalError(Error::Db(e.to_string()));
2695 },
2696 }
2697
2698 let statement_len = statement.data_len();
2699 let oversize = statement_len > validation.max_size as usize;
2704 let cached = submit_index.account_statements.peek(&account_id).is_some();
2705 let summary_admits = !cached &&
2706 statement.channel().is_none() &&
2707 submit_index.summaries.peek(&account_id).is_some_and(|summary| {
2708 summary.count < validation.max_count as usize &&
2709 summary.data_size + statement_len <= validation.max_size as usize
2710 }) && submit_index.statement_count <
2711 submit_index.config.max_total_statements &&
2712 submit_index.total_size + statement_len <= submit_index.config.max_total_size;
2713 let loaded_record = if cached || summary_admits || oversize {
2714 None
2715 } else {
2716 match self.load_account_record(&account_id) {
2717 Ok(record) => Some(record),
2718 Err(e) => {
2719 self.metrics.report(|metrics| {
2720 metrics.internal_errors.with_label_values(&["db_read"]).inc();
2721 });
2722 return SubmitResult::InternalError(e);
2723 },
2724 }
2725 };
2726
2727 let empty_record = StatementsForAccount::default();
2728 let record = loaded_record
2729 .as_ref()
2730 .or_else(|| submit_index.account_statements.peek(&account_id))
2731 .unwrap_or(&empty_record);
2732 let plan = match submit_index.plan_insert(
2733 record,
2734 hash,
2735 &statement,
2736 &account_id,
2737 &validation,
2738 current_time,
2739 ) {
2740 Ok(plan) => plan,
2741 Err(reason) => {
2742 self.metrics.report(|metrics| {
2743 metrics.rejections.with_label_values(&[reason.label()]).inc();
2744 });
2745 if let Some(record) = loaded_record {
2751 submit_index.cache_record_with_summary(account_id, record);
2752 }
2753 return SubmitResult::Rejected(reason);
2754 },
2755 };
2756
2757 let mut commit = Vec::new();
2759 commit.push((col::STATEMENTS, hash.to_vec(), Some(statement.encode())));
2760 commit.push((col::ADMISSION_SEQ, plan.seq.to_be_bytes().to_vec(), Some(hash.to_vec())));
2761 commit.extend(statement_index_ops(&hash, &statement, true));
2762 let details = EntryDetails {
2763 channel: statement.channel(),
2764 data_len: statement_len,
2765 admission_seq: plan.seq,
2766 };
2767 commit.extend(account_index_ops(
2768 &account_id,
2769 Expiry(statement.expiry()),
2770 &hash,
2771 Some(&details),
2772 ));
2773
2774 let mut evicted_statements = Vec::new();
2775 for (key, evicted_details) in &plan.evicted {
2776 commit.push((col::STATEMENTS, key.hash.to_vec(), None));
2777 commit.push((
2778 col::ADMISSION_SEQ,
2779 evicted_details.admission_seq.to_be_bytes().to_vec(),
2780 None,
2781 ));
2782 commit.extend(account_index_ops(&account_id, key.expiry, &key.hash, None));
2783 match self.db.get(col::STATEMENTS, &key.hash) {
2784 Ok(Some(encoded)) => match Statement::decode(&mut encoded.as_slice()) {
2785 Ok(evicted_statement) => {
2786 commit.extend(statement_index_ops(
2787 &key.hash,
2788 &evicted_statement,
2789 false,
2790 ));
2791 evicted_statements.push(evicted_statement);
2792 },
2793 Err(_) => log::error!(
2794 target: LOG_TARGET,
2795 "Corrupt statement {:?}",
2796 HexDisplay::from(&key.hash)
2797 ),
2798 },
2799 Ok(None) => log::error!(
2800 target: LOG_TARGET,
2801 "Missing body of the indexed statement {:?}",
2802 HexDisplay::from(&key.hash)
2803 ),
2804 Err(e) => log::warn!(
2805 target: LOG_TARGET,
2806 "Could not read evicted statement {:?} to clear its index: {:?}",
2807 HexDisplay::from(&key.hash),
2808 e
2809 ),
2810 }
2811 }
2812 for (h, purge_at) in &plan.banned {
2813 commit.push((col::EXPIRED, h.to_vec(), Some((h, current_time).encode())));
2814 commit.push((
2815 col::INDEX_EVICTED,
2816 evicted_index_key(*purge_at, h),
2817 Some(INDEX_EMPTY_VALUE.to_vec()),
2818 ));
2819 }
2820 let freed: usize = plan.evicted.iter().map(|(_, details)| details.data_len).sum();
2821 commit.push(SubmitIndex::counters_op(
2822 submit_index.statement_count + 1 - plan.evicted.len(),
2823 submit_index.total_size + statement_len - freed,
2824 plan.seq.saturating_add(1),
2825 ));
2826
2827 if let Err(e) = self.db.commit(commit) {
2828 log::debug!(
2829 target: LOG_TARGET,
2830 "Statement validation failed: database error {}, {:?}",
2831 e,
2832 statement
2833 );
2834 self.metrics.report(|metrics| {
2835 metrics.internal_errors.with_label_values(&["db_commit"]).inc();
2836 });
2837 return SubmitResult::InternalError(Error::Db(e.to_string()));
2838 }
2839 let seq = plan.seq;
2840 submit_index.apply_insert(&account_id, loaded_record, hash, &statement, &plan);
2841 {
2846 let mut query_index = self.query_index.write();
2847 for evicted_statement in &evicted_statements {
2848 query_index.note_remove(&evicted_statement.hash(), evicted_statement);
2849 }
2850 query_index.note_insert(hash, &statement, plan.seq);
2851 }
2852 seq
2853 }; self.subscription_manager.notify(seq, statement);
2855 self.metrics.report(|metrics| metrics.submitted_statements.inc());
2856 log::trace!(target: LOG_TARGET, "Statement submitted: {:?}", HexDisplay::from(&hash));
2857 SubmitResult::New
2858 }
2859
2860 fn remove(&self, hash: &Hash) -> Result<()> {
2864 self.remove_statement(hash).map(|_| ())
2865 }
2866
2867 fn remove_by(&self, who: [u8; 32]) -> Result<()> {
2870 let current_time = self.timestamp();
2871 {
2872 let mut submit_index = self.submit_index.write();
2873 let entries: Vec<(PriorityKey, EntryDetails)> =
2876 match submit_index.account_statements.peek(&who) {
2877 Some(record) => {
2878 record.by_priority.iter().map(|(key, details)| (*key, *details)).collect()
2879 },
2880 None => self.load_account_entries(&who)?,
2881 };
2882 if entries.is_empty() {
2883 return Ok(());
2884 }
2885
2886 let mut commit = Vec::new();
2887 let mut removed_statements = Vec::new();
2888 let mut banned_count = 0usize;
2889 let mut freed_size = 0usize;
2890 for (key, details) in &entries {
2891 commit.push((col::STATEMENTS, key.hash.to_vec(), None));
2892 commit.push((
2893 col::ADMISSION_SEQ,
2894 details.admission_seq.to_be_bytes().to_vec(),
2895 None,
2896 ));
2897 commit.extend(account_index_ops(&who, key.expiry, &key.hash, None));
2898 if current_time < key.expiry.get_expiration_timestamp_secs() {
2899 let purge_at = key
2900 .expiry
2901 .get_expiration_timestamp_secs()
2902 .min(current_time.saturating_add(submit_index.config.purge_after_sec));
2903 commit.push((
2904 col::EXPIRED,
2905 key.hash.to_vec(),
2906 Some((key.hash, current_time).encode()),
2907 ));
2908 commit.push((
2909 col::INDEX_EVICTED,
2910 evicted_index_key(purge_at, &key.hash),
2911 Some(INDEX_EMPTY_VALUE.to_vec()),
2912 ));
2913 banned_count += 1;
2914 }
2915 freed_size += details.data_len;
2916 match self.db.get(col::STATEMENTS, &key.hash) {
2917 Ok(Some(encoded)) => match Statement::decode(&mut encoded.as_slice()) {
2918 Ok(statement) => {
2919 commit.extend(statement_index_ops(&key.hash, &statement, false));
2920 removed_statements.push((key.hash, statement));
2921 },
2922 Err(_) => log::error!(
2923 target: LOG_TARGET,
2924 "Corrupt statement {:?}",
2925 HexDisplay::from(&key.hash)
2926 ),
2927 },
2928 Ok(None) => log::error!(
2929 target: LOG_TARGET,
2930 "Missing body of the indexed statement {:?}",
2931 HexDisplay::from(&key.hash)
2932 ),
2933 Err(e) => {
2934 log::warn!(
2935 target: LOG_TARGET,
2936 "Could not read statement {:?} to clear its index: {:?}",
2937 HexDisplay::from(&key.hash),
2938 e
2939 );
2940 },
2941 }
2942 }
2943 commit.push(SubmitIndex::counters_op(
2944 submit_index.statement_count.saturating_sub(entries.len()),
2945 submit_index.total_size.saturating_sub(freed_size),
2946 submit_index.next_seq,
2947 ));
2948 self.db.commit(commit).map_err(|e| {
2949 log::debug!(
2950 target: LOG_TARGET,
2951 "Error removing statement: database error {}, remove by {:?}",
2952 e,
2953 HexDisplay::from(&who),
2954 );
2955
2956 Error::Db(e.to_string())
2957 })?;
2958 submit_index.apply_account_removal(&who, entries.len(), freed_size, banned_count);
2959 let mut query_index = self.query_index.write();
2961 for (hash, statement) in &removed_statements {
2962 query_index.note_remove(hash, statement);
2963 }
2964 }
2965 Ok(())
2966 }
2967}
2968
2969struct ScanGuard<'a> {
2973 store: &'a Store,
2974 watermark: u64,
2975}
2976
2977impl Drop for ScanGuard<'_> {
2978 fn drop(&mut self) {
2979 self.store.submit_index.write().end_scan(self.watermark);
2980 }
2981}
2982
2983impl StatementStoreSubscriptionApi for Store {
2984 fn subscribe_statement(
2985 &self,
2986 topic_filter: OptimizedTopicFilter,
2987 ) -> Result<(Vec<Vec<u8>>, async_channel::Sender<StatementEvent>, SubscriptionStatementsStream)>
2988 {
2989 let (subscription_sender, subscription_stream, watermark) = {
2994 let mut submit_index = self.submit_index.write();
2995 let watermark = submit_index.begin_scan();
2996 let (sender, stream) =
2997 self.subscription_manager.subscribe(topic_filter.clone(), watermark);
2998 (sender, stream, watermark)
2999 };
3000 let _scan_guard = ScanGuard { store: self, watermark };
3001
3002 let mut hashes = HashSet::new();
3003 self.iterate_with(None, &topic_filter, |hash| {
3004 hashes.insert(*hash);
3005 Ok(())
3006 })?;
3007
3008 let hashes: Vec<Hash> = {
3009 let submit_index = self.submit_index.read();
3010 hashes
3011 .into_iter()
3012 .filter(|hash| submit_index.seq_covered_by_snapshot(hash, watermark))
3013 .collect()
3014 };
3015
3016 let mut existing_statements = Vec::with_capacity(hashes.len());
3017 for hash in hashes {
3018 if let Some(entry) = self.read_statement_encoded(&hash)? {
3019 existing_statements.push(entry);
3020 }
3021 }
3022
3023 if existing_statements.is_empty() {
3024 subscription_sender
3025 .send_blocking(StatementEvent::NewStatements {
3026 statements: vec![],
3027 remaining: Some(0),
3028 })
3029 .ok();
3030 }
3031 Ok((existing_statements, subscription_sender, subscription_stream))
3032 }
3033}
3034
3035impl Store {
3036 fn remove_statement(&self, hash: &Hash) -> Result<bool> {
3042 let current_time = self.timestamp();
3043 {
3044 let mut submit_index = self.submit_index.write();
3045 let Some(encoded) =
3047 self.db.get(col::STATEMENTS, hash).map_err(|e| Error::Db(e.to_string()))?
3048 else {
3049 return Ok(false);
3050 };
3051 let Some((statement, account)) = Statement::decode(&mut encoded.as_slice())
3052 .ok()
3053 .and_then(|statement| statement.account_id().map(|account| (statement, account)))
3054 else {
3055 log::error!(target: LOG_TARGET, "Corrupt statement {:?}", HexDisplay::from(hash));
3057 return Ok(false);
3058 };
3059 let expiry = Expiry(statement.expiry());
3060 let account_key = account_index_key(&account, expiry, hash);
3061 let details = self
3062 .db
3063 .get(col::INDEX_BY_ACCOUNT, &account_key)
3064 .map_err(|e| Error::Db(e.to_string()))?
3065 .as_deref()
3066 .and_then(|mut value| EntryDetails::decode(&mut value).ok());
3067
3068 let mut commit = vec![(col::STATEMENTS, hash.to_vec(), None)];
3069 commit.extend(statement_index_ops(hash, &statement, false));
3070 commit.extend(account_index_ops(&account, expiry, hash, None));
3071 match &details {
3072 Some(details) => commit.push((
3073 col::ADMISSION_SEQ,
3074 details.admission_seq.to_be_bytes().to_vec(),
3075 None,
3076 )),
3077 None => log::error!(
3080 target: LOG_TARGET,
3081 "Missing or corrupt account index entry for statement {:?}",
3082 HexDisplay::from(hash)
3083 ),
3084 }
3085 let banned = current_time < expiry.get_expiration_timestamp_secs();
3086 if banned {
3087 let purge_at = expiry
3088 .get_expiration_timestamp_secs()
3089 .min(current_time.saturating_add(submit_index.config.purge_after_sec));
3090 commit.push((col::EXPIRED, hash.to_vec(), Some((hash, current_time).encode())));
3091 commit.push((
3092 col::INDEX_EVICTED,
3093 evicted_index_key(purge_at, hash),
3094 Some(INDEX_EMPTY_VALUE.to_vec()),
3095 ));
3096 }
3097 let data_len = details
3098 .as_ref()
3099 .map_or_else(|| statement.data_len(), |details| details.data_len);
3100 commit.push(SubmitIndex::counters_op(
3101 submit_index.statement_count.saturating_sub(1),
3102 submit_index.total_size.saturating_sub(data_len),
3103 submit_index.next_seq,
3104 ));
3105 if let Err(e) = self.db.commit(commit) {
3106 log::debug!(
3107 target: LOG_TARGET,
3108 "Error removing statement: database error {}, {:?}",
3109 e,
3110 HexDisplay::from(hash),
3111 );
3112 return Err(Error::Db(e.to_string()));
3113 }
3114 submit_index.apply_removal(
3115 &account,
3116 &PriorityKey { hash: *hash, expiry },
3117 data_len,
3118 banned,
3119 );
3120 self.query_index.write().note_remove(hash, &statement);
3121 log::trace!(target: LOG_TARGET, "Expired statement {:?}", HexDisplay::from(hash));
3122 }
3123 Ok(true)
3124 }
3125
3126 fn report_orphan_expiry_row(&self, expiry: u64, hash: &Hash) {
3132 let _submit_index = self.submit_index.write();
3133 let body = self.db.get_size(col::STATEMENTS, hash.as_slice());
3134 let row = self.db.get(col::INDEX_BY_EXPIRY, &expiry_index_key(Expiry(expiry), hash));
3135 match (body, row) {
3136 (Ok(None), Ok(Some(_))) => log::error!(
3137 target: LOG_TARGET,
3138 "Orphan expiry index row for statement {:?}",
3139 HexDisplay::from(hash)
3140 ),
3141 (Err(e), _) | (_, Err(e)) => {
3142 log::debug!(target: LOG_TARGET, "Error checking statement presence: {:?}", e)
3143 },
3144 _ => {},
3145 }
3146 }
3147
3148 fn register_replay(&self, enqueue: &mut dyn FnMut(u64) -> bool) -> Result<Option<u64>> {
3149 let registered = {
3150 let submit_index = self.submit_index.write();
3151 let watermark = submit_index.next_seq;
3152 enqueue(watermark).then_some(watermark)
3153 }; Ok(registered)
3155 }
3156
3157 fn replay_batch(
3158 &self,
3159 filter: &OptimizedTopicFilter,
3160 cursor: u64,
3161 watermark: u64,
3162 ) -> Result<ReplayBatch> {
3163 let mut statements = Vec::new();
3164 let mut chunk_bytes = 0usize;
3165 let batch = StatementStore::admitted_statements(
3168 self,
3169 cursor,
3170 watermark,
3171 usize::MAX,
3172 &mut |_, encoded, statement| {
3173 if !filter.matches(statement) {
3174 return FilterDecision::Skip;
3175 }
3176 if !statements.is_empty() && chunk_bytes + encoded.len() > REPLAY_CHUNK_RAW_BYTES {
3177 return FilterDecision::Abort;
3178 }
3179 chunk_bytes += encoded.len();
3180 statements.push(encoded.to_vec());
3181 FilterDecision::Skip
3184 },
3185 )?;
3186 Ok(ReplayBatch { statements, cursor: batch.cursor, done: batch.done })
3187 }
3188}
3189
3190impl MultiFilterSubscriptionApi for Arc<Store> {
3191 fn create_subscription(&self) -> (SubscriptionHandle, MultiFilterEventStream) {
3192 let inner =
3193 Arc::new(parking_lot::Mutex::new(crate::subscription::SubscriptionHandleInner::new()));
3194 let snapshot_provider: Arc<dyn ReplaySnapshotProvider> = Arc::new(Arc::downgrade(self));
3195 let (sub_id, stream) = self.subscription_manager.subscribe_empty(snapshot_provider.clone());
3196
3197 let handle = SubscriptionHandle {
3198 sub_id,
3199 inner,
3200 matchers: self.subscription_manager.matchers(),
3201 snapshot_provider,
3202 };
3203 (handle, stream)
3204 }
3205}
3206
3207#[cfg(test)]
3208impl Store {
3209 fn evicted_count(&self) -> usize {
3211 self.submit_index.read().evicted_count
3212 }
3213
3214 fn is_evicted(&self, hash: &Hash) -> bool {
3216 self.db.get_size(col::EXPIRED, hash.as_slice()).ok().flatten().is_some()
3217 }
3218
3219 fn index_has_topic(&self, topic: &Topic, hash: &Hash) -> bool {
3221 self.index_set_contains(&IndexSet::Topic(*topic), hash).unwrap_or(false)
3222 }
3223
3224 fn index_has_dec_key(&self, key: &Option<DecryptionKey>, hash: &Hash) -> bool {
3226 self.index_set_contains(&IndexSet::DecKey(*key), hash).unwrap_or(false)
3227 }
3228
3229 fn statement_count(&self) -> usize {
3231 self.submit_index.read().statement_count
3232 }
3233
3234 fn details_cached(&self, who: &AccountId) -> bool {
3236 self.submit_index.read().account_statements.peek(who).is_some()
3237 }
3238
3239 fn summary_cached(&self, who: &AccountId) -> bool {
3241 self.submit_index.read().summaries.peek(who).is_some()
3242 }
3243
3244 fn total_size(&self) -> usize {
3246 self.submit_index.read().total_size
3247 }
3248
3249 fn has_account(&self, who: &AccountId) -> bool {
3251 self.next_account_from(who).expect("failed to read the account index") == Some(*who)
3252 }
3253
3254 fn account_count(&self) -> usize {
3256 self.count_accounts().expect("failed to count the accounts")
3257 }
3258
3259 fn force_insert(&self, statement: &Statement) {
3262 let hash = statement.hash();
3263 let account = statement.account_id().expect("test statements are signed; qed");
3264 let mut submit_index = self.submit_index.write();
3265 let seq = submit_index.next_seq;
3266 let details = EntryDetails {
3267 channel: statement.channel(),
3268 data_len: statement.data_len(),
3269 admission_seq: seq,
3270 };
3271 let mut commit = vec![
3272 (col::STATEMENTS, hash.to_vec(), Some(statement.encode())),
3273 (col::ADMISSION_SEQ, seq.to_be_bytes().to_vec(), Some(hash.to_vec())),
3274 ];
3275 commit.extend(statement_index_ops(&hash, statement, true));
3276 commit.extend(account_index_ops(
3277 &account,
3278 Expiry(statement.expiry()),
3279 &hash,
3280 Some(&details),
3281 ));
3282 commit.push(SubmitIndex::counters_op(
3283 submit_index.statement_count + 1,
3284 submit_index.total_size + statement.data_len(),
3285 seq.saturating_add(1),
3286 ));
3287 self.db.commit(commit).expect("failed to commit the statement");
3288 let plan = InsertPlan { seq, evicted: Vec::new(), banned: Vec::new() };
3289 submit_index.apply_insert(&account, None, hash, statement, &plan);
3290 self.query_index.write().note_insert(hash, statement, seq);
3291 }
3292}
3293
3294#[cfg(test)]
3295mod tests {
3296
3297 use crate::{col, Store, KEY_VERSION};
3298 use sc_keystore::Keystore;
3299 use sp_core::{Decode, Encode, Pair};
3300 use sp_statement_store::{
3301 AccountId, Channel, DecryptionKey, FilterDecision, InvalidReason, OptimizedTopicFilter,
3302 Proof, RejectionReason, Statement, StatementSource, StatementStore, SubmitResult, Topic,
3303 };
3304
3305 type Extrinsic = sp_runtime::OpaqueExtrinsic;
3306 type Hash = sp_core::H256;
3307 type Hashing = sp_runtime::traits::BlakeTwo256;
3308 type BlockNumber = u64;
3309 type Header = sp_runtime::generic::Header<BlockNumber, Hashing>;
3310 type Block = sp_runtime::generic::Block<Header, Extrinsic>;
3311
3312 const TEST_BEST_BLOCK_HASH: [u8; 32] = [1u8; 32];
3313
3314 const MAX_TEST_ACCOUNT_SEED: u64 = 64;
3319
3320 fn account_seed_table() -> &'static std::collections::BTreeMap<AccountId, u64> {
3325 use std::sync::OnceLock;
3326 static TABLE: OnceLock<std::collections::BTreeMap<AccountId, u64>> = OnceLock::new();
3327 TABLE.get_or_init(|| {
3328 let mut t = std::collections::BTreeMap::new();
3329 for seed in 0..=MAX_TEST_ACCOUNT_SEED {
3330 t.insert(account_keypair(seed).public().0, seed);
3331 }
3332 t
3333 })
3334 }
3335
3336 #[derive(Clone)]
3337 pub(crate) struct TestClient;
3338
3339 pub(crate) type TestBackend = sc_client_api::in_mem::Backend<Block>;
3340
3341 impl sc_client_api::StorageProvider<Block, TestBackend> for TestClient {
3342 fn storage(
3343 &self,
3344 _hash: Hash,
3345 key: &sc_client_api::StorageKey,
3346 ) -> sp_blockchain::Result<Option<sc_client_api::StorageData>> {
3347 use sp_statement_store::StatementAllowance;
3348
3349 assert_eq!(&key.0[0..21], b":statement_allowance:" as &[u8],);
3350
3351 let account_bytes: AccountId = key.0[21..53].try_into().unwrap();
3354 let seed = account_seed_table().get(&account_bytes).copied();
3355 let allowance = match seed {
3356 Some(0) => return Ok(None),
3358 Some(1) => StatementAllowance::new(1, 1000),
3359 Some(2) => StatementAllowance::new(2, 1000),
3360 Some(3) => StatementAllowance::new(3, 1000),
3361 Some(4) => StatementAllowance::new(4, 1000),
3362 Some(42) => StatementAllowance::new(42, (42 * crate::MAX_STATEMENT_SIZE) as u32),
3363 Some(50) => StatementAllowance::new(0, 1000),
3365 Some(51) => StatementAllowance::new(5, 0),
3366 Some(_) | None => StatementAllowance::new(100, 1000),
3367 };
3368 Ok(Some(sc_client_api::StorageData(allowance.encode())))
3369 }
3370
3371 fn storage_hash(
3372 &self,
3373 _hash: Hash,
3374 _key: &sc_client_api::StorageKey,
3375 ) -> sp_blockchain::Result<Option<Hash>> {
3376 unimplemented!()
3377 }
3378
3379 fn storage_keys(
3380 &self,
3381 _hash: Hash,
3382 _prefix: Option<&sc_client_api::StorageKey>,
3383 _start_key: Option<&sc_client_api::StorageKey>,
3384 ) -> sp_blockchain::Result<
3385 sc_client_api::backend::KeysIter<
3386 <TestBackend as sc_client_api::Backend<Block>>::State,
3387 Block,
3388 >,
3389 > {
3390 unimplemented!()
3391 }
3392
3393 fn storage_pairs(
3394 &self,
3395 _hash: Hash,
3396 _prefix: Option<&sc_client_api::StorageKey>,
3397 _start_key: Option<&sc_client_api::StorageKey>,
3398 ) -> sp_blockchain::Result<
3399 sc_client_api::backend::PairsIter<
3400 <TestBackend as sc_client_api::Backend<Block>>::State,
3401 Block,
3402 >,
3403 > {
3404 unimplemented!()
3405 }
3406
3407 fn child_storage(
3408 &self,
3409 _hash: Hash,
3410 _child_info: &sc_client_api::ChildInfo,
3411 _key: &sc_client_api::StorageKey,
3412 ) -> sp_blockchain::Result<Option<sc_client_api::StorageData>> {
3413 unimplemented!()
3414 }
3415
3416 fn child_storage_keys(
3417 &self,
3418 _hash: Hash,
3419 _child_info: sc_client_api::ChildInfo,
3420 _prefix: Option<&sc_client_api::StorageKey>,
3421 _start_key: Option<&sc_client_api::StorageKey>,
3422 ) -> sp_blockchain::Result<
3423 sc_client_api::backend::KeysIter<
3424 <TestBackend as sc_client_api::Backend<Block>>::State,
3425 Block,
3426 >,
3427 > {
3428 unimplemented!()
3429 }
3430
3431 fn child_storage_hash(
3432 &self,
3433 _hash: Hash,
3434 _child_info: &sc_client_api::ChildInfo,
3435 _key: &sc_client_api::StorageKey,
3436 ) -> sp_blockchain::Result<Option<Hash>> {
3437 unimplemented!()
3438 }
3439
3440 fn closest_merkle_value(
3441 &self,
3442 _hash: Hash,
3443 _key: &sc_client_api::StorageKey,
3444 ) -> sp_blockchain::Result<Option<sc_client_api::MerkleValue<Hash>>> {
3445 unimplemented!()
3446 }
3447
3448 fn child_closest_merkle_value(
3449 &self,
3450 _hash: Hash,
3451 _child_info: &sc_client_api::ChildInfo,
3452 _key: &sc_client_api::StorageKey,
3453 ) -> sp_blockchain::Result<Option<sc_client_api::MerkleValue<Hash>>> {
3454 unimplemented!()
3455 }
3456 }
3457
3458 impl sp_blockchain::HeaderBackend<Block> for TestClient {
3459 fn header(&self, _hash: Hash) -> sp_blockchain::Result<Option<Header>> {
3460 unimplemented!()
3461 }
3462 fn info(&self) -> sp_blockchain::Info<Block> {
3463 sp_blockchain::Info {
3464 best_hash: TEST_BEST_BLOCK_HASH.into(),
3465 best_number: 0,
3466 genesis_hash: Default::default(),
3467 finalized_hash: TEST_BEST_BLOCK_HASH.into(),
3468 finalized_number: 1,
3469 finalized_state: None,
3470 number_leaves: 0,
3471 block_gap: None,
3472 }
3473 }
3474 fn status(&self, _hash: Hash) -> sp_blockchain::Result<sp_blockchain::BlockStatus> {
3475 unimplemented!()
3476 }
3477 fn number(&self, _hash: Hash) -> sp_blockchain::Result<Option<BlockNumber>> {
3478 unimplemented!()
3479 }
3480 fn hash(&self, _number: BlockNumber) -> sp_blockchain::Result<Option<Hash>> {
3481 unimplemented!()
3482 }
3483 }
3484
3485 fn test_store() -> (Store, tempfile::TempDir) {
3486 sp_tracing::init_for_tests();
3487 let temp_dir = tempfile::Builder::new().tempdir().expect("Error creating test dir");
3488
3489 let client = std::sync::Arc::new(TestClient);
3490 let mut path: std::path::PathBuf = temp_dir.path().into();
3491 path.push("db");
3492 let keystore = std::sync::Arc::new(sc_keystore::LocalKeystore::in_memory());
3493 let store = Store::new::<Block, TestClient, TestBackend>(
3494 &path,
3495 Default::default(),
3496 client,
3497 keystore,
3498 None,
3499 Box::new(sp_core::testing::TaskExecutor::new()),
3500 )
3501 .unwrap();
3502 (store, temp_dir) }
3504
3505 pub fn signed_statement(data: u8) -> Statement {
3506 signed_statement_with_topics(data, &[], None)
3507 }
3508
3509 fn signed_statement_with_topics(
3510 data: u8,
3511 topics: &[Topic],
3512 dec_key: Option<DecryptionKey>,
3513 ) -> Statement {
3514 let mut statement = Statement::new();
3515 statement.set_plain_data(vec![data]);
3516 statement.set_expiry(u64::MAX);
3517
3518 for i in 0..topics.len() {
3519 statement.set_topic(i, topics[i]);
3520 }
3521 if let Some(key) = dec_key {
3522 statement.set_decryption_key(key);
3523 }
3524 let kp = sp_core::ed25519::Pair::from_string("//Alice", None).unwrap();
3525 statement.sign_ed25519_private(&kp);
3526 statement
3527 }
3528
3529 fn topic(data: u64) -> Topic {
3530 let mut bytes = [0u8; 32];
3531 bytes[0..8].copy_from_slice(&data.to_le_bytes());
3532 Topic::from(bytes)
3533 }
3534
3535 fn dec_key(data: u64) -> DecryptionKey {
3536 let mut dec_key: DecryptionKey = Default::default();
3537 dec_key[0..8].copy_from_slice(&data.to_le_bytes());
3538 dec_key
3539 }
3540
3541 fn account_keypair(seed: u64) -> sp_core::ed25519::Pair {
3549 sp_core::ed25519::Pair::from_string(&format!("//StatementAccount{seed}"), None)
3550 .expect("Derivation path is valid; qed")
3551 }
3552
3553 fn account(id: u64) -> AccountId {
3554 account_keypair(id).public().0
3555 }
3556
3557 fn sign_with(stmt: &mut Statement, account_id: u64) {
3560 stmt.sign_ed25519_private(&account_keypair(account_id));
3561 }
3562
3563 fn channel(id: u64) -> Channel {
3564 let mut channel: Channel = Default::default();
3565 channel[0..8].copy_from_slice(&id.to_le_bytes());
3566 channel
3567 }
3568
3569 fn unsigned_statement(
3573 account_id: u64,
3574 priority: u32,
3575 c: Option<u64>,
3576 data_len: usize,
3577 ) -> Statement {
3578 assert!(
3579 account_id <= MAX_TEST_ACCOUNT_SEED,
3580 "account_id {account_id} exceeds MAX_TEST_ACCOUNT_SEED ({MAX_TEST_ACCOUNT_SEED}); \
3581 raise the constant if you need a wider range",
3582 );
3583 let mut statement = Statement::new();
3584 let mut data = Vec::new();
3585 data.resize(data_len, 0);
3586 statement.set_plain_data(data);
3587 statement.set_expiry_from_parts(u32::MAX, priority);
3588 if let Some(c) = c {
3589 statement.set_channel(channel(c));
3590 }
3591 statement
3592 }
3593
3594 fn statement(account_id: u64, priority: u32, c: Option<u64>, data_len: usize) -> Statement {
3595 let mut statement = unsigned_statement(account_id, priority, c, data_len);
3596 sign_with(&mut statement, account_id);
3597 statement
3598 }
3599
3600 #[test]
3601 fn submit_one() {
3602 let (store, _temp) = test_store();
3603 let statement0 = signed_statement(0);
3604 assert_eq!(store.submit(statement0, StatementSource::Network), SubmitResult::New);
3605 let statement1 = statement(1, 1, None, 0);
3606 assert_eq!(store.submit(statement1, StatementSource::Network), SubmitResult::New);
3607 }
3608
3609 #[test]
3610 fn depleted_count_allowance_admits_nothing() {
3611 let (store, _temp) = test_store();
3612 let result = store.submit(statement(50, 1, None, 100), StatementSource::Network);
3614 assert_eq!(result, SubmitResult::Rejected(RejectionReason::NoAllowance));
3615 assert_eq!(store.statement_count(), 0);
3616 assert_eq!(store.total_size(), 0);
3617 }
3618
3619 #[test]
3620 fn depleted_size_allowance_admits_nothing() {
3621 let (store, _temp) = test_store();
3622 let result = store.submit(statement(51, 1, None, 0), StatementSource::Network);
3624 assert_eq!(result, SubmitResult::Rejected(RejectionReason::NoAllowance));
3625 assert_eq!(store.statement_count(), 0);
3626 assert_eq!(store.total_size(), 0);
3627 }
3628
3629 #[test]
3630 fn save_and_load_statements() {
3631 let (store, temp) = test_store();
3632 let statement0 = signed_statement(0);
3633 let statement1 = signed_statement(1);
3634 let statement2 = signed_statement(2);
3635 assert_eq!(store.submit(statement0.clone(), StatementSource::Network), SubmitResult::New);
3636 assert_eq!(store.submit(statement1.clone(), StatementSource::Network), SubmitResult::New);
3637 assert_eq!(store.submit(statement2.clone(), StatementSource::Network), SubmitResult::New);
3638 assert_eq!(store.statements().unwrap().len(), 3);
3639 assert_eq!(store.broadcasts(&[]).unwrap().len(), 3);
3640 assert_eq!(store.statement(&statement1.hash()).unwrap(), Some(statement1.clone()));
3641 let keystore = store.keystore.clone();
3642 drop(store);
3643
3644 let client = std::sync::Arc::new(TestClient);
3645 let mut path: std::path::PathBuf = temp.path().into();
3646 path.push("db");
3647 let store = Store::new::<Block, TestClient, TestBackend>(
3648 &path,
3649 Default::default(),
3650 client,
3651 keystore,
3652 None,
3653 Box::new(sp_core::testing::TaskExecutor::new()),
3654 )
3655 .unwrap();
3656 assert_eq!(store.statements().unwrap().len(), 3);
3657 assert_eq!(store.broadcasts(&[]).unwrap().len(), 3);
3658 assert_eq!(store.statement(&statement1.hash()).unwrap(), Some(statement1));
3659 }
3660
3661 #[test]
3662 fn admission_seq_is_monotonic_across_restarts() {
3663 let (store, temp) = test_store();
3667 let first = signed_statement(10);
3668 let second = signed_statement(11);
3669 assert_eq!(store.submit(first.clone(), StatementSource::Network), SubmitResult::New);
3670 assert_eq!(store.submit(second.clone(), StatementSource::Network), SubmitResult::New);
3671 assert_eq!(
3672 store.db.get(col::ADMISSION_SEQ, &0u64.to_be_bytes()).unwrap(),
3673 Some(first.hash().to_vec())
3674 );
3675 assert_eq!(
3676 store.db.get(col::ADMISSION_SEQ, &1u64.to_be_bytes()).unwrap(),
3677 Some(second.hash().to_vec())
3678 );
3679 store.remove(&second.hash()).unwrap();
3680 assert_eq!(
3681 store.db.get(col::ADMISSION_SEQ, &0u64.to_be_bytes()).unwrap(),
3682 Some(first.hash().to_vec())
3683 );
3684 assert_eq!(store.db.get(col::ADMISSION_SEQ, &1u64.to_be_bytes()).unwrap(), None);
3685 let keystore = store.keystore.clone();
3686 drop(store);
3687
3688 let mut path: std::path::PathBuf = temp.path().into();
3689 path.push("db");
3690 let store = Store::new::<Block, TestClient, TestBackend>(
3691 &path,
3692 Default::default(),
3693 std::sync::Arc::new(TestClient),
3694 keystore,
3695 None,
3696 Box::new(sp_core::testing::TaskExecutor::new()),
3697 )
3698 .unwrap();
3699 assert_eq!(store.submit_index.read().next_seq, 2);
3700
3701 let replay = store.replay_batch(&OptimizedTopicFilter::Any, 0, 2).unwrap();
3702 assert_eq!(replay.statements, vec![first.encode()]);
3703 assert!(replay.done);
3704
3705 let third = signed_statement(12);
3708 assert_eq!(store.submit(third.clone(), StatementSource::Network), SubmitResult::New);
3709 assert_eq!(store.db.get(col::ADMISSION_SEQ, &1u64.to_be_bytes()).unwrap(), None);
3710 assert_eq!(
3711 store.db.get(col::ADMISSION_SEQ, &2u64.to_be_bytes()).unwrap(),
3712 Some(third.hash().to_vec())
3713 );
3714 }
3715
3716 #[test]
3717 fn lagging_counters_row_does_not_reuse_admission_seqs() {
3718 let (store, temp) = test_store();
3722 let first = signed_statement(1);
3723 let second = signed_statement(2);
3724 assert_eq!(store.submit(first.clone(), StatementSource::Network), SubmitResult::New);
3725 assert_eq!(store.submit(second.clone(), StatementSource::Network), SubmitResult::New);
3726 let (statement_count, total_size, _) = <(u64, u64, u64)>::decode(
3728 &mut store.db.get(col::META, crate::KEY_COUNTERS).unwrap().unwrap().as_slice(),
3729 )
3730 .unwrap();
3731 store
3732 .db
3733 .commit([(
3734 col::META,
3735 crate::KEY_COUNTERS.to_vec(),
3736 Some((statement_count, total_size, 0u64).encode()),
3737 )])
3738 .unwrap();
3739 let keystore = store.keystore.clone();
3740 drop(store);
3741
3742 let mut path: std::path::PathBuf = temp.path().into();
3743 path.push("db");
3744 let store = Store::new::<Block, TestClient, TestBackend>(
3745 &path,
3746 Default::default(),
3747 std::sync::Arc::new(TestClient),
3748 keystore,
3749 None,
3750 Box::new(sp_core::testing::TaskExecutor::new()),
3751 )
3752 .unwrap();
3753 assert_eq!(store.submit_index.read().next_seq, 2);
3754
3755 let third = signed_statement(3);
3757 assert_eq!(store.submit(third.clone(), StatementSource::Network), SubmitResult::New);
3758 assert_eq!(
3759 store.db.get(col::ADMISSION_SEQ, &0u64.to_be_bytes()).unwrap(),
3760 Some(first.hash().to_vec())
3761 );
3762 assert_eq!(
3763 store.db.get(col::ADMISSION_SEQ, &1u64.to_be_bytes()).unwrap(),
3764 Some(second.hash().to_vec())
3765 );
3766 assert_eq!(
3767 store.db.get(col::ADMISSION_SEQ, &2u64.to_be_bytes()).unwrap(),
3768 Some(third.hash().to_vec())
3769 );
3770 }
3771
3772 #[test]
3773 fn statement_without_admission_entry_is_restored_by_migration() {
3774 let (store, temp) = test_store();
3779 let statement = signed_statement_with_topics(20, &[topic(1)], None);
3780 assert_eq!(store.submit(statement.clone(), StatementSource::Network), SubmitResult::New);
3781 store
3784 .db
3785 .commit([
3786 (col::ADMISSION_SEQ, 0u64.to_be_bytes().to_vec(), None),
3787 (col::META, KEY_VERSION.to_vec(), Some(1u32.to_le_bytes().to_vec())),
3788 ])
3789 .unwrap();
3790 let filter = OptimizedTopicFilter::MatchAny([topic(1)].into_iter().collect());
3791 assert!(store.replay_batch(&filter, 0, 1).unwrap().statements.is_empty());
3792 let keystore = store.keystore.clone();
3793 drop(store);
3794
3795 let mut path: std::path::PathBuf = temp.path().into();
3796 path.push("db");
3797 let store = Store::new::<Block, TestClient, TestBackend>(
3798 &path,
3799 Default::default(),
3800 std::sync::Arc::new(TestClient),
3801 keystore,
3802 None,
3803 Box::new(sp_core::testing::TaskExecutor::new()),
3804 )
3805 .unwrap();
3806
3807 let watermark = store.submit_index.read().next_seq;
3808 assert_eq!(watermark, 1);
3809 assert_eq!(
3810 store.replay_batch(&filter, 0, watermark).unwrap().statements,
3811 vec![statement.encode()]
3812 );
3813 }
3814
3815 #[test]
3816 fn admission_cursor_resumes_from_the_middle_of_the_range() {
3817 let (store, _temp) = test_store();
3818 let first = signed_statement(1);
3819 let second = signed_statement(2);
3820 let third = signed_statement(3);
3821 for statement in [&first, &second, &third] {
3822 assert_eq!(
3823 store.submit(statement.clone(), StatementSource::Network),
3824 SubmitResult::New
3825 );
3826 }
3827 let batch = store.replay_batch(&OptimizedTopicFilter::Any, 1, 3).unwrap();
3828 assert_eq!(batch.statements, vec![second.encode(), third.encode()]);
3829 assert_eq!(batch.cursor, 3);
3830 assert!(batch.done);
3831 }
3832
3833 #[test]
3834 fn admission_cursor_replays_only_current_matching_admissions() {
3835 let (store, _temp) = test_store();
3836 let matching = signed_statement_with_topics(20, &[topic(1)], None);
3837 let non_matching = signed_statement_with_topics(21, &[topic(2)], None);
3838 let removed = signed_statement_with_topics(22, &[topic(1)], None);
3839 assert_eq!(store.submit(matching.clone(), StatementSource::Network), SubmitResult::New);
3840 assert_eq!(store.submit(non_matching, StatementSource::Network), SubmitResult::New);
3841 assert_eq!(store.submit(removed.clone(), StatementSource::Network), SubmitResult::New);
3842 store.remove(&removed.hash()).unwrap();
3843
3844 let replay = store
3845 .replay_batch(&OptimizedTopicFilter::MatchAny([topic(1)].into_iter().collect()), 0, 3)
3846 .unwrap();
3847 assert_eq!(replay.statements, vec![matching.encode()]);
3848 assert_eq!(replay.cursor, 3);
3849 assert!(replay.done);
3850 }
3851
3852 #[test]
3853 fn admission_watermark_is_one_past_the_newest_admission() {
3854 let (store, _temp) = test_store();
3855 assert_eq!(store.admission_watermark().unwrap(), 0);
3856
3857 let first = signed_statement(1);
3858 let second = signed_statement(2);
3859 assert_eq!(store.submit(first.clone(), StatementSource::Network), SubmitResult::New);
3860 assert_eq!(store.submit(second, StatementSource::Network), SubmitResult::New);
3861 assert_eq!(store.admission_watermark().unwrap(), 2);
3862
3863 store.remove(&first.hash()).unwrap();
3865 assert_eq!(store.admission_watermark().unwrap(), 2);
3866 }
3867
3868 #[test]
3869 fn admitted_statements_walk_applies_filter_decisions_and_resumes() {
3870 let (store, _temp) = test_store();
3871 let statements: Vec<_> = (1..=4).map(signed_statement).collect();
3872 for statement in &statements {
3873 assert_eq!(
3874 store.submit(statement.clone(), StatementSource::Network),
3875 SubmitResult::New
3876 );
3877 }
3878 store.remove(&statements[1].hash()).unwrap();
3880
3881 let batch = store
3882 .admitted_statements(0, 4, usize::MAX, &mut |hash, _encoded, _statement| {
3883 if *hash == statements[0].hash() {
3884 FilterDecision::Skip
3885 } else if *hash == statements[3].hash() {
3886 FilterDecision::Abort
3887 } else {
3888 FilterDecision::Take
3889 }
3890 })
3891 .unwrap();
3892 assert_eq!(batch.statements, vec![(statements[2].hash(), statements[2].clone())]);
3893 assert_eq!(batch.cursor, 3);
3895 assert!(!batch.done);
3896
3897 let batch = store
3899 .admitted_statements(batch.cursor, 4, usize::MAX, &mut |_, _, _| FilterDecision::Take)
3900 .unwrap();
3901 assert_eq!(batch.statements, vec![(statements[3].hash(), statements[3].clone())]);
3902 assert_eq!(batch.cursor, 4);
3903 assert!(batch.done);
3904 }
3905
3906 #[test]
3907 fn admitted_statements_walk_stops_at_the_scan_limit() {
3908 let (store, _temp) = test_store();
3909 let statements: Vec<_> = (1..=4).map(signed_statement).collect();
3910 for statement in &statements {
3911 assert_eq!(
3912 store.submit(statement.clone(), StatementSource::Network),
3913 SubmitResult::New
3914 );
3915 }
3916
3917 let mut visited = 0;
3919 let batch = store
3920 .admitted_statements(0, 4, 3, &mut |_, _, _| {
3921 visited += 1;
3922 FilterDecision::Skip
3923 })
3924 .unwrap();
3925 assert!(batch.statements.is_empty());
3926 assert_eq!(visited, 3);
3927 assert_eq!(batch.cursor, 3, "the cursor must sit after the last visited entry");
3928 assert!(!batch.done);
3929
3930 let batch = store
3932 .admitted_statements(batch.cursor, 4, 3, &mut |_, _, _| FilterDecision::Skip)
3933 .unwrap();
3934 assert!(batch.statements.is_empty());
3935 assert_eq!(batch.cursor, 4);
3936 assert!(batch.done);
3937 }
3938
3939 #[test]
3940 fn take_recent_statements_are_ordered_by_admission() {
3941 let (store, _temp) = test_store();
3942 let statements: Vec<_> = (1..=3).map(signed_statement).collect();
3943 for statement in &statements {
3944 assert_eq!(
3945 store.submit(statement.clone(), StatementSource::Network),
3946 SubmitResult::New
3947 );
3948 }
3949
3950 let recent = store.take_recent_statements().unwrap();
3951 let expected: Vec<_> = statements
3952 .into_iter()
3953 .enumerate()
3954 .map(|(seq, statement)| (seq as u64, statement.hash(), statement))
3955 .collect();
3956 assert_eq!(recent, expected);
3957 }
3958
3959 #[test]
3960 fn migrates_v1_database_to_on_disk_index() {
3961 sp_tracing::init_for_tests();
3962 let temp = tempfile::Builder::new().tempdir().expect("Error creating test dir");
3963 let mut path: std::path::PathBuf = temp.path().into();
3964 path.push("db");
3965 let mut db_path = path.clone();
3967 db_path.push("statements");
3968
3969 let addressed = signed_statement_with_topics(1, &[topic(1), topic(2)], Some(dec_key(9)));
3972 let broadcast = signed_statement_with_topics(2, &[topic(1)], None);
3973 let h_addressed = addressed.hash();
3974 let h_broadcast = broadcast.hash();
3975
3976 let expired_hash = topic(999);
3979 let expired_ts = 10_000_000_000u64;
3980
3981 {
3983 let mut cfg = parity_db::Options::with_columns(&db_path, 3);
3984 let statement_col = &mut cfg.columns[1];
3985 statement_col.ref_counted = false;
3986 statement_col.preimage = true;
3987 statement_col.uniform = true;
3988 let db = parity_db::Db::open_or_create(&cfg).unwrap();
3989 db.commit([
3990 (0u8, b"version".to_vec(), Some(1u32.to_le_bytes().to_vec())),
3991 (1u8, h_addressed.to_vec(), Some(addressed.encode())),
3992 (1u8, h_broadcast.to_vec(), Some(broadcast.encode())),
3993 (2u8, expired_hash.to_vec(), Some((expired_hash, expired_ts).encode())),
3994 ])
3995 .unwrap();
3996 }
3997
3998 let open = |path: &std::path::Path| {
3999 Store::new::<Block, TestClient, TestBackend>(
4000 path,
4001 Default::default(),
4002 std::sync::Arc::new(TestClient),
4003 std::sync::Arc::new(sc_keystore::LocalKeystore::in_memory()),
4004 None,
4005 Box::new(sp_core::testing::TaskExecutor::new()),
4006 )
4007 .unwrap()
4008 };
4009
4010 let store = open(&path);
4013
4014 assert_eq!(store.statements().unwrap().len(), 2);
4016 assert!(store.statement(&h_addressed).unwrap().is_some());
4017 assert!(store.statement(&h_broadcast).unwrap().is_some());
4018
4019 assert!(store.index_has_topic(&topic(1), &h_addressed));
4021 assert!(store.index_has_topic(&topic(1), &h_broadcast));
4022 assert!(store.index_has_topic(&topic(2), &h_addressed));
4023 assert!(store.index_has_dec_key(&Some(dec_key(9)), &h_addressed));
4024 assert!(store.index_has_dec_key(&None, &h_broadcast));
4025 let mut admitted = std::collections::HashSet::new();
4026 let mut iter = store.db.iter(col::ADMISSION_SEQ).unwrap();
4027 iter.seek_to_first().unwrap();
4028 while let Some((_key, value)) = iter.next().unwrap() {
4029 admitted.insert(value.as_slice().try_into().expect("admission hash is 32 bytes"));
4030 }
4031 assert_eq!(admitted, std::collections::HashSet::from([h_addressed, h_broadcast]));
4032 assert_eq!(store.submit_index.read().next_seq, 2);
4033
4034 assert_eq!(store.broadcasts(&[topic(1)]).unwrap().len(), 1);
4037 assert_eq!(store.posted(&[topic(1)], dec_key(9)).unwrap().len(), 1);
4038
4039 assert!(store.is_evicted(&expired_hash));
4041 assert_eq!(store.evicted_count(), 1);
4042
4043 assert_eq!(store.statement_count(), 2);
4045 assert_eq!(store.total_size(), 2);
4046 assert!(store.has_account(&addressed.account_id().unwrap()));
4047 assert_eq!(store.account_count(), 1);
4048
4049 drop(store);
4051 let store = open(&path);
4052 assert_eq!(store.statements().unwrap().len(), 2);
4053 assert!(store.index_has_topic(&topic(1), &h_broadcast));
4054 assert!(store.is_evicted(&expired_hash));
4055 }
4056
4057 #[test]
4058 fn interrupted_migration_resumes_preserving_admission_entries() {
4059 sp_tracing::init_for_tests();
4064 let temp = tempfile::Builder::new().tempdir().expect("Error creating test dir");
4065 let mut path: std::path::PathBuf = temp.path().into();
4066 path.push("db");
4067 let mut db_path = path.clone();
4068 db_path.push("statements");
4069
4070 let s1 = signed_statement_with_topics(1, &[topic(1)], None);
4071 let s2 = statement(2, 5, Some(9), 200);
4072
4073 {
4077 let mut cfg = parity_db::Options::with_columns(&db_path, col::COUNT);
4078 let statement_col = &mut cfg.columns[col::STATEMENTS as usize];
4079 statement_col.ref_counted = false;
4080 statement_col.preimage = true;
4081 statement_col.uniform = true;
4082 for c in crate::BTREE_COLUMNS {
4083 cfg.columns[c as usize].btree_index = true;
4084 }
4085 let db = parity_db::Db::open_or_create(&cfg).unwrap();
4086 let mut commit: Vec<(u8, Vec<u8>, Option<Vec<u8>>)> = vec![
4087 (col::META, b"version".to_vec(), Some(1u32.to_le_bytes().to_vec())),
4088 (col::STATEMENTS, s1.hash().to_vec(), Some(s1.encode())),
4089 (col::STATEMENTS, s2.hash().to_vec(), Some(s2.encode())),
4090 (col::ADMISSION_SEQ, 7u64.to_be_bytes().to_vec(), Some(s1.hash().to_vec())),
4091 ];
4092 commit.extend(crate::statement_index_ops(&s1.hash(), &s1, true));
4093 db.commit(commit).unwrap();
4094 }
4095
4096 let store = Store::new::<Block, TestClient, TestBackend>(
4097 &path,
4098 Default::default(),
4099 std::sync::Arc::new(TestClient),
4100 std::sync::Arc::new(sc_keystore::LocalKeystore::in_memory()),
4101 None,
4102 Box::new(sp_core::testing::TaskExecutor::new()),
4103 )
4104 .unwrap();
4105
4106 assert_eq!(store.statements().unwrap().len(), 2);
4108 assert_eq!(store.statement_count(), 2);
4109 assert_eq!(store.total_size(), 1 + 200);
4110 assert!(store.has_account(&s1.account_id().unwrap()));
4111 assert!(store.has_account(&account(2)));
4112 assert!(store
4113 .db
4114 .get(
4115 col::INDEX_BY_EXPIRY,
4116 &crate::expiry_index_key(crate::Expiry(s2.expiry()), &s2.hash())
4117 )
4118 .unwrap()
4119 .is_some());
4120
4121 assert_eq!(
4123 store.db.get(col::ADMISSION_SEQ, &7u64.to_be_bytes()).unwrap(),
4124 Some(s1.hash().to_vec())
4125 );
4126 assert_eq!(
4127 store.db.get(col::ADMISSION_SEQ, &8u64.to_be_bytes()).unwrap(),
4128 Some(s2.hash().to_vec())
4129 );
4130 assert_eq!(store.submit_index.read().next_seq, 9);
4131 let replay = store.replay_batch(&OptimizedTopicFilter::Any, 7, 9).unwrap();
4132 assert_eq!(replay.statements, vec![s1.encode(), s2.encode()]);
4133
4134 assert!(matches!(
4136 store.submit(statement(2, 4, Some(9), 100), StatementSource::Network),
4137 SubmitResult::Rejected(RejectionReason::ChannelPriorityTooLow { .. })
4138 ));
4139 }
4140
4141 #[test]
4142 fn counters_and_seq_survive_restart() {
4143 let (store, temp) = test_store();
4144 for i in 0..3u8 {
4145 assert_eq!(
4146 store.submit(signed_statement(i), StatementSource::Network),
4147 SubmitResult::New
4148 );
4149 }
4150 assert_eq!(
4151 store.submit(statement(1, 1, None, 100), StatementSource::Network),
4152 SubmitResult::New
4153 );
4154 assert_eq!(store.statement_count(), 4);
4155 assert_eq!(store.total_size(), 3 + 100);
4156 let keystore = store.keystore.clone();
4157 drop(store);
4158
4159 let mut path: std::path::PathBuf = temp.path().into();
4160 path.push("db");
4161 let store = Store::new::<Block, TestClient, TestBackend>(
4162 &path,
4163 Default::default(),
4164 std::sync::Arc::new(TestClient),
4165 keystore,
4166 None,
4167 Box::new(sp_core::testing::TaskExecutor::new()),
4168 )
4169 .unwrap();
4170 assert_eq!(store.statement_count(), 4);
4171 assert_eq!(store.total_size(), 3 + 100);
4172 assert_eq!(store.submit_index.read().next_seq, 4);
4173 assert_eq!(store.account_count(), 2);
4175 }
4176
4177 #[test]
4178 fn resubmitting_live_statement_is_known_and_not_double_counted() {
4179 let (store, _temp) = test_store();
4180 let statement = statement(1, 1, None, 100);
4181 assert_eq!(store.submit(statement.clone(), StatementSource::Local), SubmitResult::New);
4182 assert_eq!(store.total_size(), 100);
4183 assert_eq!(store.submit(statement.clone(), StatementSource::Local), SubmitResult::Known);
4184 assert_eq!(store.submit(statement, StatementSource::Network), SubmitResult::Known);
4185 assert_eq!(store.total_size(), 100);
4186 assert_eq!(store.statement_count(), 1);
4187 }
4188
4189 #[test]
4190 fn eviction_clears_all_index_rows() {
4191 let (mut store, _temp) = test_store();
4192 store.set_time(0);
4193 let mut stmt = unsigned_statement(1, 1, Some(7), 100);
4194 stmt.set_topic(0, topic(3));
4195 stmt.set_expiry_from_parts(500, 1);
4196 sign_with(&mut stmt, 1);
4197 let hash = stmt.hash();
4198 let expiry = crate::Expiry(stmt.expiry());
4199 let who = account(1);
4200 assert_eq!(store.submit(stmt, StatementSource::Network), SubmitResult::New);
4201 let account_key = crate::account_index_key(&who, expiry, &hash);
4202 let expiry_key = crate::expiry_index_key(expiry, &hash);
4203 assert!(store.db.get(col::INDEX_BY_ACCOUNT, &account_key).unwrap().is_some());
4204 assert!(store.db.get(col::INDEX_BY_EXPIRY, &expiry_key).unwrap().is_some());
4205
4206 store.remove(&hash).unwrap();
4207 assert!(store.db.get(col::INDEX_BY_ACCOUNT, &account_key).unwrap().is_none());
4208 assert!(store.db.get(col::INDEX_BY_EXPIRY, &expiry_key).unwrap().is_none());
4209 assert!(!store.index_has_topic(&topic(3), &hash));
4210 assert_eq!(store.db.get(col::ADMISSION_SEQ, &0u64.to_be_bytes()).unwrap(), None);
4211 assert!(store.is_evicted(&hash));
4213 assert_eq!(store.statement_count(), 0);
4214 assert_eq!(store.total_size(), 0);
4215 }
4216
4217 #[test]
4218 fn index_key_layouts_roundtrip() {
4219 use crate::{
4223 account_index_key, dec_key_index_key, dec_key_index_prefix, evicted_index_key,
4224 expiry_index_key, hash_from_index_key, parse_account_index_key,
4225 parse_dec_key_index_prefix, parse_time_index_key, prefix_from_index_key,
4226 topic_index_key, Expiry,
4227 };
4228
4229 let who = account(7);
4230 let hash: crate::Hash = account(8);
4231 let expiry = Expiry((123u64 << 32) | 45);
4232
4233 let key = account_index_key(&who, expiry, &hash);
4234 assert_eq!(parse_account_index_key(&key), Some((who, expiry, hash)));
4235 assert_eq!(parse_account_index_key(&key[..key.len() - 1]), None);
4237 assert_eq!(parse_account_index_key(&[]), None);
4238
4239 let key = expiry_index_key(expiry, &hash);
4240 assert_eq!(parse_time_index_key(&key), Some((expiry.0, hash)));
4241 assert_eq!(parse_time_index_key(&key[1..]), None);
4242
4243 let key = evicted_index_key(789, &hash);
4244 assert_eq!(parse_time_index_key(&key), Some((789, hash)));
4245
4246 assert!(expiry_index_key(Expiry(256), &hash) > expiry_index_key(Expiry(1), &hash));
4249 assert!(evicted_index_key(256, &hash) > evicted_index_key(1, &hash));
4250
4251 let key = topic_index_key(&topic(3), &hash);
4252 assert_eq!(hash_from_index_key(&key), Some(hash));
4253 assert_eq!(prefix_from_index_key(&key), Some(&topic(3)[..]));
4254
4255 for dec_key in [None, Some(dec_key(5))] {
4256 assert_eq!(parse_dec_key_index_prefix(&dec_key_index_prefix(&dec_key)), Some(dec_key));
4257 let key = dec_key_index_key(&dec_key, &hash);
4258 assert_eq!(hash_from_index_key(&key), Some(hash));
4259 assert_eq!(
4260 prefix_from_index_key(&key).and_then(parse_dec_key_index_prefix),
4261 Some(dec_key)
4262 );
4263 }
4264 }
4265
4266 #[test]
4267 fn summary_fast_path_stays_coherent_with_disk() {
4268 use crate::{DETAILS_CACHE_BUDGET, SUMMARY_CACHE_ACCOUNTS};
4269
4270 let (store, _temp) = test_store();
4271 let source = StatementSource::Network;
4272
4273 assert!(SUMMARY_CACHE_ACCOUNTS >= 3, "three account summaries must fit the cache");
4275 let per_filler_account = DETAILS_CACHE_BUDGET.div_ceil(2);
4278 assert!(
4279 per_filler_account <= 100,
4280 "the filler accounts' allowance (100 statements, 1000 bytes of data) caps the cache \
4281 budget this test can exercise",
4282 );
4283
4284 let s5_1 = statement(5, 1, None, 100);
4286 assert_eq!(store.submit(s5_1.clone(), source), SubmitResult::New);
4287 assert!(store.details_cached(&account(5)));
4288
4289 for acc in [6u64, 7] {
4292 for c in 1..=per_filler_account as u64 {
4293 assert_eq!(store.submit(statement(acc, 1, Some(c), 10), source), SubmitResult::New);
4294 }
4295 }
4296 assert!(!store.details_cached(&account(5)));
4297
4298 let s5_2 = statement(5, 2, None, 50);
4301 assert_eq!(store.submit(s5_2.clone(), source), SubmitResult::New);
4302 assert!(!store.details_cached(&account(5)));
4303
4304 let s5_3 = statement(5, 3, None, 900);
4308 assert_eq!(store.submit(s5_3.clone(), source), SubmitResult::New);
4309 assert!(store.details_cached(&account(5)));
4310 assert!(!store.has_statement(&s5_1.hash()), "lowest priority must be evicted");
4311 assert!(store.has_statement(&s5_2.hash()));
4312 assert!(store.has_statement(&s5_3.hash()));
4313 let filler_statements = 2 * per_filler_account;
4314 assert_eq!(store.statement_count(), 2 + filler_statements);
4315 assert_eq!(store.total_size(), 50 + 900 + filler_statements * 10);
4316 }
4317
4318 #[test]
4319 fn rejected_submission_caches_the_loaded_record() {
4320 let (store, temp) = test_store();
4321 let source = StatementSource::Network;
4322
4323 assert_eq!(store.submit(statement(5, 2, Some(1), 100), source), SubmitResult::New);
4325
4326 let keystore = store.keystore.clone();
4328 drop(store);
4329 let mut path: std::path::PathBuf = temp.path().into();
4330 path.push("db");
4331 let store = Store::new::<Block, TestClient, TestBackend>(
4332 &path,
4333 Default::default(),
4334 std::sync::Arc::new(TestClient),
4335 keystore,
4336 None,
4337 Box::new(sp_core::testing::TaskExecutor::new()),
4338 )
4339 .unwrap();
4340
4341 assert_eq!(
4344 store.submit(statement(5, 3, None, 1500), source),
4345 SubmitResult::Rejected(RejectionReason::DataTooLarge {
4346 submitted_size: 1500,
4347 available_size: 1000,
4348 })
4349 );
4350 assert!(!store.details_cached(&account(5)));
4351 assert!(!store.summary_cached(&account(5)));
4352
4353 assert!(matches!(
4357 store.submit(statement(5, 1, Some(1), 100), source),
4358 SubmitResult::Rejected(RejectionReason::ChannelPriorityTooLow { .. })
4359 ));
4360 assert!(store.details_cached(&account(5)));
4361 assert!(store.summary_cached(&account(5)));
4362 }
4363
4364 #[test]
4365 fn take_recent_statements_clears_index() {
4366 let (store, _temp) = test_store();
4367 let statement0 = signed_statement(0);
4368 let statement1 = signed_statement(1);
4369 let statement2 = signed_statement(2);
4370 let statement3 = signed_statement(3);
4371
4372 let _ = store.submit(statement0.clone(), StatementSource::Local);
4373 let _ = store.submit(statement1.clone(), StatementSource::Local);
4374 let _ = store.submit(statement2.clone(), StatementSource::Local);
4375
4376 let recent1 = store.take_recent_statements().unwrap();
4377 let (recent1_hashes, recent1_statements): (Vec<_>, Vec<_>) =
4378 recent1.into_iter().map(|(_seq, hash, statement)| (hash, statement)).unzip();
4379 let expected1 = vec![statement0, statement1, statement2];
4380 assert!(expected1.iter().all(|s| recent1_hashes.contains(&s.hash())));
4381 assert!(expected1.iter().all(|s| recent1_statements.contains(s)));
4382
4383 let recent2 = store.take_recent_statements().unwrap();
4385 assert_eq!(recent2.len(), 0);
4386
4387 store.submit(statement3.clone(), StatementSource::Network);
4388
4389 let recent3 = store.take_recent_statements().unwrap();
4390 let (recent3_hashes, recent3_statements): (Vec<_>, Vec<_>) =
4391 recent3.into_iter().map(|(_seq, hash, statement)| (hash, statement)).unzip();
4392 let expected3 = vec![statement3];
4393 assert!(expected3.iter().all(|s| recent3_hashes.contains(&s.hash())));
4394 assert!(expected3.iter().all(|s| recent3_statements.contains(s)));
4395
4396 assert_eq!(store.statements().unwrap().len(), 4);
4398 }
4399
4400 #[test]
4401 fn search_by_topic_and_key() {
4402 let (store, _temp) = test_store();
4403 let statement0 = signed_statement(0);
4404 let statement1 = signed_statement_with_topics(1, &[topic(0)], None);
4405 let statement2 = signed_statement_with_topics(2, &[topic(0), topic(1)], Some(dec_key(2)));
4406 let statement3 = signed_statement_with_topics(3, &[topic(0), topic(1), topic(2)], None);
4407 let statement4 =
4408 signed_statement_with_topics(4, &[topic(0), topic(42), topic(2), topic(3)], None);
4409 let statements = vec![statement0, statement1, statement2, statement3, statement4];
4410 for s in &statements {
4411 store.submit(s.clone(), StatementSource::Network);
4412 }
4413
4414 let assert_topics = |topics: &[u64], key: Option<u64>, expected: &[u8]| {
4415 let key = key.map(dec_key);
4416 let topics: Vec<_> = topics.iter().map(|t| topic(*t)).collect();
4417 let mut got_vals: Vec<_> = if let Some(key) = key {
4418 store.posted(&topics, key).unwrap().into_iter().map(|d| d[0]).collect()
4419 } else {
4420 store.broadcasts(&topics).unwrap().into_iter().map(|d| d[0]).collect()
4421 };
4422 got_vals.sort();
4423 assert_eq!(expected.to_vec(), got_vals);
4424 };
4425
4426 assert_topics(&[], None, &[0, 1, 3, 4]);
4427 assert_topics(&[], Some(2), &[2]);
4428 assert_topics(&[0], None, &[1, 3, 4]);
4429 assert_topics(&[1], None, &[3]);
4430 assert_topics(&[2], None, &[3, 4]);
4431 assert_topics(&[3], None, &[4]);
4432 assert_topics(&[42], None, &[4]);
4433
4434 assert_topics(&[0, 1], None, &[3]);
4435 assert_topics(&[0, 1], Some(2), &[2]);
4436 assert_topics(&[0, 1, 99], Some(2), &[]);
4437 assert_topics(&[1, 2], None, &[3]);
4438 assert_topics(&[99], None, &[]);
4439 assert_topics(&[0, 99], None, &[]);
4440 assert_topics(&[0, 1, 2, 3, 42], None, &[]);
4441 }
4442
4443 #[test]
4444 fn constraints() {
4445 let (store, _temp) = test_store();
4446
4447 store.submit_index.write().config.max_total_size = 3000;
4448 let source = StatementSource::Network;
4449 let ok = SubmitResult::New;
4450
4451 assert!(matches!(
4455 store.submit(statement(1, 1, Some(1), 2000), source),
4456 SubmitResult::Rejected(_)
4457 ));
4458 assert_eq!(store.submit(statement(1, 1, Some(1), 500), source), ok);
4459 assert!(matches!(
4461 store.submit(statement(1, 1, Some(1), 200), source),
4462 SubmitResult::Rejected(_)
4463 ));
4464 assert_eq!(store.submit(statement(1, 2, Some(1), 600), source), ok);
4465 assert!(matches!(
4468 store.submit(statement(1, 1, Some(2), 100), source),
4469 SubmitResult::Rejected(_)
4470 ));
4471 assert_eq!(store.evicted_count(), 1);
4472
4473 let s2_prio1 = statement(2, 1, None, 500);
4476 let s2_prio2 = statement(2, 2, None, 100);
4477 assert_eq!(store.submit(s2_prio1.clone(), source), ok);
4478 assert_eq!(store.submit(s2_prio2.clone(), source), ok);
4479 assert!(matches!(
4481 store.submit(statement(2, 1, None, 50), source),
4482 SubmitResult::Rejected(RejectionReason::AccountFull { .. })
4483 ));
4484 let s2_prio3 = statement(2, 3, None, 500);
4486 assert_eq!(store.submit(s2_prio3.clone(), source), ok);
4487 assert_eq!(store.evicted_count(), 2);
4488 assert!(store.is_evicted(&s2_prio1.hash()));
4489 assert!(store.statement(&s2_prio1.hash()).unwrap().is_none());
4490 assert_eq!(store.submit(statement(2, 4, None, 1000), source), ok);
4492 assert_eq!(store.evicted_count(), 4);
4493 assert!(store.is_evicted(&s2_prio2.hash()));
4494 assert!(store.is_evicted(&s2_prio3.hash()));
4495
4496 let s3_prio2 = statement(3, 2, Some(1), 300);
4499 let s3_prio3 = statement(3, 3, Some(2), 300);
4500 assert_eq!(store.submit(s3_prio2.clone(), source), ok);
4501 assert_eq!(store.submit(s3_prio3.clone(), source), ok);
4502 assert_eq!(store.submit(statement(3, 4, Some(3), 300), source), ok);
4503 assert_eq!(store.submit(statement(3, 5, None, 500), source), ok);
4505 assert_eq!(store.evicted_count(), 6);
4506 assert!(store.is_evicted(&s3_prio2.hash()));
4507 assert!(store.is_evicted(&s3_prio3.hash()));
4508
4509 assert_eq!(store.total_size(), 2400);
4510 assert_eq!(store.statement_count(), 4);
4511
4512 assert!(matches!(
4514 store.submit(statement(1, 1, None, 700), source),
4515 SubmitResult::Rejected(_)
4516 ));
4517 store.submit_index.write().config.max_total_statements = 4;
4519 assert!(matches!(
4520 store.submit(statement(1, 1, None, 100), source),
4521 SubmitResult::Rejected(_)
4522 ));
4523
4524 let mut expected_statements = vec![
4525 statement(1, 2, Some(1), 600).hash(),
4526 statement(2, 4, None, 1000).hash(),
4527 statement(3, 4, Some(3), 300).hash(),
4528 statement(3, 5, None, 500).hash(),
4529 ];
4530 expected_statements.sort();
4531 let mut statements: Vec<_> =
4532 store.statements().unwrap().into_iter().map(|(hash, _)| hash).collect();
4533 statements.sort();
4534 assert_eq!(expected_statements, statements);
4535 }
4536
4537 #[test]
4538 fn insufficient_remaining_account_bytes_is_data_too_large() {
4539 let (store, _temp) = test_store();
4540 let source = StatementSource::Network;
4541
4542 assert_eq!(store.submit(statement(4, 2, None, 900), source), SubmitResult::New);
4545 assert_eq!(
4546 store.submit(statement(4, 1, None, 200), source),
4547 SubmitResult::Rejected(RejectionReason::DataTooLarge {
4548 submitted_size: 200,
4549 available_size: 100,
4550 })
4551 );
4552 }
4553
4554 #[test]
4555 fn max_statement_size_for_gossiping() {
4556 let (store, _temp) = test_store();
4557 store.submit_index.write().config.max_total_size = 42 * crate::MAX_STATEMENT_SIZE;
4558
4559 assert_eq!(
4560 store.submit(
4561 statement(42, 1, Some(1), crate::MAX_STATEMENT_SIZE - 500),
4562 StatementSource::Local
4563 ),
4564 SubmitResult::New
4565 );
4566
4567 assert!(matches!(
4568 store.submit(
4569 statement(42, 2, Some(1), 2 * crate::MAX_STATEMENT_SIZE),
4570 StatementSource::Local
4571 ),
4572 SubmitResult::Invalid(_)
4573 ));
4574 }
4575
4576 #[test]
4577 fn expired_statements_are_purged() {
4578 use super::DEFAULT_PURGE_AFTER_SEC;
4579 let (mut store, temp) = test_store();
4580 let mut statement = unsigned_statement(1, 1, Some(3), 100);
4581 store.set_time(0);
4582 statement.set_topic(0, topic(4));
4583 sign_with(&mut statement, 1);
4584 store.submit(statement.clone(), StatementSource::Network);
4585 assert_eq!(store.statement_count(), 1);
4586 store.remove(&statement.hash()).unwrap();
4587 assert_eq!(store.statement_count(), 0);
4588 assert_eq!(store.account_count(), 0);
4589 store.set_time(DEFAULT_PURGE_AFTER_SEC + 1);
4590 store.maintain();
4591 assert_eq!(store.evicted_count(), 0);
4592 let keystore = store.keystore.clone();
4593 drop(store);
4594
4595 let client = std::sync::Arc::new(TestClient);
4596 let mut path: std::path::PathBuf = temp.path().into();
4597 path.push("db");
4598 let store = Store::new::<Block, TestClient, TestBackend>(
4599 &path,
4600 Default::default(),
4601 client,
4602 keystore,
4603 None,
4604 Box::new(sp_core::testing::TaskExecutor::new()),
4605 )
4606 .unwrap();
4607 assert_eq!(store.statements().unwrap().len(), 0);
4608 assert_eq!(store.evicted_count(), 0);
4609 }
4610
4611 #[test]
4612 fn posted_clear_decrypts() {
4613 let (store, _temp) = test_store();
4614 let public = store
4615 .keystore
4616 .ed25519_generate_new(sp_core::crypto::key_types::STATEMENT, None)
4617 .unwrap();
4618 let statement1 = statement(1, 1, None, 100);
4619 let mut statement2 = unsigned_statement(1, 2, None, 0);
4620 let plain = b"The most valuable secret".to_vec();
4621 statement2.encrypt(&plain, &public).unwrap();
4622 sign_with(&mut statement2, 1);
4623 store.submit(statement1, StatementSource::Network);
4624 store.submit(statement2, StatementSource::Network);
4625 let posted_clear = store.posted_clear(&[], public.into()).unwrap();
4626 assert_eq!(posted_clear, vec![plain]);
4627 }
4628
4629 #[test]
4630 fn broadcasts_stmt_returns_encoded_statements() {
4631 let (store, _tmp) = test_store();
4632
4633 let s0 = signed_statement_with_topics(0, &[], None);
4635 let s1 = signed_statement_with_topics(1, &[topic(42)], None);
4637 let s2 = signed_statement_with_topics(2, &[topic(42)], Some(dec_key(99)));
4639
4640 for s in [&s0, &s1, &s2] {
4641 store.submit(s.clone(), StatementSource::Network);
4642 }
4643
4644 let mut hashes: Vec<_> = store
4646 .broadcasts_stmt(&[])
4647 .unwrap()
4648 .into_iter()
4649 .map(|bytes| Statement::decode(&mut &bytes[..]).unwrap().hash())
4650 .collect();
4651 hashes.sort();
4652 let expected_hashes = {
4653 let mut e = vec![s0.hash(), s1.hash()];
4654 e.sort();
4655 e
4656 };
4657 assert_eq!(hashes, expected_hashes);
4658
4659 let got = store.broadcasts_stmt(&[topic(42)]).unwrap();
4661 assert_eq!(got.len(), 1);
4662 let st = Statement::decode(&mut &got[0][..]).unwrap();
4663 assert_eq!(st.hash(), s1.hash());
4664 }
4665
4666 #[test]
4667 fn posted_stmt_returns_encoded_statements_for_dest() {
4668 let (store, _tmp) = test_store();
4669
4670 let public1 = store
4671 .keystore
4672 .ed25519_generate_new(sp_core::crypto::key_types::STATEMENT, None)
4673 .unwrap();
4674 let dest: [u8; 32] = public1.into();
4675
4676 let public2 = store
4677 .keystore
4678 .ed25519_generate_new(sp_core::crypto::key_types::STATEMENT, None)
4679 .unwrap();
4680
4681 let mut s_with_key = unsigned_statement(1, 1, None, 0);
4683 let plain1 = b"The most valuable secret".to_vec();
4684 s_with_key.encrypt(&plain1, &public1).unwrap();
4685 sign_with(&mut s_with_key, 1);
4686
4687 let mut s_other_key = unsigned_statement(2, 2, None, 0);
4689 let plain2 = b"The second most valuable secret".to_vec();
4690 s_other_key.encrypt(&plain2, &public2).unwrap();
4691 sign_with(&mut s_other_key, 2);
4692
4693 for s in [&s_with_key, &s_other_key] {
4695 store.submit(s.clone(), StatementSource::Network);
4696 }
4697
4698 let retrieved = store.posted_stmt(&[], dest).unwrap();
4700 assert_eq!(retrieved.len(), 1, "Only one statement has dec_key=dest");
4701
4702 let returned_stmt = Statement::decode(&mut &retrieved[0][..]).unwrap();
4704 assert_eq!(
4705 returned_stmt.hash(),
4706 s_with_key.hash(),
4707 "Returned statement must match s_with_key"
4708 );
4709 }
4710
4711 #[test]
4712 fn posted_clear_stmt_returns_statement_followed_by_plain_data() {
4713 let (store, _tmp) = test_store();
4714
4715 let public1 = store
4716 .keystore
4717 .ed25519_generate_new(sp_core::crypto::key_types::STATEMENT, None)
4718 .unwrap();
4719 let dest: [u8; 32] = public1.into();
4720
4721 let public2 = store
4722 .keystore
4723 .ed25519_generate_new(sp_core::crypto::key_types::STATEMENT, None)
4724 .unwrap();
4725
4726 let mut s_with_key = unsigned_statement(1, 1, None, 0);
4728 let plain1 = b"The most valuable secret".to_vec();
4729 s_with_key.encrypt(&plain1, &public1).unwrap();
4730 sign_with(&mut s_with_key, 1);
4731
4732 let mut s_other_key = unsigned_statement(2, 2, None, 0);
4734 let plain2 = b"The second most valuable secret".to_vec();
4735 s_other_key.encrypt(&plain2, &public2).unwrap();
4736 sign_with(&mut s_other_key, 2);
4737
4738 for s in [&s_with_key, &s_other_key] {
4740 store.submit(s.clone(), StatementSource::Network);
4741 }
4742
4743 let retrieved = store.posted_clear_stmt(&[], dest).unwrap();
4745 assert_eq!(retrieved.len(), 1, "Only one statement has dec_key=dest");
4746
4747 let encoded_stmt = s_with_key.encode();
4749 let stmt_len = encoded_stmt.len();
4750
4751 assert_eq!(&retrieved[0][..stmt_len], &encoded_stmt[..]);
4753
4754 let trailing = &retrieved[0][stmt_len..];
4756 assert_eq!(trailing, &plain1[..]);
4757 }
4758
4759 #[test]
4760 fn posted_clear_returns_plain_data_for_dest_and_topics() {
4761 let (store, _tmp) = test_store();
4762
4763 let public_dest = store
4765 .keystore
4766 .ed25519_generate_new(sp_core::crypto::key_types::STATEMENT, None)
4767 .unwrap();
4768 let dest: [u8; 32] = public_dest.into();
4769
4770 let public_other = store
4771 .keystore
4772 .ed25519_generate_new(sp_core::crypto::key_types::STATEMENT, None)
4773 .unwrap();
4774
4775 let mut s_good = unsigned_statement(1, 1, None, 0);
4777 let plaintext_good = b"The most valuable secret".to_vec();
4778 s_good.encrypt(&plaintext_good, &public_dest).unwrap();
4779 s_good.set_topic(0, topic(42));
4780 sign_with(&mut s_good, 1);
4781
4782 let mut s_wrong_topic = unsigned_statement(2, 2, None, 0);
4784 s_wrong_topic.encrypt(b"Wrong topic", &public_dest).unwrap();
4785 s_wrong_topic.set_topic(0, topic(99));
4786 sign_with(&mut s_wrong_topic, 2);
4787
4788 let mut s_other_dest = unsigned_statement(3, 3, None, 0);
4790 s_other_dest.encrypt(b"Other dest", &public_other).unwrap();
4791 s_other_dest.set_topic(0, topic(42));
4792 sign_with(&mut s_other_dest, 3);
4793
4794 for s in [&s_good, &s_wrong_topic, &s_other_dest] {
4796 store.submit(s.clone(), StatementSource::Network);
4797 }
4798
4799 let retrieved = store.posted_clear(&[topic(42)], dest).unwrap();
4801
4802 assert_eq!(retrieved, vec![plaintext_good]);
4804 }
4805
4806 #[test]
4807 fn already_expired_statement_is_rejected() {
4808 let (mut store, _temp) = test_store();
4809
4810 store.set_time(1000);
4812
4813 let mut expired_statement = unsigned_statement(1, 1, None, 100);
4816 expired_statement.set_expiry_from_parts(500, 1);
4818 sign_with(&mut expired_statement, 1);
4819
4820 assert_eq!(
4822 store.submit(expired_statement, StatementSource::Network),
4823 SubmitResult::Invalid(InvalidReason::AlreadyExpired)
4824 );
4825
4826 assert_eq!(store.statements().unwrap().len(), 0);
4828
4829 let mut valid_statement = unsigned_statement(1, 1, None, 100);
4832 valid_statement.set_expiry_from_parts(2000, 1);
4833 sign_with(&mut valid_statement, 1);
4834
4835 assert_eq!(store.submit(valid_statement, StatementSource::Network), SubmitResult::New);
4837 assert_eq!(store.statements().unwrap().len(), 1);
4838 }
4839
4840 #[test]
4841 fn remove_by_covers_various_situations() {
4842 use sp_statement_store::{StatementSource, StatementStore, SubmitResult};
4843
4844 let (mut store, _temp) = test_store();
4846 store.set_time(0);
4847
4848 let t42 = topic(42);
4850 let k7 = dec_key(7);
4851
4852 let mut s_a1 = unsigned_statement(4, 10, Some(100), 100);
4855 s_a1.set_topic(0, t42);
4856 sign_with(&mut s_a1, 4);
4857 let h_a1 = s_a1.hash();
4858
4859 let mut s_a2 = unsigned_statement(4, 20, Some(200), 150);
4860 s_a2.set_decryption_key(k7);
4861 sign_with(&mut s_a2, 4);
4862 let h_a2 = s_a2.hash();
4863
4864 let s_a3 = statement(4, 30, None, 50);
4865 let h_a3 = s_a3.hash();
4866
4867 let s_b1 = statement(3, 10, None, 100);
4869 let h_b1 = s_b1.hash();
4870
4871 let mut s_b2 = unsigned_statement(3, 15, Some(300), 100);
4872 s_b2.set_topic(0, t42);
4873 s_b2.set_decryption_key(k7);
4874 sign_with(&mut s_b2, 3);
4875 let h_b2 = s_b2.hash();
4876
4877 for s in [&s_a1, &s_a2, &s_a3, &s_b1, &s_b2] {
4879 assert_eq!(store.submit(s.clone(), StatementSource::Network), SubmitResult::New);
4880 }
4881
4882 {
4884 assert_eq!(store.statement_count(), 5, "all 5 should be present");
4885 assert!(store.has_account(&account(4)));
4886 assert!(store.has_account(&account(3)));
4887 assert_eq!(store.total_size(), 100 + 150 + 50 + 100 + 100);
4888
4889 assert!(store.index_has_topic(&t42, &h_a1) && store.index_has_topic(&t42, &h_b2));
4891 assert!(
4892 store.index_has_dec_key(&Some(k7), &h_a2) &&
4893 store.index_has_dec_key(&Some(k7), &h_b2)
4894 );
4895 }
4896
4897 store.remove_by(account(4)).expect("remove_by should succeed");
4899
4900 {
4902 for h in [h_a1, h_a2, h_a3] {
4904 assert!(store.statement(&h).unwrap().is_none(), "A's statement should be removed");
4905 }
4906
4907 for h in [h_b1, h_b2] {
4909 assert!(store.statement(&h).unwrap().is_some(), "B's statement should remain");
4910 }
4911
4912 assert!(!store.has_account(&account(4)), "Account A must be gone");
4914 assert!(store.has_account(&account(3)), "Account B must remain");
4915 assert_eq!(store.statement_count(), 2);
4917 assert_eq!(store.total_size(), 100 + 100);
4918
4919 assert!(store.is_evicted(&h_a1));
4921 assert!(store.is_evicted(&h_a2));
4922 assert!(store.is_evicted(&h_a3));
4923 assert_eq!(store.evicted_count(), 3);
4924
4925 assert!(store.index_has_topic(&t42, &h_b2));
4927 assert!(!store.index_has_topic(&t42, &h_a1));
4928
4929 assert!(store.index_has_dec_key(&Some(k7), &h_b2));
4931 assert!(!store.index_has_dec_key(&Some(k7), &h_a2));
4932 }
4933
4934 store.remove_by(account(4)).expect("second remove_by should be a no-op");
4936
4937 let purge_after = store.submit_index.read().config.purge_after_sec;
4939 store.set_time(purge_after + 1);
4940 store.maintain();
4941 assert_eq!(store.evicted_count(), 0, "expired entries should be purged");
4942
4943 let s_new = statement(4, 40, None, 10);
4945 assert_eq!(store.submit(s_new, StatementSource::Network), SubmitResult::New);
4946 }
4947
4948 #[test]
4949 fn check_expiration_expires_statements_past_current_time() {
4950 let (mut store, _temp) = test_store();
4951
4952 store.set_time(100);
4957
4958 let mut expired_stmt = unsigned_statement(1, 1, None, 100);
4960 expired_stmt.set_expiry_from_parts(500, 1);
4961 sign_with(&mut expired_stmt, 1);
4962 let expired_hash = expired_stmt.hash();
4963 store.submit(expired_stmt, StatementSource::Network);
4964
4965 let valid_stmt = statement(2, 1, None, 100); let valid_hash = valid_stmt.hash();
4968 store.submit(valid_stmt, StatementSource::Network);
4969
4970 assert_eq!(store.statement_count(), 2);
4972
4973 store.set_time(1000);
4975
4976 store.enforce_limits();
4978
4979 assert!(
4982 !store.is_evicted(&expired_hash),
4983 "Naturally expired statement must not be added to the expired map"
4984 );
4985 assert!(
4986 !store.has_statement(&expired_hash),
4987 "Expired statement should be removed from the store"
4988 );
4989
4990 assert!(store.has_statement(&valid_hash), "Valid statement should still be stored");
4992 assert!(!store.is_evicted(&valid_hash), "Valid statement should not be expired");
4993 }
4994
4995 #[test]
4996 fn allowance_sweep_resumes_from_cursor_across_calls() {
4997 use std::time::Duration;
4998
4999 let (mut store, _temp) = test_store();
5000 store.set_time(0);
5001
5002 for seed in 10u64..15 {
5005 store.force_insert(&statement(seed, 1, None, 600));
5006 store.force_insert(&statement(seed, 2, None, 600));
5007 }
5008 assert_eq!(store.statement_count(), 10);
5009 assert!(store.submit_index.read().allowance_cursor.is_none());
5010
5011 store.enforce_limits_bounded(usize::MAX, 2, Duration::from_secs(3600));
5014 assert_eq!(store.statement_count(), 8);
5015 assert!(store.submit_index.read().allowance_cursor.is_some());
5016
5017 store.enforce_limits_bounded(usize::MAX, 2, Duration::from_secs(3600));
5018 assert_eq!(store.statement_count(), 6);
5019 assert!(store.submit_index.read().allowance_cursor.is_some());
5020
5021 store.enforce_limits_bounded(usize::MAX, 2, Duration::from_secs(3600));
5024 assert_eq!(store.statement_count(), 5);
5025 assert!(store.submit_index.read().allowance_cursor.is_none());
5026 assert_eq!(store.known_accounts_count.load(std::sync::atomic::Ordering::Relaxed), 5);
5027
5028 store.enforce_limits();
5030 assert_eq!(store.statement_count(), 5);
5031 }
5032
5033 #[test]
5034 fn allowance_eviction_budget_defers_the_residue_to_the_next_pass() {
5035 use std::time::Duration;
5036
5037 let (mut store, _temp) = test_store();
5038 store.set_time(0);
5039
5040 for priority in 1..=4 {
5042 store.force_insert(&statement(1, priority, None, 100));
5043 }
5044 assert_eq!(store.statement_count(), 4);
5045
5046 store.enforce_limits_bounded(2, usize::MAX, Duration::from_secs(3600));
5049 assert_eq!(store.statement_count(), 2);
5050
5051 store.enforce_limits_bounded(2, usize::MAX, Duration::from_secs(3600));
5053 assert_eq!(store.statement_count(), 2);
5054 assert!(store.submit_index.read().allowance_cursor.is_none());
5055
5056 store.enforce_limits_bounded(2, usize::MAX, Duration::from_secs(3600));
5058 assert_eq!(store.statement_count(), 1);
5059 }
5060
5061 #[test]
5062 fn expiry_sweep_is_bounded_by_the_statement_budget() {
5063 use std::time::Duration;
5064
5065 let (mut store, _temp) = test_store();
5066 store.set_time(100);
5067
5068 for seed in 1u64..=5 {
5070 let mut stmt = unsigned_statement(seed, 1, None, 100);
5071 stmt.set_expiry_from_parts(200, 1);
5072 sign_with(&mut stmt, seed);
5073 assert_eq!(store.submit(stmt, StatementSource::Network), SubmitResult::New);
5074 }
5075 store.set_time(300);
5076
5077 store.enforce_limits_bounded(2, usize::MAX, Duration::from_secs(3600));
5080 assert_eq!(store.statement_count(), 3);
5081 store.enforce_limits_bounded(2, usize::MAX, Duration::from_secs(3600));
5082 assert_eq!(store.statement_count(), 1);
5083 store.enforce_limits_bounded(2, usize::MAX, Duration::from_secs(3600));
5084 assert_eq!(store.statement_count(), 0);
5085 assert_eq!(store.evicted_count(), 0);
5087 }
5088
5089 #[test]
5090 fn expiry_sweep_leaves_inconsistent_data_in_place() {
5091 let (mut store, _temp) = test_store();
5092 store.set_time(100);
5093
5094 let expiry = crate::Expiry(200u64 << 32 | 1);
5095 let undecodable_hash = [0xAB_u8; 32];
5096 let undecodable_key = crate::expiry_index_key(expiry, &undecodable_hash);
5097 store
5098 .db
5099 .commit([
5100 (col::STATEMENTS, undecodable_hash.to_vec(), Some(vec![0xFF])),
5101 (col::INDEX_BY_EXPIRY, undecodable_key.clone(), Some(Vec::new())),
5102 ])
5103 .unwrap();
5104
5105 store.set_time(300);
5108 store.enforce_limits();
5109 store.enforce_limits();
5110 assert!(store.has_statement(&undecodable_hash));
5111 assert!(store.db.get(col::INDEX_BY_EXPIRY, &undecodable_key).unwrap().is_some());
5112
5113 let orphan_hash = [0xCD_u8; 32];
5116 let orphan_key = crate::expiry_index_key(expiry, &orphan_hash);
5117 store
5118 .db
5119 .commit([(col::INDEX_BY_EXPIRY, orphan_key.clone(), Some(Vec::new()))])
5120 .unwrap();
5121 store.enforce_limits();
5122 assert!(!store.has_statement(&orphan_hash));
5123 assert!(store.db.get(col::INDEX_BY_EXPIRY, &orphan_key).unwrap().is_some());
5124 }
5125
5126 #[test]
5127 fn corrupt_account_index_key_does_not_end_account_enumeration() {
5128 let (store, _temp) = test_store();
5129 assert_eq!(
5130 store.submit(statement(1, 1, None, 1), StatementSource::Network),
5131 SubmitResult::New
5132 );
5133 assert_eq!(
5134 store.submit(statement(2, 1, None, 1), StatementSource::Network),
5135 SubmitResult::New
5136 );
5137 assert_eq!(store.account_count(), 2);
5138
5139 let (first, second) = {
5143 let (a, b) = (account(1), account(2));
5144 if a < b {
5145 (a, b)
5146 } else {
5147 (b, a)
5148 }
5149 };
5150 let mut between = first.to_vec();
5151 between.extend_from_slice(&[0xFF; 41]);
5152 store
5153 .db
5154 .commit([
5155 (col::INDEX_BY_ACCOUNT, vec![0u8; 33], Some(Vec::new())),
5156 (col::INDEX_BY_ACCOUNT, between, Some(Vec::new())),
5157 ])
5158 .unwrap();
5159
5160 assert_eq!(store.account_count(), 2);
5161 assert!(store.has_account(&first));
5162 assert!(store.has_account(&second));
5163
5164 store.enforce_limits();
5166 assert_eq!(store.known_accounts_count.load(std::sync::atomic::Ordering::Relaxed), 2);
5167 }
5168
5169 #[test]
5170 fn concurrent_remove_and_resubmit_keep_query_bookkeeping_consistent() {
5171 use std::sync::Arc;
5176
5177 let (store, _temp) = test_store();
5178 let store = Arc::new(store);
5179 let mut stmt = unsigned_statement(1, 1, None, 100);
5180 stmt.set_topic(0, topic(7));
5181 sign_with(&mut stmt, 1);
5182 let hash = stmt.hash();
5183
5184 for _ in 0..200 {
5185 if !store.has_statement(&hash) {
5187 assert_eq!(store.submit(stmt.clone(), StatementSource::Local), SubmitResult::New);
5188 }
5189 let remover = {
5190 let store = store.clone();
5191 std::thread::spawn(move || store.remove(&hash).expect("remove succeeds"))
5192 };
5193 let resubmitter = {
5194 let store = store.clone();
5195 let stmt = stmt.clone();
5196 std::thread::spawn(move || store.submit(stmt, StatementSource::Local))
5197 };
5198 remover.join().expect("remover joins");
5199 let _ = resubmitter.join().expect("resubmitter joins");
5200
5201 let query_index = store.query_index.read();
5203 let present = store.has_statement(&hash);
5204 assert_eq!(query_index.recent.contains_key(&hash), present);
5205 assert_eq!(
5206 query_index.topic_counts.get(&topic(7)).copied().unwrap_or(0),
5207 present as usize
5208 );
5209 }
5210 }
5211
5212 #[test]
5213 fn check_expiration_handles_multiple_statements_per_account() {
5214 let (mut store, _temp) = test_store();
5215 store.set_time(100);
5216
5217 let mut stmt1 = unsigned_statement(42, 1, Some(1), 100);
5220 stmt1.set_expiry_from_parts(200, 1); sign_with(&mut stmt1, 42);
5222 let hash1 = stmt1.hash();
5223 store.submit(stmt1, StatementSource::Network);
5224
5225 let mut stmt2 = unsigned_statement(42, 2, Some(2), 100);
5226 stmt2.set_expiry_from_parts(300, 2); sign_with(&mut stmt2, 42);
5228 let hash2 = stmt2.hash();
5229 store.submit(stmt2, StatementSource::Network);
5230
5231 let mut stmt3 = unsigned_statement(42, 3, Some(3), 100);
5232 stmt3.set_expiry_from_parts(500, 3); sign_with(&mut stmt3, 42);
5234 let hash3 = stmt3.hash();
5235 store.submit(stmt3, StatementSource::Network);
5236
5237 assert_eq!(store.statement_count(), 3);
5239
5240 store.set_time(250);
5242 store.enforce_limits();
5243
5244 {
5245 assert!(!store.is_evicted(&hash1), "stmt1 naturally expired, not in map");
5247 assert!(!store.is_evicted(&hash2), "stmt2 should not be expired yet");
5248 assert!(!store.is_evicted(&hash3), "stmt3 should not be expired yet");
5249 assert_eq!(store.statement_count(), 2);
5250 }
5251
5252 store.set_time(400);
5254 store.enforce_limits();
5255
5256 {
5257 assert!(!store.is_evicted(&hash1));
5258 assert!(!store.is_evicted(&hash2), "stmt2 naturally expired, not in map");
5259 assert!(!store.is_evicted(&hash3), "stmt3 should not be expired yet");
5260 assert_eq!(store.statement_count(), 1);
5261 }
5262
5263 store.set_time(600);
5265 store.enforce_limits();
5266
5267 {
5268 assert!(!store.is_evicted(&hash1));
5269 assert!(!store.is_evicted(&hash2));
5270 assert!(!store.is_evicted(&hash3), "stmt3 naturally expired, not in map");
5271 assert_eq!(store.statement_count(), 0);
5272 }
5273 }
5274
5275 #[test]
5276 fn check_expiration_does_nothing_when_no_expired_statements() {
5277 let (mut store, _temp) = test_store();
5278 store.set_time(1000);
5279
5280 let stmt = statement(1, 1, None, 100);
5283 let hash = stmt.hash();
5284 store.submit(stmt, StatementSource::Network);
5285
5286 store.enforce_limits();
5288
5289 assert!(store.has_statement(&hash));
5291 assert!(!store.is_evicted(&hash));
5292 assert_eq!(store.statement_count(), 1);
5293 assert_eq!(store.evicted_count(), 0);
5294 }
5295
5296 #[test]
5297 fn check_expiration_correctly_updates_account_data() {
5298 let (mut store, _temp) = test_store();
5299 store.set_time(100);
5300
5301 let mut stmt = unsigned_statement(1, 1, Some(1), 100);
5303 stmt.set_expiry_from_parts(200, 1);
5304 sign_with(&mut stmt, 1);
5305 let hash = stmt.hash();
5306 store.submit(stmt, StatementSource::Network);
5307
5308 {
5310 assert!(store.has_account(&account(1)));
5311 assert_eq!(store.total_size(), 100);
5312 }
5313
5314 store.set_time(300);
5316 store.enforce_limits();
5317
5318 {
5320 assert!(
5321 !store.has_account(&account(1)),
5322 "Account should be removed when all its statements expire"
5323 );
5324 assert_eq!(store.total_size(), 0, "Total size should be zero");
5325 assert!(!store.is_evicted(&hash), "Naturally expired, not in map");
5326 }
5327 }
5328
5329 #[test]
5330 fn check_expiration_clears_topic_and_key_indexes() {
5331 let (mut store, _temp) = test_store();
5332 store.set_time(100);
5333
5334 let mut stmt = unsigned_statement(1, 1, Some(1), 100);
5336 stmt.set_expiry_from_parts(200, 1);
5337 stmt.set_topic(0, topic(42));
5338 stmt.set_decryption_key(dec_key(7));
5339 sign_with(&mut stmt, 1);
5340 let hash = stmt.hash();
5341 store.submit(stmt, StatementSource::Network);
5342
5343 {
5345 assert!(store.index_has_topic(&topic(42), &hash));
5346 assert!(store.index_has_dec_key(&Some(dec_key(7)), &hash));
5347 }
5348
5349 store.set_time(300);
5351 store.enforce_limits();
5352
5353 {
5355 assert!(!store.index_has_topic(&topic(42), &hash), "Topic index should be cleared");
5356 assert!(
5357 !store.index_has_dec_key(&Some(dec_key(7)), &hash),
5358 "Decryption key index should be cleared"
5359 );
5360 assert!(!store.is_evicted(&hash), "Naturally expired, not in map");
5361 }
5362 }
5363
5364 #[test]
5365 fn check_expiration_handles_empty_store() {
5366 let (mut store, _temp) = test_store();
5367 store.set_time(1000);
5368
5369 store.enforce_limits();
5371
5372 store.enforce_limits();
5374
5375 assert!(store.submit_index.read().allowance_cursor.is_none());
5376 assert_eq!(store.statement_count(), 0);
5377 assert_eq!(store.evicted_count(), 0);
5378 }
5379
5380 #[test]
5381 fn check_expiration_expires_properly_formatted_statements() {
5382 let (mut store, _temp) = test_store();
5386 store.set_time(1000);
5387
5388 let mut stmt = unsigned_statement(1, 1, None, 100);
5390 stmt.set_expiry_from_parts(1001, 1); sign_with(&mut stmt, 1);
5392 let hash = stmt.hash();
5393 store.submit(stmt, StatementSource::Network);
5394
5395 assert_eq!(store.statement_count(), 1);
5396
5397 store.set_time(2000);
5399 store.enforce_limits();
5400
5401 assert!(
5405 !store.has_statement(&hash),
5406 "Statement should be removed from the store after expiration"
5407 );
5408 assert!(!store.is_evicted(&hash), "Naturally expired, not in map");
5410 }
5411
5412 #[test]
5413 fn check_expiration_updates_database_columns() {
5414 let (mut store, _temp) = test_store();
5416 store.set_time(100);
5417
5418 let mut stmt = unsigned_statement(1, 1, None, 100);
5420 stmt.set_expiry_from_parts(200, 1);
5421 sign_with(&mut stmt, 1);
5422 let hash = stmt.hash();
5423 store.submit(stmt.clone(), StatementSource::Network);
5424
5425 let db_entry = store.db.get(col::STATEMENTS, &hash).unwrap();
5427 assert!(db_entry.is_some(), "Statement should be in col::STATEMENTS after submit");
5428
5429 store.set_time(300);
5431 store.enforce_limits();
5432
5433 {
5435 assert_eq!(store.statement_count(), 0, "Statement should be removed from the store");
5436 assert!(
5438 !store.is_evicted(&hash),
5439 "Naturally expired statement must not be in the expired map"
5440 );
5441 }
5442
5443 let db_entry = store.db.get(col::STATEMENTS, &hash).unwrap();
5444 assert!(
5445 db_entry.is_none(),
5446 "Statement should be removed from col::STATEMENTS after expiration"
5447 );
5448
5449 let expired_entry = store.db.get(col::EXPIRED, &hash).unwrap();
5452 assert!(expired_entry.is_none(), "Naturally expired: not written to col::EXPIRED");
5453 }
5454
5455 #[test]
5456 fn enforce_allowances_evicts_excess_statements() {
5457 let (mut store, _temp) = test_store();
5462 store.set_time(0);
5463
5464 let s1 = statement(4, 10, None, 100); let s2 = statement(4, 20, None, 100);
5467 let s3 = statement(4, 30, None, 100);
5468 let s4 = statement(4, 40, None, 100);
5469 let s5 = statement(4, 50, None, 100); let h1 = s1.hash();
5472 let h5 = s5.hash();
5473
5474 for statement in [&s1, &s2, &s3, &s4, &s5] {
5476 store.force_insert(statement);
5477 }
5478
5479 assert_eq!(store.statement_count(), 5);
5481 assert_eq!(store.total_size(), 500);
5482
5483 store.enforce_limits();
5486
5487 assert_eq!(store.statement_count(), 4, "Should have 4 statements after eviction");
5489 assert!(!store.has_statement(&h1), "Lowest priority should be evicted");
5490 assert!(store.has_statement(&h5), "Highest priority should remain");
5491 assert_eq!(store.total_size(), 400);
5492
5493 assert!(store.is_evicted(&h1));
5495 }
5496
5497 #[test]
5498 fn enforce_allowances_evicts_all_when_no_allowance_found() {
5499 let (mut store, _temp) = test_store();
5500 store.set_time(0);
5501
5502 let s1 = statement(0, 10, None, 100);
5504 let s2 = statement(0, 20, None, 150);
5505
5506 let h1 = s1.hash();
5507 let h2 = s2.hash();
5508
5509 store.force_insert(&s1);
5511 store.force_insert(&s2);
5512
5513 assert_eq!(store.statement_count(), 2);
5514
5515 store.enforce_limits();
5517
5518 assert_eq!(store.statement_count(), 0, "All statements should be evicted");
5519 assert!(!store.has_account(&account(0)), "Account should be removed");
5520 assert!(store.is_evicted(&h1));
5521 assert!(store.is_evicted(&h2));
5522 }
5523
5524 #[test]
5525 fn enforce_allowances_based_on_size() {
5526 let (mut store, _temp) = test_store();
5528 store.set_time(0);
5529
5530 let s1 = statement(2, 10, None, 600); let s2 = statement(2, 20, None, 600); let h1 = s1.hash();
5536 let h2 = s2.hash();
5537
5538 store.force_insert(&s1);
5540 store.force_insert(&s2);
5541
5542 assert_eq!(store.total_size(), 1200);
5543
5544 store.enforce_limits();
5546
5547 assert_eq!(store.statement_count(), 1);
5548 assert!(store.has_statement(&h2), "Higher priority should remain");
5549 assert!(!store.has_statement(&h1), "Lower priority should be evicted");
5550 assert_eq!(store.total_size(), 600);
5551 }
5552
5553 #[test]
5554 fn channel_replacement_only_higher_priority_succeeds() {
5555 let (store, _temp) = test_store();
5556 let source = StatementSource::Network;
5557
5558 let s1 = statement(1, 5, Some(1), 100);
5561 let h1 = s1.hash();
5562 assert_eq!(store.submit(s1, source), SubmitResult::New);
5563
5564 let result = store.submit(statement(1, 3, Some(1), 100), source);
5566 assert!(
5567 matches!(result, SubmitResult::Rejected(RejectionReason::ChannelPriorityTooLow { .. })),
5568 "Lower priority should be rejected with ChannelPriorityTooLow, got: {result:?}"
5569 );
5570
5571 let result = store.submit(statement(1, 5, Some(1), 101), source);
5574 assert!(
5575 matches!(result, SubmitResult::Rejected(RejectionReason::ChannelPriorityTooLow { .. })),
5576 "Equal priority should be rejected with ChannelPriorityTooLow, got: {result:?}"
5577 );
5578
5579 let s2 = statement(1, 10, Some(1), 200);
5581 let h2 = s2.hash();
5582 assert_eq!(store.submit(s2, source), SubmitResult::New);
5583
5584 {
5585 assert_eq!(store.statement_count(), 1);
5586 assert!(!store.has_statement(&h1), "Old channel message should be gone");
5587 assert!(store.has_statement(&h2), "New channel message should exist");
5588 assert!(store.is_evicted(&h1), "Old should be in expired");
5589 assert_eq!(store.total_size(), 200);
5590 }
5591 }
5592
5593 #[test]
5594 fn submit_rejects_malformed_statements() {
5595 let (store, _temp) = test_store();
5596
5597 let mut base = Statement::new();
5598 base.set_expiry(u64::MAX);
5599 base.set_plain_data(vec![1]);
5600
5601 let ed_kp = sp_core::ed25519::Pair::from_string("//Alice", None).unwrap();
5602 let sr_kp = sp_core::sr25519::Pair::from_string("//Alice", None).unwrap();
5603 let ecdsa_kp = sp_core::ecdsa::Pair::from_string("//Alice", None).unwrap();
5604
5605 assert_eq!(
5606 store.submit(base.clone(), StatementSource::Network),
5607 SubmitResult::Invalid(InvalidReason::NoProof)
5608 );
5609
5610 let bad_proofs = [
5611 Proof::Ed25519 { signature: [0xAB; 64], signer: ed_kp.public().0 },
5612 Proof::Sr25519 { signature: [0xCD; 64], signer: sr_kp.public().0 },
5613 Proof::Secp256k1Ecdsa { signature: [0xEF; 65], signer: ecdsa_kp.public().0 },
5614 ];
5615 for proof in bad_proofs {
5616 let mut s = base.clone();
5617 s.set_proof(proof);
5618 assert_eq!(
5619 store.submit(s, StatementSource::Network),
5620 SubmitResult::Invalid(InvalidReason::BadProof)
5621 );
5622 }
5623
5624 let mut wrong_signer = base.clone();
5625 wrong_signer.sign_ed25519_private(&ed_kp);
5626 let alice_sig = match wrong_signer.proof().unwrap() {
5627 Proof::Ed25519 { signature, .. } => *signature,
5628 _ => panic!("expected Ed25519 proof after sign_ed25519_private"),
5629 };
5630 let bob_kp = sp_core::ed25519::Pair::from_string("//Bob", None).unwrap();
5631 wrong_signer.set_proof(Proof::Ed25519 { signature: alice_sig, signer: bob_kp.public().0 });
5632 assert_eq!(
5633 store.submit(wrong_signer, StatementSource::Network),
5634 SubmitResult::Invalid(InvalidReason::BadProof)
5635 );
5636 }
5637
5638 #[test]
5639 fn channel_replacement_with_size_increase_evicts_others() {
5640 let (store, _temp) = test_store();
5641 let source = StatementSource::Network;
5642
5643 let s_ch = statement(3, 5, Some(1), 200);
5646 let s_low = statement(3, 2, None, 300);
5647 let s_mid = statement(3, 3, None, 300);
5648 let h_ch = s_ch.hash();
5649 let h_low = s_low.hash();
5650 let h_mid = s_mid.hash();
5651
5652 assert_eq!(store.submit(s_ch, source), SubmitResult::New);
5653 assert_eq!(store.submit(s_low, source), SubmitResult::New);
5654 assert_eq!(store.submit(s_mid, source), SubmitResult::New);
5655 assert_eq!(store.total_size(), 800);
5656
5657 let s_ch_big = statement(3, 10, Some(1), 600);
5660 let h_ch_big = s_ch_big.hash();
5661 assert_eq!(store.submit(s_ch_big, source), SubmitResult::New);
5662
5663 {
5664 assert_eq!(store.statement_count(), 2);
5665 assert!(!store.has_statement(&h_ch), "Old channel message replaced");
5666 assert!(!store.has_statement(&h_low), "Priority 2 evicted to fit size");
5667 assert!(store.has_statement(&h_mid), "Priority 3 should remain");
5668 assert!(store.has_statement(&h_ch_big), "New channel message added");
5669 assert_eq!(store.total_size(), 900); }
5671 }
5672
5673 #[test]
5674 fn subscription_reconnect_receives_current_state() {
5675 use crate::StatementStoreSubscriptionApi;
5676 use sp_statement_store::OptimizedTopicFilter;
5677
5678 let (store, _temp) = test_store();
5679 let source = StatementSource::Local;
5680
5681 for i in 0..3u8 {
5683 let res = store.submit(signed_statement(i), source);
5684 assert_eq!(res, SubmitResult::New);
5685 }
5686
5687 let (existing, sender, stream) =
5689 store.subscribe_statement(OptimizedTopicFilter::Any).unwrap();
5690 assert_eq!(existing.len(), 3, "First subscribe should return 3 existing statements");
5691
5692 drop(stream);
5694 drop(sender);
5695
5696 for i in 3..5u8 {
5698 assert_eq!(store.submit(signed_statement(i), source), SubmitResult::New);
5699 }
5700 let (existing, sender, stream) =
5701 store.subscribe_statement(OptimizedTopicFilter::Any).unwrap();
5702 assert_eq!(existing.len(), 5, "Re-subscribe should return all 5 current statements");
5703
5704 drop(stream);
5706 drop(sender);
5707 let hash_to_remove = signed_statement(0).hash();
5708 store.remove(&hash_to_remove).unwrap();
5709
5710 let (existing, _sender, _stream) =
5712 store.subscribe_statement(OptimizedTopicFilter::Any).unwrap();
5713 assert_eq!(existing.len(), 4, "Re-subscribe after removal should return 4 statements");
5714 }
5715
5716 #[test]
5717 fn subscription_reconnect_with_topic_filter() {
5718 use crate::StatementStoreSubscriptionApi;
5719 use sp_statement_store::OptimizedTopicFilter;
5720
5721 let (store, _temp) = test_store();
5722 let source = StatementSource::Local;
5723 let topic_a = topic(1);
5724 let topic_b = topic(2);
5725
5726 let s1 = signed_statement_with_topics(1, &[topic_a], None);
5728 let s2 = signed_statement_with_topics(2, &[topic_b], None);
5730 let s3 = signed_statement_with_topics(3, &[topic_a, topic_b], None);
5732
5733 assert_eq!(store.submit(s1, source), SubmitResult::New);
5734 assert_eq!(store.submit(s2, source), SubmitResult::New);
5735 assert_eq!(store.submit(s3, source), SubmitResult::New);
5736
5737 let filter_a = OptimizedTopicFilter::MatchAll(std::collections::HashSet::from([topic_a]));
5739 let (existing, sender, stream) = store.subscribe_statement(filter_a.clone()).unwrap();
5740 assert_eq!(existing.len(), 2, "MatchAll([A]) should match s1 and s3");
5741
5742 drop(sender);
5744 drop(stream);
5745 let s4 = signed_statement_with_topics(4, &[topic_a], None);
5746 assert_eq!(store.submit(s4, source), SubmitResult::New);
5747 let (existing, sender, stream) = store.subscribe_statement(filter_a).unwrap();
5749 assert_eq!(existing.len(), 3, "Re-subscribe MatchAll([A]) should return s1, s3, s4");
5750
5751 drop(sender);
5753 drop(stream);
5754 let filter_b = OptimizedTopicFilter::MatchAll(std::collections::HashSet::from([topic_b]));
5755 let (existing, _sender, _stream) = store.subscribe_statement(filter_b).unwrap();
5756 assert_eq!(existing.len(), 2, "Re-subscribe MatchAll([B]) should return s2 and s3");
5757 }
5758
5759 #[tokio::test]
5760 async fn subscription_delivers_each_statement_exactly_once_across_boundary() {
5761 use crate::StatementStoreSubscriptionApi;
5765 use futures::StreamExt;
5766 use sp_statement_store::{OptimizedTopicFilter, StatementEvent};
5767
5768 let (store, _temp) = test_store();
5769 let source = StatementSource::Local;
5770
5771 let a = signed_statement(0);
5773 let b = signed_statement(1);
5774 assert_eq!(store.submit(a.clone(), source), SubmitResult::New);
5775 assert_eq!(store.submit(b.clone(), source), SubmitResult::New);
5776
5777 let (existing, _sender, mut stream) =
5779 store.subscribe_statement(OptimizedTopicFilter::Any).unwrap();
5780 let mut snapshot: Vec<Statement> = existing
5781 .iter()
5782 .map(|bytes| Statement::decode(&mut &bytes[..]).unwrap())
5783 .collect();
5784 snapshot.sort_by_key(|s| s.hash());
5785 let mut expected_snapshot = vec![a.clone(), b.clone()];
5786 expected_snapshot.sort_by_key(|s| s.hash());
5787 assert_eq!(
5788 snapshot, expected_snapshot,
5789 "snapshot must contain exactly the pre-existing statements"
5790 );
5791
5792 let c = signed_statement(2);
5795 assert_eq!(store.submit(c.clone(), source), SubmitResult::New);
5796
5797 let event = tokio::time::timeout(std::time::Duration::from_secs(5), stream.next())
5798 .await
5799 .expect("live statement should arrive within the timeout")
5800 .expect("stream should yield an event");
5801 let StatementEvent::NewStatements { statements, .. } = event;
5802 let live: Vec<Statement> = statements
5803 .iter()
5804 .map(|bytes| Statement::decode(&mut &bytes.0[..]).unwrap())
5805 .collect();
5806 assert_eq!(
5807 live,
5808 vec![c.clone()],
5809 "live stream must deliver exactly the post-subscribe statement"
5810 );
5811
5812 assert!(
5814 tokio::time::timeout(std::time::Duration::from_millis(500), stream.next())
5815 .await
5816 .is_err(),
5817 "no further (duplicate) statements must be delivered"
5818 );
5819 }
5820
5821 #[test]
5822 fn subscription_snapshot_deduplicates_multi_topic_match_any() {
5823 use crate::StatementStoreSubscriptionApi;
5826 use sp_statement_store::OptimizedTopicFilter;
5827
5828 let (store, _temp) = test_store();
5829 let topic_a = topic(1);
5830 let topic_b = topic(2);
5831
5832 let s = signed_statement_with_topics(1, &[topic_a, topic_b], None);
5834 assert_eq!(store.submit(s, StatementSource::Local), SubmitResult::New);
5835
5836 let filter =
5837 OptimizedTopicFilter::MatchAny(std::collections::HashSet::from([topic_a, topic_b]));
5838 let (existing, _sender, _stream) = store.subscribe_statement(filter).unwrap();
5839 assert_eq!(
5840 existing.len(),
5841 1,
5842 "MatchAny snapshot must not duplicate a multi-topic statement"
5843 );
5844 }
5845
5846 #[tokio::test]
5847 async fn subscription_match_all_delivers_exactly_once_across_boundary() {
5848 use crate::StatementStoreSubscriptionApi;
5853 use futures::StreamExt;
5854 use sp_statement_store::{OptimizedTopicFilter, StatementEvent};
5855
5856 let (store, _temp) = test_store();
5857 let source = StatementSource::Local;
5858 let t = topic(7);
5859
5860 let a = signed_statement_with_topics(0, &[t], None);
5862 let b = signed_statement_with_topics(1, &[t], None);
5863 assert_eq!(store.submit(a, source), SubmitResult::New);
5864 assert_eq!(store.submit(b, source), SubmitResult::New);
5865
5866 let filter = OptimizedTopicFilter::MatchAll(std::collections::HashSet::from([t]));
5867 let (existing, _sender, mut stream) = store.subscribe_statement(filter).unwrap();
5868 assert_eq!(
5869 existing.len(),
5870 2,
5871 "MatchAll snapshot must contain both pre-existing statements"
5872 );
5873
5874 let c = signed_statement_with_topics(2, &[t], None);
5876 assert_eq!(store.submit(c.clone(), source), SubmitResult::New);
5877
5878 let event = tokio::time::timeout(std::time::Duration::from_secs(5), stream.next())
5879 .await
5880 .expect("live statement should arrive within the timeout")
5881 .expect("stream should yield an event");
5882 let StatementEvent::NewStatements { statements, .. } = event;
5883 let live: Vec<Statement> = statements
5884 .iter()
5885 .map(|bytes| Statement::decode(&mut &bytes.0[..]).unwrap())
5886 .collect();
5887 assert_eq!(
5888 live,
5889 vec![c],
5890 "MatchAll live stream must deliver exactly the post-subscribe statement"
5891 );
5892
5893 assert!(
5894 tokio::time::timeout(std::time::Duration::from_millis(500), stream.next())
5895 .await
5896 .is_err(),
5897 "no further (duplicate) statements must be delivered"
5898 );
5899 }
5900
5901 #[tokio::test]
5902 async fn subscription_no_loss_or_duplicate_under_concurrent_submits() {
5903 use crate::StatementStoreSubscriptionApi;
5910 use futures::StreamExt;
5911 use sp_statement_store::{OptimizedTopicFilter, StatementEvent};
5912 use std::collections::HashSet;
5913
5914 let (store, _temp) = test_store();
5915 let store = std::sync::Arc::new(store);
5916 let t = topic(9);
5917 const N: u8 = 60;
5920
5921 let all: Vec<Statement> =
5922 (0..N).map(|i| signed_statement_with_topics(i, &[t], None)).collect();
5923 let all_hashes: HashSet<_> = all.iter().map(|s| s.hash()).collect();
5924
5925 let split = 10usize;
5928 for s in &all[..split] {
5929 assert_eq!(store.submit(s.clone(), StatementSource::Local), SubmitResult::New);
5930 }
5931 let store2 = store.clone();
5932 let rest: Vec<Statement> = all[split..].to_vec();
5933 let submitter = std::thread::spawn(move || {
5934 for s in rest {
5935 let _ = store2.submit(s, StatementSource::Local);
5936 }
5937 });
5938
5939 let filter = OptimizedTopicFilter::MatchAll(HashSet::from([t]));
5940 let (existing, _sender, mut stream) = store.subscribe_statement(filter).unwrap();
5941
5942 submitter.join().unwrap();
5943
5944 let mut seen = existing
5946 .iter()
5947 .map(|b| Statement::decode(&mut &b[..]).unwrap().hash())
5948 .collect::<Vec<_>>();
5949 while seen.len() < N as usize {
5951 match tokio::time::timeout(std::time::Duration::from_secs(5), stream.next()).await {
5952 Ok(Some(StatementEvent::NewStatements { statements, .. })) => {
5953 for b in statements {
5954 seen.push(Statement::decode(&mut &b.0[..]).unwrap().hash());
5955 }
5956 },
5957 _ => break,
5958 }
5959 }
5960
5961 let seen_set: HashSet<_> = seen.iter().copied().collect();
5962 assert_eq!(
5963 seen.len(),
5964 N as usize,
5965 "each statement must be delivered exactly once (got {} deliveries for {} statements — loss or duplicate)",
5966 seen.len(),
5967 N
5968 );
5969 assert_eq!(seen_set, all_hashes, "delivered set must equal submitted set");
5970 }
5971
5972 mod multi_filter {
5975 use super::*;
5976 use crate::{
5977 MultiFilterEventStream, MultiFilterSubscriptionApi, MultiFilterSubscriptionEvent,
5978 };
5979 use futures::StreamExt;
5980 use sp_statement_store::OptimizedTopicFilter;
5981 use std::{collections::HashSet, sync::Arc, time::Duration};
5982
5983 fn arc_test_store() -> (Arc<Store>, tempfile::TempDir) {
5984 let (store, dir) = test_store();
5985 (Arc::new(store), dir)
5986 }
5987
5988 async fn drain_all(
5989 stream: &mut MultiFilterEventStream,
5990 idle: Duration,
5991 ) -> Vec<MultiFilterSubscriptionEvent> {
5992 let mut events = Vec::new();
5993 while let Ok(Some(event)) = tokio::time::timeout(idle, stream.next()).await {
5994 events.push(event);
5995 }
5996 events
5997 }
5998
5999 #[test]
6000 fn replay_watermark_excludes_later_admissions() {
6001 let (store, _dir) = test_store();
6002 let before = signed_statement(1);
6003 let after = signed_statement(2);
6004 assert_eq!(store.submit(before.clone(), StatementSource::Local), SubmitResult::New);
6005
6006 let mut watermark = None;
6007 assert!(store
6008 .register_replay(&mut |captured| {
6009 watermark = Some(captured);
6010 true
6011 })
6012 .unwrap()
6013 .is_some());
6014 assert_eq!(store.submit(after.clone(), StatementSource::Local), SubmitResult::New);
6015
6016 let replay =
6017 store.replay_batch(&OptimizedTopicFilter::Any, 0, watermark.unwrap()).unwrap();
6018 let hashes: HashSet<_> = replay
6019 .statements
6020 .iter()
6021 .map(|encoded| Statement::decode(&mut encoded.as_slice()).unwrap().hash())
6022 .collect();
6023 assert!(replay.done);
6024 assert!(hashes.contains(&before.hash()));
6025 assert!(!hashes.contains(&after.hash()));
6026 }
6027
6028 #[tokio::test]
6029 async fn add_filter_replays_snapshot_and_delivers_later_submissions_live() {
6030 let (store, _dir) = arc_test_store();
6031 let (handle, mut stream) = store.create_subscription();
6032
6033 const NUM_STATEMENTS: u8 = 50;
6034 const NUM_PRE_FILTER: u8 = 20;
6035
6036 for i in 0..NUM_PRE_FILTER {
6038 let stmt = signed_statement(i);
6039 assert_eq!(store.submit(stmt, StatementSource::Local), SubmitResult::New);
6040 }
6041
6042 let filter_id = handle.add_filter(OptimizedTopicFilter::Any).unwrap();
6043
6044 for i in NUM_PRE_FILTER..NUM_STATEMENTS {
6047 let stmt = signed_statement(i);
6048 assert_eq!(store.submit(stmt, StatementSource::Local), SubmitResult::New);
6049 }
6050
6051 let mut snapshot_hashes: HashSet<[u8; 32]> = HashSet::new();
6052 let mut live_with_filter: HashSet<[u8; 32]> = HashSet::new();
6053 for event in drain_all(&mut stream, Duration::from_millis(300)).await {
6054 match event {
6055 MultiFilterSubscriptionEvent::ReplayStatements { statements, .. } => {
6056 snapshot_hashes.extend(
6057 statements
6058 .iter()
6059 .map(|bytes| Statement::decode(&mut &bytes[..]).unwrap().hash()),
6060 );
6061 },
6062 MultiFilterSubscriptionEvent::NewStatement(event)
6063 if event.matched_filter_ids.contains(&filter_id) =>
6064 {
6065 live_with_filter.insert(event.hash);
6066 },
6067 _ => {},
6068 }
6069 }
6070
6071 let expected_replayed: HashSet<[u8; 32]> =
6072 (0..NUM_PRE_FILTER).map(|i| signed_statement(i).hash()).collect();
6073 let expected_live: HashSet<[u8; 32]> =
6074 (NUM_PRE_FILTER..NUM_STATEMENTS).map(|i| signed_statement(i).hash()).collect();
6075
6076 assert_eq!(snapshot_hashes, expected_replayed);
6077 assert_eq!(live_with_filter, expected_live);
6078 }
6079 }
6080}