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