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