hotmint_consensus/
store.rs1use std::collections::{BTreeMap, HashMap};
2
3use hotmint_types::{Block, BlockHash, Height, QuorumCertificate};
4
5pub trait BlockStore: Send + Sync {
6 fn put_block(&mut self, block: Block);
7 fn get_block(&self, hash: &BlockHash) -> Option<Block>;
8 fn get_block_by_height(&self, h: Height) -> Option<Block>;
9
10 fn put_commit_qc(&mut self, _height: Height, _qc: QuorumCertificate) {}
12 fn get_commit_qc(&self, _height: Height) -> Option<QuorumCertificate> {
14 None
15 }
16
17 fn flush(&self) {}
19
20 fn get_blocks_in_range(&self, from: Height, to: Height) -> Vec<Block> {
22 let mut blocks = Vec::new();
23 let mut h = from.as_u64();
24 while h <= to.as_u64() {
25 if let Some(block) = self.get_block_by_height(Height(h)) {
26 blocks.push(block);
27 }
28 h += 1;
29 }
30 blocks
31 }
32
33 fn tip_height(&self) -> Height {
35 Height::GENESIS
36 }
37}
38
39pub struct MemoryBlockStore {
41 by_hash: HashMap<BlockHash, Block>,
42 by_height: BTreeMap<u64, BlockHash>,
43 commit_qcs: HashMap<u64, QuorumCertificate>,
44}
45
46impl Default for MemoryBlockStore {
47 fn default() -> Self {
48 Self::new()
49 }
50}
51
52impl MemoryBlockStore {
53 pub fn new() -> Self {
54 let mut store = Self {
55 by_hash: HashMap::new(),
56 by_height: BTreeMap::new(),
57 commit_qcs: HashMap::new(),
58 };
59 let genesis = Block::genesis();
60 store.put_block(genesis);
61 store
62 }
63}
64
65impl BlockStore for MemoryBlockStore {
66 fn put_block(&mut self, block: Block) {
67 let hash = block.hash;
68 self.by_height.insert(block.height.as_u64(), hash);
69 self.by_hash.insert(hash, block);
70 }
71
72 fn get_block(&self, hash: &BlockHash) -> Option<Block> {
73 self.by_hash.get(hash).cloned()
74 }
75
76 fn get_block_by_height(&self, h: Height) -> Option<Block> {
77 self.by_height
78 .get(&h.as_u64())
79 .and_then(|hash| self.by_hash.get(hash))
80 .cloned()
81 }
82
83 fn get_blocks_in_range(&self, from: Height, to: Height) -> Vec<Block> {
84 self.by_height
85 .range(from.as_u64()..=to.as_u64())
86 .filter_map(|(_, hash)| self.by_hash.get(hash).cloned())
87 .collect()
88 }
89
90 fn tip_height(&self) -> Height {
91 self.by_height
92 .keys()
93 .next_back()
94 .map(|h| Height(*h))
95 .unwrap_or(Height::GENESIS)
96 }
97
98 fn put_commit_qc(&mut self, height: Height, qc: QuorumCertificate) {
99 self.commit_qcs.insert(height.as_u64(), qc);
100 }
101
102 fn get_commit_qc(&self, height: Height) -> Option<QuorumCertificate> {
103 self.commit_qcs.get(&height.as_u64()).cloned()
104 }
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110 use hotmint_types::{ValidatorId, ViewNumber};
111
112 fn make_block(height: u64, parent: BlockHash) -> Block {
113 let hash = BlockHash([height as u8; 32]);
114 Block {
115 height: Height(height),
116 parent_hash: parent,
117 view: ViewNumber(height),
118 proposer: ValidatorId(0),
119 payload: vec![],
120 hash,
121 }
122 }
123
124 #[test]
125 fn test_genesis_present() {
126 let store = MemoryBlockStore::new();
127 let genesis = store.get_block(&BlockHash::GENESIS);
128 assert!(genesis.is_some());
129 assert_eq!(genesis.unwrap().height, Height::GENESIS);
130 }
131
132 #[test]
133 fn test_put_and_get_by_hash() {
134 let mut store = MemoryBlockStore::new();
135 let block = make_block(1, BlockHash::GENESIS);
136 let hash = block.hash;
137 store.put_block(block);
138 let retrieved = store.get_block(&hash).unwrap();
139 assert_eq!(retrieved.height, Height(1));
140 }
141
142 #[test]
143 fn test_get_by_height() {
144 let mut store = MemoryBlockStore::new();
145 let b1 = make_block(1, BlockHash::GENESIS);
146 let b2 = make_block(2, b1.hash);
147 store.put_block(b1);
148 store.put_block(b2);
149
150 assert!(store.get_block_by_height(Height(1)).is_some());
151 assert!(store.get_block_by_height(Height(2)).is_some());
152 assert!(store.get_block_by_height(Height(99)).is_none());
153 }
154
155 #[test]
156 fn test_get_nonexistent() {
157 let store = MemoryBlockStore::new();
158 assert!(store.get_block(&BlockHash([99u8; 32])).is_none());
159 assert!(store.get_block_by_height(Height(999)).is_none());
160 }
161
162 #[test]
163 fn test_get_blocks_in_range() {
164 let mut store = MemoryBlockStore::new();
165 let b1 = make_block(1, BlockHash::GENESIS);
166 let b2 = make_block(2, b1.hash);
167 let b3 = make_block(3, b2.hash);
168 store.put_block(b1);
169 store.put_block(b2);
170 store.put_block(b3);
171
172 let blocks = store.get_blocks_in_range(Height(1), Height(3));
173 assert_eq!(blocks.len(), 3);
174 assert_eq!(blocks[0].height, Height(1));
175 assert_eq!(blocks[2].height, Height(3));
176
177 let blocks = store.get_blocks_in_range(Height(2), Height(3));
179 assert_eq!(blocks.len(), 2);
180
181 let blocks = store.get_blocks_in_range(Height(10), Height(20));
183 assert!(blocks.is_empty());
184 }
185
186 #[test]
187 fn test_tip_height() {
188 let store = MemoryBlockStore::new();
189 assert_eq!(store.tip_height(), Height::GENESIS);
190
191 let mut store = MemoryBlockStore::new();
192 let b1 = make_block(1, BlockHash::GENESIS);
193 let b2 = make_block(2, b1.hash);
194 store.put_block(b1);
195 store.put_block(b2);
196 assert_eq!(store.tip_height(), Height(2));
197 }
198
199 #[test]
200 fn test_overwrite_same_height() {
201 let mut store = MemoryBlockStore::new();
202 let b1 = make_block(1, BlockHash::GENESIS);
203 store.put_block(b1);
204 let mut b2 = make_block(1, BlockHash::GENESIS);
206 b2.hash = BlockHash([42u8; 32]);
207 b2.payload = vec![1, 2, 3];
208 store.put_block(b2);
209 let retrieved = store.get_block_by_height(Height(1)).unwrap();
211 assert_eq!(retrieved.hash, BlockHash([42u8; 32]));
212 }
213}