iota_core/epoch/
committee_store.rs1use std::{collections::HashMap, path::PathBuf, sync::Arc};
6
7use iota_types::{
8 committee::{Committee, EpochId},
9 error::{IotaError, IotaResult},
10};
11use parking_lot::RwLock;
12use typed_store::{
13 DBMapUtils, Map,
14 rocks::{DBMap, DBOptions, MetricConf, default_db_options},
15 rocksdb::Options,
16};
17
18pub struct CommitteeStore {
19 tables: CommitteeStoreTables,
20 cache: RwLock<HashMap<EpochId, Arc<Committee>>>,
21}
22
23#[derive(DBMapUtils)]
24pub struct CommitteeStoreTables {
25 #[default_options_override_fn = "committee_table_default_config"]
27 committee_map: DBMap<EpochId, Committee>,
28}
29
30fn committee_table_default_config() -> DBOptions {
32 default_db_options().optimize_for_point_lookup(64)
33}
34
35impl CommitteeStore {
36 fn open_tables(path: PathBuf, db_options: Option<Options>) -> Self {
39 let tables = CommitteeStoreTables::open_tables_read_write(
40 path,
41 MetricConf::new("committee"),
42 db_options,
43 None,
44 );
45 Self {
46 tables,
47 cache: RwLock::new(HashMap::new()),
48 }
49 }
50
51 pub fn new(path: PathBuf, genesis_committee: &Committee, db_options: Option<Options>) -> Self {
52 let store = Self::open_tables(path, db_options);
53 if store
54 .database_is_empty()
55 .expect("CommitteeStore initialization failed")
56 {
57 store
58 .init_genesis_committee(genesis_committee.clone())
59 .expect("Init genesis committee data must not fail");
60 }
61 store
62 }
63
64 pub fn new_for_testing(genesis_committee: &Committee) -> Self {
65 let path = iota_common::tempdir().keep();
66 Self::new(path, genesis_committee, None)
67 }
68
69 pub fn open(path: PathBuf, db_options: Option<Options>) -> IotaResult<Self> {
74 let store = Self::open_tables(path, db_options);
75 if store.database_is_empty()? {
76 return Err(IotaError::Storage(
77 "committee store has no genesis committee".to_string(),
78 ));
79 }
80 Ok(store)
81 }
82
83 pub fn init_genesis_committee(&self, genesis_committee: Committee) -> IotaResult {
84 assert_eq!(genesis_committee.epoch, 0);
85 self.tables.committee_map.insert(&0, &genesis_committee)?;
86 self.cache.write().insert(0, Arc::new(genesis_committee));
87 Ok(())
88 }
89
90 pub fn insert_new_committee(&self, new_committee: &Committee) -> IotaResult {
91 if let Some(old_committee) = self.get_committee(&new_committee.epoch)? {
92 assert_eq!(&*old_committee, new_committee);
95 } else {
96 self.tables
97 .committee_map
98 .insert(&new_committee.epoch, new_committee)?;
99 self.cache
100 .write()
101 .insert(new_committee.epoch, Arc::new(new_committee.clone()));
102 }
103 Ok(())
104 }
105
106 pub fn get_committee(&self, epoch_id: &EpochId) -> IotaResult<Option<Arc<Committee>>> {
107 if let Some(committee) = self.cache.read().get(epoch_id) {
108 return Ok(Some(committee.clone()));
109 }
110 let committee = self.tables.committee_map.get(epoch_id)?;
111 let committee = committee.map(Arc::new);
112 if let Some(committee) = committee.as_ref() {
113 self.cache.write().insert(*epoch_id, committee.clone());
114 }
115 Ok(committee)
116 }
117
118 pub fn get_latest_committee(&self) -> IotaResult<Committee> {
120 Ok(self
121 .tables
122 .committee_map
123 .safe_range_iter_reversed(..)
124 .next()
125 .transpose()?
126 .unwrap()
129 .1)
130 }
131 pub fn get_or_latest_committee(&self, epoch: Option<EpochId>) -> IotaResult<Committee> {
135 Ok(match epoch {
136 Some(epoch) => self
137 .get_committee(&epoch)?
138 .ok_or(IotaError::MissingCommitteeAtEpoch(epoch))
139 .map(|c| Committee::clone(&*c))?,
140 None => self.get_latest_committee()?,
141 })
142 }
143
144 fn database_is_empty(&self) -> IotaResult<bool> {
145 Ok(self
146 .tables
147 .committee_map
148 .safe_iter()
149 .next()
150 .transpose()?
151 .is_none())
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use iota_types::committee::Committee;
158
159 use super::*;
160
161 #[tokio::test]
162 async fn open_reads_existing_genesis_and_rejects_empty() {
163 let dir = iota_common::tempdir();
164 let path = dir.path().to_path_buf();
165 let (genesis_committee, _) = Committee::new_simple_test_committee();
166
167 assert!(CommitteeStore::open(path.clone(), None).is_err());
169
170 {
173 let _store = CommitteeStore::new(path.clone(), &genesis_committee, None);
174 }
175 let opened = CommitteeStore::open(path, None).expect("store has a genesis committee");
176 assert_eq!(
177 *opened.get_committee(&0).unwrap().unwrap(),
178 genesis_committee,
179 );
180 }
181}