1use std::collections::{BTreeMap, HashMap};
2use std::sync::Arc;
3
4use parking_lot::RwLock;
5
6use hotmint_types::{Block, BlockHash, Height, QuorumCertificate};
7
8pub trait BlockStore: Send + Sync {
9 fn put_block(&mut self, block: Block);
10 fn get_block(&self, hash: &BlockHash) -> Option<Block>;
11 fn get_block_by_height(&self, h: Height) -> Option<Block>;
12
13 fn put_commit_qc(&mut self, _height: Height, _qc: QuorumCertificate) {}
15 fn get_commit_qc(&self, _height: Height) -> Option<QuorumCertificate> {
17 None
18 }
19
20 fn flush(&self) {}
22
23 fn get_blocks_in_range(&self, from: Height, to: Height) -> Vec<Block> {
25 let mut blocks = Vec::new();
26 let mut h = from.as_u64();
27 while h <= to.as_u64() {
28 if let Some(block) = self.get_block_by_height(Height(h)) {
29 blocks.push(block);
30 }
31 h += 1;
32 }
33 blocks
34 }
35
36 fn tip_height(&self) -> Height {
38 Height::GENESIS
39 }
40
41 fn put_tx_index(&mut self, _tx_hash: [u8; 32], _height: Height, _index: u32) {}
43
44 fn get_tx_location(&self, _tx_hash: &[u8; 32]) -> Option<(Height, u32)> {
46 None
47 }
48
49 fn put_block_results(&mut self, _height: Height, _results: hotmint_types::EndBlockResponse) {}
51
52 fn get_block_results(&self, _height: Height) -> Option<hotmint_types::EndBlockResponse> {
54 None
55 }
56}
57
58pub struct SharedStoreAdapter(pub Arc<RwLock<Box<dyn BlockStore>>>);
64
65impl BlockStore for SharedStoreAdapter {
66 fn put_block(&mut self, block: Block) {
67 self.0.write().put_block(block);
68 }
69 fn get_block(&self, hash: &BlockHash) -> Option<Block> {
70 self.0.read().get_block(hash)
71 }
72 fn get_block_by_height(&self, h: Height) -> Option<Block> {
73 self.0.read().get_block_by_height(h)
74 }
75 fn get_blocks_in_range(&self, from: Height, to: Height) -> Vec<Block> {
76 self.0.read().get_blocks_in_range(from, to)
77 }
78 fn tip_height(&self) -> Height {
79 self.0.read().tip_height()
80 }
81 fn put_commit_qc(&mut self, height: Height, qc: QuorumCertificate) {
82 self.0.write().put_commit_qc(height, qc);
83 }
84 fn get_commit_qc(&self, height: Height) -> Option<QuorumCertificate> {
85 self.0.read().get_commit_qc(height)
86 }
87 fn flush(&self) {
88 self.0.read().flush();
89 }
90 fn put_tx_index(&mut self, tx_hash: [u8; 32], height: Height, index: u32) {
91 self.0.write().put_tx_index(tx_hash, height, index);
92 }
93 fn get_tx_location(&self, tx_hash: &[u8; 32]) -> Option<(Height, u32)> {
94 self.0.read().get_tx_location(tx_hash)
95 }
96 fn put_block_results(&mut self, height: Height, results: hotmint_types::EndBlockResponse) {
97 self.0.write().put_block_results(height, results);
98 }
99 fn get_block_results(&self, height: Height) -> Option<hotmint_types::EndBlockResponse> {
100 self.0.read().get_block_results(height)
101 }
102}
103
104pub struct MemoryBlockStore {
106 by_hash: HashMap<BlockHash, Block>,
107 by_height: BTreeMap<u64, BlockHash>,
108 commit_qcs: HashMap<u64, QuorumCertificate>,
109}
110
111impl Default for MemoryBlockStore {
112 fn default() -> Self {
113 Self::new()
114 }
115}
116
117impl MemoryBlockStore {
118 pub fn new() -> Self {
119 let mut store = Self {
120 by_hash: HashMap::new(),
121 by_height: BTreeMap::new(),
122 commit_qcs: HashMap::new(),
123 };
124 let genesis = Block::genesis();
125 store.put_block(genesis);
126 store
127 }
128
129 pub fn new_shared() -> crate::engine::SharedBlockStore {
132 Arc::new(RwLock::new(Box::new(Self::new())))
133 }
134}
135
136impl BlockStore for MemoryBlockStore {
137 fn put_block(&mut self, block: Block) {
138 let hash = block.hash;
139 self.by_height.insert(block.height.as_u64(), hash);
140 self.by_hash.insert(hash, block);
141 }
142
143 fn get_block(&self, hash: &BlockHash) -> Option<Block> {
144 self.by_hash.get(hash).cloned()
145 }
146
147 fn get_block_by_height(&self, h: Height) -> Option<Block> {
148 self.by_height
149 .get(&h.as_u64())
150 .and_then(|hash| self.by_hash.get(hash))
151 .cloned()
152 }
153
154 fn get_blocks_in_range(&self, from: Height, to: Height) -> Vec<Block> {
155 self.by_height
156 .range(from.as_u64()..=to.as_u64())
157 .filter_map(|(_, hash)| self.by_hash.get(hash).cloned())
158 .collect()
159 }
160
161 fn tip_height(&self) -> Height {
162 self.by_height
163 .keys()
164 .next_back()
165 .map(|h| Height(*h))
166 .unwrap_or(Height::GENESIS)
167 }
168
169 fn put_commit_qc(&mut self, height: Height, qc: QuorumCertificate) {
170 self.commit_qcs.insert(height.as_u64(), qc);
171 }
172
173 fn get_commit_qc(&self, height: Height) -> Option<QuorumCertificate> {
174 self.commit_qcs.get(&height.as_u64()).cloned()
175 }
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181 use hotmint_types::{ValidatorId, ViewNumber};
182
183 fn make_block(height: u64, parent: BlockHash) -> Block {
184 let hash = BlockHash([height as u8; 32]);
185 Block {
186 height: Height(height),
187 parent_hash: parent,
188 view: ViewNumber(height),
189 proposer: ValidatorId(0),
190 timestamp: 0,
191 payload: vec![],
192 app_hash: BlockHash::GENESIS,
193 evidence: Vec::new(),
194 hash,
195 }
196 }
197
198 #[test]
199 fn test_genesis_present() {
200 let store = MemoryBlockStore::new();
201 let genesis = store.get_block(&BlockHash::GENESIS);
202 assert!(genesis.is_some());
203 assert_eq!(genesis.unwrap().height, Height::GENESIS);
204 }
205
206 #[test]
207 fn test_put_and_get_by_hash() {
208 let mut store = MemoryBlockStore::new();
209 let block = make_block(1, BlockHash::GENESIS);
210 let hash = block.hash;
211 store.put_block(block);
212 let retrieved = store.get_block(&hash).unwrap();
213 assert_eq!(retrieved.height, Height(1));
214 }
215
216 #[test]
217 fn test_get_by_height() {
218 let mut store = MemoryBlockStore::new();
219 let b1 = make_block(1, BlockHash::GENESIS);
220 let b2 = make_block(2, b1.hash);
221 store.put_block(b1);
222 store.put_block(b2);
223
224 assert!(store.get_block_by_height(Height(1)).is_some());
225 assert!(store.get_block_by_height(Height(2)).is_some());
226 assert!(store.get_block_by_height(Height(99)).is_none());
227 }
228
229 #[test]
230 fn test_get_nonexistent() {
231 let store = MemoryBlockStore::new();
232 assert!(store.get_block(&BlockHash([99u8; 32])).is_none());
233 assert!(store.get_block_by_height(Height(999)).is_none());
234 }
235
236 #[test]
237 fn test_get_blocks_in_range() {
238 let mut store = MemoryBlockStore::new();
239 let b1 = make_block(1, BlockHash::GENESIS);
240 let b2 = make_block(2, b1.hash);
241 let b3 = make_block(3, b2.hash);
242 store.put_block(b1);
243 store.put_block(b2);
244 store.put_block(b3);
245
246 let blocks = store.get_blocks_in_range(Height(1), Height(3));
247 assert_eq!(blocks.len(), 3);
248 assert_eq!(blocks[0].height, Height(1));
249 assert_eq!(blocks[2].height, Height(3));
250
251 let blocks = store.get_blocks_in_range(Height(2), Height(3));
253 assert_eq!(blocks.len(), 2);
254
255 let blocks = store.get_blocks_in_range(Height(10), Height(20));
257 assert!(blocks.is_empty());
258 }
259
260 #[test]
261 fn test_tip_height() {
262 let store = MemoryBlockStore::new();
263 assert_eq!(store.tip_height(), Height::GENESIS);
264
265 let mut store = MemoryBlockStore::new();
266 let b1 = make_block(1, BlockHash::GENESIS);
267 let b2 = make_block(2, b1.hash);
268 store.put_block(b1);
269 store.put_block(b2);
270 assert_eq!(store.tip_height(), Height(2));
271 }
272
273 #[test]
274 fn test_overwrite_same_height() {
275 let mut store = MemoryBlockStore::new();
276 let b1 = make_block(1, BlockHash::GENESIS);
277 store.put_block(b1);
278 let mut b2 = make_block(1, BlockHash::GENESIS);
280 b2.hash = BlockHash([42u8; 32]);
281 b2.payload = vec![1, 2, 3];
282 store.put_block(b2);
283 let retrieved = store.get_block_by_height(Height(1)).unwrap();
285 assert_eq!(retrieved.hash, BlockHash([42u8; 32]));
286 }
287}