miden_objects/transaction/
partial_blockchain.rs1use alloc::{collections::BTreeMap, vec::Vec};
2
3use crate::{
4 PartialBlockchainError,
5 block::{BlockHeader, BlockNumber},
6 crypto::merkle::{InnerNodeInfo, MmrPeaks, PartialMmr},
7 utils::serde::{Deserializable, Serializable},
8};
9
10#[derive(Debug, Clone, PartialEq, Eq)]
36pub struct PartialBlockchain {
37 mmr: PartialMmr,
39 blocks: BTreeMap<BlockNumber, BlockHeader>,
42}
43
44impl PartialBlockchain {
45 pub fn new(
60 mmr: PartialMmr,
61 blocks: impl IntoIterator<Item = BlockHeader>,
62 ) -> Result<Self, PartialBlockchainError> {
63 let partial_chain = Self::new_unchecked(mmr, blocks)?;
64
65 for (block_num, block) in partial_chain.blocks.iter() {
67 let proof = partial_chain
70 .mmr
71 .open(block_num.as_usize())
72 .expect("block should not exceed chain length")
73 .expect("block should be tracked in the partial MMR");
74
75 partial_chain.mmr.peaks().verify(block.commitment(), proof).map_err(|source| {
76 PartialBlockchainError::BlockHeaderCommitmentMismatch {
77 block_num: *block_num,
78 block_commitment: block.commitment(),
79 source,
80 }
81 })?;
82 }
83
84 Ok(partial_chain)
85 }
86
87 pub fn new_unchecked(
104 mmr: PartialMmr,
105 blocks: impl IntoIterator<Item = BlockHeader>,
106 ) -> Result<Self, PartialBlockchainError> {
107 let chain_length = mmr.forest();
108 let mut block_map = BTreeMap::new();
109 for block in blocks {
110 let block_num = block.block_num();
111 if block.block_num().as_usize() >= chain_length {
112 return Err(PartialBlockchainError::block_num_too_big(chain_length, block_num));
113 }
114
115 if !mmr.is_tracked(block_num.as_usize()) {
118 return Err(PartialBlockchainError::untracked_block(block_num));
119 }
120
121 if block_map.insert(block_num, block).is_some() {
122 return Err(PartialBlockchainError::duplicate_block(block_num));
123 }
124 }
125
126 Ok(Self { mmr, blocks: block_map })
127 }
128
129 pub fn mmr(&self) -> &PartialMmr {
134 &self.mmr
135 }
136
137 pub fn peaks(&self) -> MmrPeaks {
139 self.mmr.peaks()
140 }
141
142 pub fn chain_length(&self) -> BlockNumber {
144 BlockNumber::from(
145 u32::try_from(self.mmr.forest())
146 .expect("partial blockchain should never contain more than u32::MAX blocks"),
147 )
148 }
149
150 pub fn contains_block(&self, block_num: BlockNumber) -> bool {
154 self.blocks.contains_key(&block_num)
155 }
156
157 pub fn get_block(&self, block_num: BlockNumber) -> Option<&BlockHeader> {
160 self.blocks.get(&block_num)
161 }
162
163 pub fn block_headers(&self) -> impl Iterator<Item = &BlockHeader> {
165 self.blocks.values()
166 }
167
168 pub fn add_block(&mut self, block_header: BlockHeader, track: bool) {
181 assert_eq!(block_header.block_num(), self.chain_length());
182 self.mmr.add(block_header.commitment(), track);
183 }
184
185 pub fn inner_nodes(&self) -> impl Iterator<Item = InnerNodeInfo> + '_ {
191 self.mmr.inner_nodes(
192 self.blocks
193 .values()
194 .map(|block| (block.block_num().as_usize(), block.commitment())),
195 )
196 }
197
198 #[cfg(any(feature = "testing", test))]
206 pub fn block_headers_mut(&mut self) -> &mut BTreeMap<BlockNumber, BlockHeader> {
207 &mut self.blocks
208 }
209
210 #[cfg(any(feature = "testing", test))]
214 pub fn partial_mmr_mut(&mut self) -> &mut PartialMmr {
215 &mut self.mmr
216 }
217}
218
219impl Serializable for PartialBlockchain {
220 fn write_into<W: miden_crypto::utils::ByteWriter>(&self, target: &mut W) {
221 self.mmr.write_into(target);
222 self.blocks.write_into(target);
223 }
224}
225
226impl Deserializable for PartialBlockchain {
227 fn read_from<R: miden_crypto::utils::ByteReader>(
228 source: &mut R,
229 ) -> Result<Self, miden_crypto::utils::DeserializationError> {
230 let mmr = PartialMmr::read_from(source)?;
231 let blocks = BTreeMap::<BlockNumber, BlockHeader>::read_from(source)?;
232 Ok(Self { mmr, blocks })
233 }
234}
235
236impl Default for PartialBlockchain {
237 fn default() -> Self {
238 let empty_mmr = PartialMmr::from_peaks(
241 MmrPeaks::new(0, Vec::new()).expect("empty MmrPeaks should be valid"),
242 );
243
244 Self::new(empty_mmr, Vec::new()).expect("empty partial blockchain should be valid")
245 }
246}
247
248#[cfg(test)]
252mod tests {
253 use assert_matches::assert_matches;
254 use vm_core::utils::{Deserializable, Serializable};
255
256 use super::PartialBlockchain;
257 use crate::{
258 Digest, PartialBlockchainError,
259 alloc::vec::Vec,
260 block::{BlockHeader, BlockNumber},
261 crypto::merkle::{Mmr, PartialMmr},
262 };
263
264 #[test]
265 fn test_partial_blockchain_add() {
266 let mut mmr = Mmr::default();
268 for i in 0..3 {
269 let block_header = int_to_block_header(i);
270 mmr.add(block_header.commitment());
271 }
272 let partial_mmr: PartialMmr = mmr.peaks().into();
273 let mut partial_blockchain = PartialBlockchain::new(partial_mmr, Vec::new()).unwrap();
274
275 let block_num = 3;
277 let bock_header = int_to_block_header(block_num);
278 mmr.add(bock_header.commitment());
279 partial_blockchain.add_block(bock_header, true);
280
281 assert_eq!(
282 mmr.open(block_num as usize).unwrap(),
283 partial_blockchain.mmr.open(block_num as usize).unwrap().unwrap()
284 );
285
286 let block_num = 4;
288 let bock_header = int_to_block_header(block_num);
289 mmr.add(bock_header.commitment());
290 partial_blockchain.add_block(bock_header, true);
291
292 assert_eq!(
293 mmr.open(block_num as usize).unwrap(),
294 partial_blockchain.mmr.open(block_num as usize).unwrap().unwrap()
295 );
296
297 let block_num = 5;
299 let bock_header = int_to_block_header(block_num);
300 mmr.add(bock_header.commitment());
301 partial_blockchain.add_block(bock_header, true);
302
303 assert_eq!(
304 mmr.open(block_num as usize).unwrap(),
305 partial_blockchain.mmr.open(block_num as usize).unwrap().unwrap()
306 );
307 }
308
309 #[test]
310 fn partial_blockchain_new_on_invalid_header_fails() {
311 let block_header0 = int_to_block_header(0);
312 let block_header1 = int_to_block_header(1);
313 let block_header2 = int_to_block_header(2);
314
315 let mut mmr = Mmr::default();
316 mmr.add(block_header0.commitment());
317 mmr.add(block_header1.commitment());
318 mmr.add(block_header2.commitment());
319
320 let mut partial_mmr = PartialMmr::from_peaks(mmr.peaks());
321 for i in 0..3 {
322 partial_mmr
323 .track(i, mmr.get(i).unwrap(), &mmr.open(i).unwrap().merkle_path)
324 .unwrap();
325 }
326
327 let fake_block_header2 = BlockHeader::mock(2, None, None, &[], Digest::default());
328
329 assert_ne!(block_header2.commitment(), fake_block_header2.commitment());
330
331 let error = PartialBlockchain::new(
333 partial_mmr,
334 vec![block_header0, block_header1, fake_block_header2.clone()],
335 )
336 .unwrap_err();
337
338 assert_matches!(
339 error,
340 PartialBlockchainError::BlockHeaderCommitmentMismatch {
341 block_commitment,
342 block_num,
343 ..
344 } if block_commitment == fake_block_header2.commitment() && block_num == fake_block_header2.block_num()
345 )
346 }
347
348 #[test]
349 fn partial_blockchain_new_on_block_number_exceeding_chain_length_fails() {
350 let block_header0 = int_to_block_header(0);
351 let mmr = Mmr::default();
352 let partial_mmr = PartialMmr::from_peaks(mmr.peaks());
353
354 let error = PartialBlockchain::new(partial_mmr, [block_header0]).unwrap_err();
355
356 assert_matches!(error, PartialBlockchainError::BlockNumTooBig {
357 chain_length,
358 block_num,
359 } if chain_length == 0 && block_num == BlockNumber::from(0));
360 }
361
362 #[test]
363 fn partial_blockchain_new_on_untracked_block_number_fails() {
364 let block_header0 = int_to_block_header(0);
365 let block_header1 = int_to_block_header(1);
366
367 let mut mmr = Mmr::default();
368 mmr.add(block_header0.commitment());
369 mmr.add(block_header1.commitment());
370
371 let mut partial_mmr = PartialMmr::from_peaks(mmr.peaks());
372 partial_mmr
373 .track(1, block_header1.commitment(), &mmr.open(1).unwrap().merkle_path)
374 .unwrap();
375
376 let error =
377 PartialBlockchain::new(partial_mmr, [block_header0, block_header1]).unwrap_err();
378
379 assert_matches!(error, PartialBlockchainError::UntrackedBlock {
380 block_num,
381 } if block_num == BlockNumber::from(0));
382 }
383
384 #[test]
385 fn partial_blockchain_serialization() {
386 let mut mmr = Mmr::default();
388 for i in 0..3 {
389 let block_header = int_to_block_header(i);
390 mmr.add(block_header.commitment());
391 }
392 let partial_mmr: PartialMmr = mmr.peaks().into();
393 let partial_blockchain = PartialBlockchain::new(partial_mmr, Vec::new()).unwrap();
394
395 let bytes = partial_blockchain.to_bytes();
396 let deserialized = PartialBlockchain::read_from_bytes(&bytes).unwrap();
397
398 assert_eq!(partial_blockchain, deserialized);
399 }
400
401 fn int_to_block_header(block_num: impl Into<BlockNumber>) -> BlockHeader {
402 BlockHeader::new(
403 0,
404 Digest::default(),
405 block_num.into(),
406 Digest::default(),
407 Digest::default(),
408 Digest::default(),
409 Digest::default(),
410 Digest::default(),
411 Digest::default(),
412 Digest::default(),
413 0,
414 )
415 }
416}