1use std::io::ErrorKind;
11use std::ops::Not;
12use std::path::{Path, PathBuf};
13
14use miden_node_utils::tracing::miden_instrument;
15use miden_protocol::block::BlockNumber;
16use miden_protocol::utils::serde::Serializable;
17
18use crate::COMPONENT;
19use crate::genesis::GenesisBlock;
20
21#[derive(Clone, Debug)]
22pub struct BlockStore {
23 store_dir: PathBuf,
24}
25
26impl BlockStore {
27 #[miden_instrument(
37 target = COMPONENT,
38 name = "store.block_store.bootstrap",
39 skip_all,
40 err,
41 fields(
42 path = %store_dir.display(),
43 ),
44 )]
45 pub fn bootstrap(store_dir: PathBuf, genesis_block: &GenesisBlock) -> std::io::Result<Self> {
46 fs_err::create_dir(&store_dir)?;
47
48 let block_store = Self { store_dir };
49 block_store.save_block_blocking(BlockNumber::GENESIS, &genesis_block.inner().to_bytes())?;
50
51 block_store.save_proven_tip(BlockNumber::GENESIS)?;
53
54 Ok(block_store)
55 }
56
57 pub fn load(store_dir: PathBuf) -> std::io::Result<Self> {
73 let meta = fs_err::metadata(&store_dir)?;
74 if meta.is_dir().not() {
75 return Err(ErrorKind::NotADirectory.into());
76 }
77
78 Ok(Self { store_dir })
79 }
80
81 pub async fn load_block(&self, block_num: BlockNumber) -> std::io::Result<Option<Vec<u8>>> {
82 match tokio::fs::read(self.block_path(block_num)).await {
83 Ok(data) => Ok(Some(data)),
84 Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(None),
85 Err(err) => Err(err),
86 }
87 }
88
89 #[miden_instrument(
90 target = COMPONENT,
91 name = "store.block_store.save_block",
92 skip(self, data),
93 err,
94 fields(
95 block_size = data.len(),
96 ),
97 )]
98 pub async fn save_block(&self, block_num: BlockNumber, data: &[u8]) -> std::io::Result<()> {
99 let (epoch_path, block_path) = self.epoch_block_path(block_num)?;
100 if !epoch_path.exists() {
101 tokio::fs::create_dir_all(epoch_path).await?;
102 }
103
104 tokio::fs::write(block_path, data).await
105 }
106
107 pub fn save_block_blocking(&self, block_num: BlockNumber, data: &[u8]) -> std::io::Result<()> {
108 let (epoch_path, block_path) = self.epoch_block_path(block_num)?;
109 if !epoch_path.exists() {
110 fs_err::create_dir_all(epoch_path)?;
111 }
112
113 fs_err::write(block_path, data)
114 }
115
116 #[miden_instrument(
120 target = COMPONENT,
121 name = "store.block_store.save_proof",
122 skip_all,
123 err,
124 fields(
125 block.number = block_num.as_u32(),
126 proof_size = data.len(),
127 ),
128 )]
129 async fn save_proof(&self, block_num: BlockNumber, data: &[u8]) -> std::io::Result<()> {
130 let (epoch_path, proof_path) = self.epoch_proof_path(block_num)?;
131 if !epoch_path.exists() {
132 tokio::fs::create_dir_all(epoch_path).await?;
133 }
134
135 tokio::fs::write(proof_path, data).await
136 }
137
138 pub async fn load_proof(&self, block_num: BlockNumber) -> std::io::Result<Option<Vec<u8>>> {
139 match tokio::fs::read(self.proof_path(block_num)).await {
140 Ok(data) => Ok(Some(data)),
141 Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(None),
142 Err(err) => Err(err),
143 }
144 }
145
146 #[miden_instrument(
150 target = COMPONENT,
151 name = "store.block_store.save_proving_inputs",
152 skip_all,
153 err,
154 fields(
155 block.number = block_num.as_u32(),
156 inputs_size = data.len(),
157 ),
158 )]
159 pub async fn save_proving_inputs(
160 &self,
161 block_num: BlockNumber,
162 data: &[u8],
163 ) -> std::io::Result<()> {
164 let (epoch_path, inputs_path) = self.epoch_inputs_path(block_num)?;
165 if !epoch_path.exists() {
166 tokio::fs::create_dir_all(epoch_path).await?;
167 }
168 tokio::fs::write(inputs_path, data).await
169 }
170
171 pub async fn load_proving_inputs(
172 &self,
173 block_num: BlockNumber,
174 ) -> std::io::Result<Option<Vec<u8>>> {
175 match tokio::fs::read(self.inputs_path(block_num)).await {
176 Ok(data) => Ok(Some(data)),
177 Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(None),
178 Err(err) => Err(err),
179 }
180 }
181
182 pub async fn delete_proving_inputs(&self, block_num: BlockNumber) -> std::io::Result<()> {
183 match tokio::fs::remove_file(self.inputs_path(block_num)).await {
184 Ok(()) => Ok(()),
185 Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(()),
186 Err(err) => Err(err),
187 }
188 }
189
190 fn block_path(&self, block_num: BlockNumber) -> PathBuf {
194 let block_num = block_num.as_u32();
195 let epoch = block_num >> 16;
196 let epoch_dir = self.store_dir.join(format!("{epoch:04x}"));
197 epoch_dir.join(format!("block_{block_num:08x}.dat"))
198 }
199
200 fn proof_path(&self, block_num: BlockNumber) -> PathBuf {
201 let block_num = block_num.as_u32();
202 let epoch = block_num >> 16;
203 let epoch_dir = self.store_dir.join(format!("{epoch:04x}"));
204 epoch_dir.join(format!("proof_{block_num:08x}.dat"))
205 }
206
207 fn epoch_block_path(&self, block_num: BlockNumber) -> std::io::Result<(PathBuf, PathBuf)> {
208 let block_path = self.block_path(block_num);
209 let epoch_path = block_path.parent().ok_or(std::io::Error::from(ErrorKind::NotFound))?;
210
211 Ok((epoch_path.to_path_buf(), block_path))
212 }
213
214 fn epoch_proof_path(&self, block_num: BlockNumber) -> std::io::Result<(PathBuf, PathBuf)> {
215 let proof_path = self.proof_path(block_num);
216 let epoch_path = proof_path.parent().ok_or(std::io::Error::from(ErrorKind::NotFound))?;
217
218 Ok((epoch_path.to_path_buf(), proof_path))
219 }
220
221 fn inputs_path(&self, block_num: BlockNumber) -> PathBuf {
222 let block_num = block_num.as_u32();
223 let epoch = block_num >> 16;
224 let epoch_dir = self.store_dir.join(format!("{epoch:04x}"));
225 epoch_dir.join(format!("inputs_{block_num:08x}.dat"))
226 }
227
228 fn epoch_inputs_path(&self, block_num: BlockNumber) -> std::io::Result<(PathBuf, PathBuf)> {
229 let inputs_path = self.inputs_path(block_num);
230 let epoch_path = inputs_path.parent().ok_or(std::io::Error::from(ErrorKind::NotFound))?;
231
232 Ok((epoch_path.to_path_buf(), inputs_path))
233 }
234
235 pub async fn commit_proof(&self, block_num: BlockNumber, proof: &[u8]) -> std::io::Result<()> {
243 self.save_proof(block_num, proof).await?;
244 self.save_proven_tip(block_num)?;
245 self.delete_proving_inputs(block_num).await
246 }
247
248 pub fn load_proven_tip(&self) -> std::io::Result<BlockNumber> {
250 Self::read_proven_tip_from(&self.proven_tip_path())
251 }
252
253 fn save_proven_tip(&self, tip: BlockNumber) -> std::io::Result<()> {
255 let path = self.proven_tip_path();
256 let tmp = path.with_extension("tmp");
257 fs_err::write(&tmp, tip.as_u32().to_le_bytes())?;
258 fs_err::rename(&tmp, &path)
259 }
260
261 fn proven_tip_path(&self) -> PathBuf {
262 self.store_dir.join("proven_tip")
263 }
264
265 fn read_proven_tip_from(path: &Path) -> std::io::Result<BlockNumber> {
266 let bytes = fs_err::read(path)?;
267 let arr: [u8; 4] = bytes.try_into().map_err(|_| {
268 std::io::Error::new(
269 std::io::ErrorKind::InvalidData,
270 "proven tip file has unexpected size (expected 4 bytes)",
271 )
272 })?;
273 Ok(BlockNumber::from(u32::from_le_bytes(arr)))
274 }
275
276 pub fn display(&self) -> std::path::Display<'_> {
277 self.store_dir.display()
278 }
279}