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