miden_objects/block/blockchain.rs
1use alloc::collections::BTreeSet;
2
3use miden_core::utils::{ByteReader, ByteWriter, Deserializable, Serializable};
4use miden_crypto::merkle::{Forest, Mmr, MmrError, MmrPeaks, MmrProof, PartialMmr};
5use miden_processor::DeserializationError;
6
7use crate::Word;
8use crate::block::BlockNumber;
9
10/// The [Merkle Mountain Range](Mmr) defining the Miden blockchain.
11///
12/// The values of the leaves in the MMR are the commitments of blocks, i.e.
13/// [`BlockHeader::commitment`](crate::block::BlockHeader::commitment).
14///
15/// Each new block updates the blockchain by adding **the previous block's commitment** to the MMR.
16/// This means the chain commitment found in block 10's header commits to all blocks 0..=9, but not
17/// 10 itself. This results from the fact that block 10 cannot compute its own block commitment
18/// and thus cannot add itself to the chain. Hence, the blockchain is lagging behind by one block.
19///
20/// Some APIs take a _checkpoint_ which is equivalent to the concept of _forest_ of the underlying
21/// MMR. As an example, if the blockchain has 20 blocks in total, and the checkpoint is 10, then
22/// the API works in the context of the chain at the time it had 10 blocks, i.e. it contains blocks
23/// 0..=9. This is useful, for example, to retrieve proofs that are valid when verified against the
24/// chain commitment of block 10.
25///
26/// The maximum number of supported blocks is [`u32::MAX`]. This is not validated however.
27#[derive(Debug, Clone)]
28pub struct Blockchain {
29 mmr: Mmr,
30}
31
32impl Blockchain {
33 // CONSTRUCTORS
34 // --------------------------------------------------------------------------------------------
35
36 /// Returns a new, empty blockchain.
37 pub fn new() -> Self {
38 Self { mmr: Mmr::new() }
39 }
40
41 /// Construct a new blockchain from an [`Mmr`] without validation.
42 pub fn from_mmr_unchecked(mmr: Mmr) -> Self {
43 Self { mmr }
44 }
45
46 // PUBLIC ACCESSORS
47 // --------------------------------------------------------------------------------------------
48
49 /// Returns the number of blocks in the chain.
50 pub fn num_blocks(&self) -> u32 {
51 // SAFETY: The chain should never contain more than u32::MAX blocks, so a non-panicking cast
52 // should be fine.
53 self.mmr.forest().num_leaves() as u32
54 }
55
56 /// Returns the tip of the chain, i.e. the number of the latest block in the chain, unless the
57 /// chain is empty.
58 pub fn chain_tip(&self) -> Option<BlockNumber> {
59 if self.num_blocks() == 0 {
60 return None;
61 }
62
63 Some(BlockNumber::from(self.num_blocks() - 1))
64 }
65
66 /// Returns the chain commitment.
67 pub fn commitment(&self) -> Word {
68 self.peaks().hash_peaks()
69 }
70
71 /// Returns the current peaks of the MMR.
72 pub fn peaks(&self) -> MmrPeaks {
73 self.mmr.peaks()
74 }
75
76 /// Returns the peaks of the chain at the state of the given block.
77 ///
78 /// Note that this represents the state of the chain where the block at the given number **is
79 /// not yet** in the chain. For example, if the given block number is 5, then the returned peaks
80 /// represent the chain whose latest block is 4. See the type-level documentation for why this
81 /// is the case.
82 ///
83 /// # Errors
84 ///
85 /// Returns an error if the specified `block` exceeds the number of blocks in the chain.
86 pub fn peaks_at(&self, checkpoint: BlockNumber) -> Result<MmrPeaks, MmrError> {
87 self.mmr.peaks_at(Forest::new(checkpoint.as_usize()))
88 }
89
90 /// Returns an [`MmrProof`] for the `block` with the given number.
91 ///
92 /// # Errors
93 ///
94 /// Returns an error if:
95 /// - The specified block number does not exist in the chain.
96 pub fn open(&self, block: BlockNumber) -> Result<MmrProof, MmrError> {
97 self.mmr.open(block.as_usize())
98 }
99
100 /// Returns an [`MmrProof`] for the `block` with the given number at the state of the given
101 /// `checkpoint`.
102 ///
103 /// # Errors
104 ///
105 /// Returns an error if:
106 /// - The specified block number does not exist in the chain.
107 /// - The specified checkpoint number exceeds the number of blocks in the chain.
108 pub fn open_at(
109 &self,
110 block: BlockNumber,
111 checkpoint: BlockNumber,
112 ) -> Result<MmrProof, MmrError> {
113 self.mmr.open_at(block.as_usize(), Forest::new(checkpoint.as_usize()))
114 }
115
116 /// Returns a reference to the underlying [`Mmr`].
117 pub fn as_mmr(&self) -> &Mmr {
118 &self.mmr
119 }
120
121 /// Creates a [`PartialMmr`] at the state of the given block. This means the hashed peaks of the
122 /// returned partial MMR will match the checkpoint's chain commitment. This MMR will include
123 /// authentication paths for all blocks in the provided `blocks` set.
124 ///
125 /// # Errors
126 ///
127 /// Returns an error if:
128 /// - the specified `latest_block_number` exceeds the number of blocks in the chain.
129 /// - any block in `blocks` is not in the state of the chain specified by `latest_block_number`.
130 pub fn partial_mmr_from_blocks(
131 &self,
132 blocks: &BTreeSet<BlockNumber>,
133 checkpoint: BlockNumber,
134 ) -> Result<PartialMmr, MmrError> {
135 // Using latest block as the target state means we take the state of the MMR one before
136 // the latest block.
137 let peaks = self.peaks_at(checkpoint)?;
138
139 // Track the merkle paths of the requested blocks in the partial MMR.
140 let mut partial_mmr = PartialMmr::from_peaks(peaks);
141 for block_num in blocks.iter() {
142 let leaf = self.mmr.get(block_num.as_usize())?;
143 let path = self.open_at(*block_num, checkpoint)?.merkle_path;
144
145 // SAFETY: We should be able to fill the partial MMR with data from the partial
146 // blockchain without errors, otherwise it indicates the blockchain is
147 // invalid.
148 partial_mmr
149 .track(block_num.as_usize(), leaf, &path)
150 .expect("filling partial mmr with data from mmr should succeed");
151 }
152
153 Ok(partial_mmr)
154 }
155
156 // PUBLIC MUTATORS
157 // --------------------------------------------------------------------------------------------
158
159 /// Adds a block commitment to the MMR.
160 ///
161 /// The caller must ensure that this commitent is the one for the next block in the chain.
162 pub fn push(&mut self, block_commitment: Word) {
163 self.mmr.add(block_commitment);
164 }
165}
166
167impl Default for Blockchain {
168 fn default() -> Self {
169 Self::new()
170 }
171}
172
173// SERIALIZATION
174// ================================================================================================
175
176impl Serializable for Blockchain {
177 fn write_into<W: ByteWriter>(&self, target: &mut W) {
178 self.mmr.write_into(target);
179 }
180}
181
182impl Deserializable for Blockchain {
183 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
184 let chain = Mmr::read_from(source)?;
185 Ok(Self::from_mmr_unchecked(chain))
186 }
187}