poi_rs/cache/
in_memory.rs1use std::collections::BTreeMap;
5use std::sync::Arc;
6
7use iota_types::committee::Committee;
8#[cfg(test)]
9use iota_types::committee::EpochId;
10use tokio::sync::RwLock;
11
12use super::{CommitteeCache, CommitteeCacheError, CommitteeCacheKey};
13
14#[derive(Clone, Debug, Default)]
16pub struct MemoryCommitteeCache {
17 committees: Arc<RwLock<BTreeMap<CommitteeCacheKey, Committee>>>,
18}
19
20impl MemoryCommitteeCache {
21 pub fn new() -> Self {
23 Self::default()
24 }
25
26 pub async fn len(&self) -> usize {
28 self.committees.read().await.len()
29 }
30
31 pub async fn is_empty(&self) -> bool {
33 self.committees.read().await.is_empty()
34 }
35}
36
37#[async_trait::async_trait]
38impl CommitteeCache for MemoryCommitteeCache {
39 async fn committee(&self, key: CommitteeCacheKey) -> Result<Option<Committee>, CommitteeCacheError> {
40 Ok(self.committees.read().await.get(&key).cloned())
41 }
42
43 async fn store(&self, key: CommitteeCacheKey, committee: &Committee) -> Result<(), CommitteeCacheError> {
44 let epoch = committee.epoch;
45 if key.epoch() != epoch {
46 return Err(CommitteeCacheError::Conflict { epoch });
47 }
48
49 let mut committees = self.committees.write().await;
50
51 if committees.get(&key).is_some_and(|cached| cached != committee) {
52 return Err(CommitteeCacheError::Conflict { epoch });
53 }
54
55 committees.entry(key).or_insert_with(|| committee.clone());
56
57 Ok(())
58 }
59}
60
61#[cfg(test)]
62mod tests {
63 use iota_types::digests::ChainIdentifier;
64
65 use super::*;
66
67 fn committee_at(epoch: EpochId) -> Committee {
68 let (committee, _) = Committee::new_simple_test_committee();
69
70 Committee::new(epoch, committee.voting_rights.iter().cloned().collect())
71 }
72
73 fn chain_identifier(byte: u8) -> ChainIdentifier {
74 ChainIdentifier::from(iota_sdk_types::CheckpointDigest::new([byte; 32]))
75 }
76
77 fn key(chain_identifier: ChainIdentifier, epoch: EpochId) -> CommitteeCacheKey {
78 CommitteeCacheKey::new(chain_identifier, epoch)
79 }
80
81 #[tokio::test]
82 async fn new_cache_is_empty() {
83 let cache = MemoryCommitteeCache::new();
84 let chain_identifier = chain_identifier(1);
85
86 assert!(cache.is_empty().await);
87 assert_eq!(cache.len().await, 0);
88 assert!(cache.committee(key(chain_identifier, 7)).await.unwrap().is_none());
89 }
90
91 #[tokio::test]
92 async fn store_makes_a_committee_available_by_epoch() {
93 let cache = MemoryCommitteeCache::new();
94 let chain_identifier = chain_identifier(1);
95 let committee = committee_at(7);
96
97 cache.store(key(chain_identifier, 7), &committee).await.unwrap();
98
99 assert_eq!(
100 cache.committee(key(chain_identifier, 7)).await.unwrap(),
101 Some(committee)
102 );
103 assert_eq!(cache.len().await, 1);
104 assert!(!cache.is_empty().await);
105 }
106
107 #[tokio::test]
108 async fn storing_the_same_committee_is_idempotent() {
109 let cache = MemoryCommitteeCache::new();
110 let chain_identifier = chain_identifier(1);
111 let committee = committee_at(7);
112
113 cache.store(key(chain_identifier, 7), &committee).await.unwrap();
114 cache.store(key(chain_identifier, 7), &committee).await.unwrap();
115
116 assert_eq!(
117 cache.committee(key(chain_identifier, 7)).await.unwrap(),
118 Some(committee)
119 );
120 assert_eq!(cache.len().await, 1);
121 }
122
123 #[tokio::test]
124 async fn conflicting_committee_is_rejected_without_replacing_the_original() {
125 let cache = MemoryCommitteeCache::new();
126 let chain_identifier = chain_identifier(1);
127 let original = committee_at(7);
128 let (conflicting, _) = Committee::new_simple_test_committee_of_size(5);
129 let conflicting = Committee::new(7, conflicting.voting_rights.iter().cloned().collect());
130 cache.store(key(chain_identifier, 7), &original).await.unwrap();
131
132 let error = cache.store(key(chain_identifier, 7), &conflicting).await.unwrap_err();
133
134 assert!(matches!(error, CommitteeCacheError::Conflict { epoch: 7 }));
135 assert_eq!(cache.committee(key(chain_identifier, 7)).await.unwrap(), Some(original));
136 assert_eq!(cache.len().await, 1);
137 }
138
139 #[tokio::test]
140 async fn clones_share_cached_committees() {
141 let cache = MemoryCommitteeCache::new();
142 let clone = cache.clone();
143 let chain_identifier = chain_identifier(1);
144 let committee = committee_at(7);
145
146 cache.store(key(chain_identifier, 7), &committee).await.unwrap();
147
148 assert_eq!(
149 clone.committee(key(chain_identifier, 7)).await.unwrap(),
150 Some(committee)
151 );
152 }
153
154 #[tokio::test]
155 async fn the_same_epoch_is_isolated_between_networks() {
156 let cache = MemoryCommitteeCache::new();
157 let first_chain = chain_identifier(1);
158 let second_chain = chain_identifier(2);
159 let committee = committee_at(7);
160
161 cache
162 .store(key(first_chain, committee.epoch), &committee)
163 .await
164 .unwrap();
165
166 assert_eq!(
167 cache.committee(key(first_chain, committee.epoch)).await.unwrap(),
168 Some(committee.clone())
169 );
170 assert!(
171 cache
172 .committee(key(second_chain, committee.epoch))
173 .await
174 .unwrap()
175 .is_none()
176 );
177 }
178}