hotmint_storage/
block_store.rs1use hotmint_consensus::store::BlockStore;
2use hotmint_types::{Block, BlockHash, EndBlockResponse, Height, QuorumCertificate};
3use ruc::*;
4use std::path::Path;
5use tracing::debug;
6use vsdb::MapxOrd;
7
8const META_FILE: &str = "block_store.meta";
10
11pub struct VsdbBlockStore {
13 by_hash: MapxOrd<[u8; 32], Block>,
14 by_height: MapxOrd<u64, [u8; 32]>,
15 commit_qcs: MapxOrd<u64, QuorumCertificate>,
16 tx_index: MapxOrd<[u8; 32], (u64, u32)>,
18 block_results: MapxOrd<u64, EndBlockResponse>,
20}
21
22impl VsdbBlockStore {
23 pub fn open(data_dir: &Path) -> Result<Self> {
33 let meta_path = data_dir.join(META_FILE);
34 if meta_path.exists() {
35 let bytes = std::fs::read(&meta_path).c(d!("read block_store.meta"))?;
36 if bytes.len() == 24 {
37 let by_hash_id = u64::from_le_bytes(bytes[0..8].try_into().unwrap());
39 let by_height_id = u64::from_le_bytes(bytes[8..16].try_into().unwrap());
40 let commit_qcs_id = u64::from_le_bytes(bytes[16..24].try_into().unwrap());
41 let tx_index: MapxOrd<[u8; 32], (u64, u32)> = MapxOrd::new();
42 let block_results: MapxOrd<u64, EndBlockResponse> = MapxOrd::new();
43 let tx_index_id = tx_index.save_meta().c(d!())?;
44 let block_results_id = block_results.save_meta().c(d!())?;
45 let mut meta = [0u8; 40];
46 meta[0..8].copy_from_slice(&by_hash_id.to_le_bytes());
47 meta[8..16].copy_from_slice(&by_height_id.to_le_bytes());
48 meta[16..24].copy_from_slice(&commit_qcs_id.to_le_bytes());
49 meta[24..32].copy_from_slice(&tx_index_id.to_le_bytes());
50 meta[32..40].copy_from_slice(&block_results_id.to_le_bytes());
51 std::fs::write(&meta_path, meta).c(d!("write block_store.meta v2"))?;
52 Ok(Self {
53 by_hash: MapxOrd::from_meta(by_hash_id).c(d!("restore by_hash"))?,
54 by_height: MapxOrd::from_meta(by_height_id).c(d!("restore by_height"))?,
55 commit_qcs: MapxOrd::from_meta(commit_qcs_id).c(d!("restore commit_qcs"))?,
56 tx_index,
57 block_results,
58 })
59 } else if bytes.len() == 40 {
60 let by_hash_id = u64::from_le_bytes(bytes[0..8].try_into().unwrap());
61 let by_height_id = u64::from_le_bytes(bytes[8..16].try_into().unwrap());
62 let commit_qcs_id = u64::from_le_bytes(bytes[16..24].try_into().unwrap());
63 let tx_index_id = u64::from_le_bytes(bytes[24..32].try_into().unwrap());
64 let block_results_id = u64::from_le_bytes(bytes[32..40].try_into().unwrap());
65 Ok(Self {
66 by_hash: MapxOrd::from_meta(by_hash_id).c(d!("restore by_hash"))?,
67 by_height: MapxOrd::from_meta(by_height_id).c(d!("restore by_height"))?,
68 commit_qcs: MapxOrd::from_meta(commit_qcs_id).c(d!("restore commit_qcs"))?,
69 tx_index: MapxOrd::from_meta(tx_index_id).c(d!("restore tx_index"))?,
70 block_results: MapxOrd::from_meta(block_results_id)
71 .c(d!("restore block_results"))?,
72 })
73 } else {
74 Err(eg!(
75 "corrupt block_store.meta: expected 24 or 40 bytes, got {}",
76 bytes.len()
77 ))
78 }
79 } else {
80 let by_hash: MapxOrd<[u8; 32], Block> = MapxOrd::new();
81 let by_height: MapxOrd<u64, [u8; 32]> = MapxOrd::new();
82 let commit_qcs: MapxOrd<u64, QuorumCertificate> = MapxOrd::new();
83 let tx_index: MapxOrd<[u8; 32], (u64, u32)> = MapxOrd::new();
84 let block_results: MapxOrd<u64, EndBlockResponse> = MapxOrd::new();
85
86 let by_hash_id = by_hash.save_meta().c(d!())?;
87 let by_height_id = by_height.save_meta().c(d!())?;
88 let commit_qcs_id = commit_qcs.save_meta().c(d!())?;
89 let tx_index_id = tx_index.save_meta().c(d!())?;
90 let block_results_id = block_results.save_meta().c(d!())?;
91
92 let mut meta = [0u8; 40];
93 meta[0..8].copy_from_slice(&by_hash_id.to_le_bytes());
94 meta[8..16].copy_from_slice(&by_height_id.to_le_bytes());
95 meta[16..24].copy_from_slice(&commit_qcs_id.to_le_bytes());
96 meta[24..32].copy_from_slice(&tx_index_id.to_le_bytes());
97 meta[32..40].copy_from_slice(&block_results_id.to_le_bytes());
98 std::fs::write(&meta_path, meta).c(d!("write block_store.meta"))?;
99
100 let mut store = Self {
101 by_hash,
102 by_height,
103 commit_qcs,
104 tx_index,
105 block_results,
106 };
107 store.put_block(Block::genesis());
108 Ok(store)
109 }
110 }
111
112 pub fn new() -> Self {
115 let mut store = Self {
116 by_hash: MapxOrd::new(),
117 by_height: MapxOrd::new(),
118 commit_qcs: MapxOrd::new(),
119 tx_index: MapxOrd::new(),
120 block_results: MapxOrd::new(),
121 };
122 store.put_block(Block::genesis());
123 store
124 }
125
126 pub fn contains(&self, hash: &BlockHash) -> bool {
127 self.by_hash.contains_key(&hash.0)
128 }
129
130 pub fn flush(&self) {
131 vsdb::vsdb_flush();
132 }
133}
134
135impl Default for VsdbBlockStore {
136 fn default() -> Self {
137 Self::new()
138 }
139}
140
141impl BlockStore for VsdbBlockStore {
142 fn put_block(&mut self, block: Block) {
143 debug!(height = block.height.as_u64(), hash = %block.hash, "storing block to vsdb");
144 self.by_height.insert(&block.height.as_u64(), &block.hash.0);
145 self.by_hash.insert(&block.hash.0, &block);
146 }
147
148 fn get_block(&self, hash: &BlockHash) -> Option<Block> {
149 self.by_hash.get(&hash.0)
150 }
151
152 fn get_block_by_height(&self, h: Height) -> Option<Block> {
153 self.by_height
154 .get(&h.as_u64())
155 .and_then(|hash_bytes| self.by_hash.get(&hash_bytes))
156 }
157
158 fn get_blocks_in_range(&self, from: Height, to: Height) -> Vec<Block> {
159 self.by_height
160 .range(from.as_u64()..=to.as_u64())
161 .filter_map(|(_, hash_bytes)| self.by_hash.get(&hash_bytes))
162 .collect()
163 }
164
165 fn tip_height(&self) -> Height {
166 self.by_height
167 .last()
168 .map(|(h, _)| Height(h))
169 .unwrap_or(Height::GENESIS)
170 }
171
172 fn put_commit_qc(&mut self, height: Height, qc: QuorumCertificate) {
173 self.commit_qcs.insert(&height.as_u64(), &qc);
174 }
175
176 fn get_commit_qc(&self, height: Height) -> Option<QuorumCertificate> {
177 self.commit_qcs.get(&height.as_u64())
178 }
179
180 fn flush(&self) {
181 vsdb::vsdb_flush();
182 }
183
184 fn put_tx_index(&mut self, tx_hash: [u8; 32], height: Height, index: u32) {
185 self.tx_index.insert(&tx_hash, &(height.as_u64(), index));
186 }
187
188 fn get_tx_location(&self, tx_hash: &[u8; 32]) -> Option<(Height, u32)> {
189 self.tx_index.get(tx_hash).map(|(h, idx)| (Height(h), idx))
190 }
191
192 fn put_block_results(&mut self, height: Height, results: EndBlockResponse) {
193 self.block_results.insert(&height.as_u64(), &results);
194 }
195
196 fn get_block_results(&self, height: Height) -> Option<EndBlockResponse> {
197 self.block_results.get(&height.as_u64())
198 }
199}