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}