Skip to main content

miden_objects/decoded/
blockchain.rs

1//! Domain construction for decoded blockchain messages.
2use miden_protobuf::unwrap_infallible;
3pub use proto::blockchain::DecodedTrackedMmrLeaf as TrackedMmrLeaf;
4
5use crate::decoded::VerificationError;
6use crate::{Verify, proto};
7
8#[cfg(test)]
9mod tests;
10
11#[cfg(test)]
12pub(crate) mod test_utils;
13
14impl Verify for TrackedMmrLeaf {
15    type Verified = (u64, miden_protocol::Word, alloc::vec::Vec<miden_protocol::Word>);
16    type Error = core::convert::Infallible;
17    fn verify(self) -> Result<Self::Verified, Self::Error> {
18        Ok((self.position, self.leaf, self.path))
19    }
20}
21
22pub use proto::blockchain::DecodedBlockNumber as BlockNumber;
23
24impl Verify for BlockNumber {
25    type Verified = miden_protocol::block::BlockNumber;
26    type Error = core::convert::Infallible;
27    fn verify(self) -> Result<Self::Verified, Self::Error> {
28        Ok(self.block_num.into())
29    }
30}
31
32pub use proto::blockchain::DecodedFeeParameters as FeeParameters;
33
34impl Verify for FeeParameters {
35    type Verified = miden_protocol::block::FeeParameters;
36    type Error = core::convert::Infallible;
37    fn verify(self) -> Result<Self::Verified, Self::Error> {
38        Ok(Self::Verified::new(self.verification_base_fee))
39    }
40}
41
42pub use proto::blockchain::DecodedNextProtocolConfig as NextProtocolConfig;
43
44impl Verify for NextProtocolConfig {
45    type Verified = miden_protocol::protocol_config::NextProtocolConfig;
46    type Error = miden_protocol::errors::ProtocolConfigError;
47    fn verify(self) -> Result<Self::Verified, Self::Error> {
48        let effective_from = unwrap_infallible(self.effective_from.verify());
49        Self::Verified::new(effective_from, self.protocol_config)
50    }
51}
52
53pub use proto::blockchain::DecodedValidatorConfig as ValidatorConfig;
54
55impl Verify for ValidatorConfig {
56    type Verified = miden_protocol::block::ValidatorConfig;
57    type Error = VerificationError;
58    fn verify(self) -> Result<Self::Verified, Self::Error> {
59        let keys = self.keys.into_iter().map(|key| unwrap_infallible(key.verify())).collect();
60        Ok(Self::Verified::new(keys, self.quorum.try_into()?)?)
61    }
62}
63
64pub use proto::blockchain::DecodedBlockHeader as BlockHeader;
65
66/// Builds a header without validating its parent linkage, signatures, or protocol transition.
67impl crate::BuildUnchecked for BlockHeader {
68    type Output = miden_protocol::block::BlockHeader;
69    type Error = VerificationError;
70    fn build_unchecked(self) -> Result<Self::Output, Self::Error> {
71        if self.version != proto::blockchain::BlockVersion::V1 {
72            return Err(BlockHeaderError::UnspecifiedVersion.into());
73        }
74        Ok(Self::Output::new(
75            self.prev_block_commitment,
76            unwrap_infallible(self.block_num.verify()),
77            self.chain_commitment,
78            self.account_root,
79            self.nullifier_root,
80            self.note_root,
81            self.tx_commitment,
82            self.validator_config.verify()?,
83            unwrap_infallible(self.fee_parameters.verify()),
84            self.protocol_config_commitment,
85            self.next_protocol_config.map(Verify::verify).transpose()?,
86            self.timestamp,
87        ))
88    }
89}
90
91#[derive(Debug, thiserror::Error)]
92pub enum BlockHeaderError {
93    #[error("block header version is unspecified")]
94    UnspecifiedVersion,
95}
96
97pub use proto::blockchain::DecodedPartialBlockchain as PartialBlockchain;
98
99/// Checks MMR reconstruction and header membership, but not header parent linkage or a trusted
100/// root.
101impl crate::BuildUnchecked for PartialBlockchain {
102    type Output = miden_protocol::transaction::PartialBlockchain;
103    type Error = VerificationError;
104    fn build_unchecked(self) -> Result<Self::Output, Self::Error> {
105        use miden_protocol::crypto::merkle::MerklePath;
106        use miden_protocol::crypto::merkle::mmr::{Forest, MmrPeaks, PartialMmr};
107
108        let size = usize::try_from(self.forest)?;
109        let peaks = MmrPeaks::new(Forest::new(size)?, self.peaks)?;
110        let mut mmr = PartialMmr::from_peaks(peaks);
111        let mut previous = None;
112        for tracked in self.tracked_leaves {
113            let position = usize::try_from(tracked.position)?;
114            if position >= size {
115                return Err(PartialBlockchainError::Position { position, size }.into());
116            }
117            if previous.is_some_and(|previous| position <= previous) {
118                return Err(PartialBlockchainError::LeafOrder.into());
119            }
120            previous = Some(position);
121            mmr.track(position, tracked.leaf, &MerklePath::new(tracked.path))?;
122        }
123        let mut previous = None;
124        let mut headers = alloc::vec::Vec::new();
125        for header in self.block_headers {
126            let header = header.build_unchecked()?;
127            if previous.is_some_and(|previous| header.block_num() <= previous) {
128                return Err(PartialBlockchainError::HeaderOrder.into());
129            }
130            previous = Some(header.block_num());
131            headers.push(header);
132        }
133        Ok(Self::Output::new(mmr, headers)?)
134    }
135}
136
137#[derive(Debug, thiserror::Error)]
138pub enum PartialBlockchainError {
139    #[error("tracked leaf position {position} is outside forest of size {size}")]
140    Position { position: usize, size: usize },
141    #[error("tracked leaf positions must be unique and strictly increasing")]
142    LeafOrder,
143    #[error("block headers must be unique and ordered by ascending block number")]
144    HeaderOrder,
145}
146
147pub use proto::blockchain::DecodedBlockAccountUpdate as BlockAccountUpdate;
148
149impl Verify for BlockAccountUpdate {
150    type Verified = miden_protocol::block::BlockAccountUpdate;
151    type Error = VerificationError;
152    fn verify(self) -> Result<Self::Verified, Self::Error> {
153        Ok(Self::Verified::new(
154            self.account_id.verify()?,
155            self.final_state_commitment,
156            self.details.verify()?,
157        )?)
158    }
159}
160
161pub use proto::blockchain::DecodedIndexedOutputNote as IndexedOutputNote;
162
163impl Verify for IndexedOutputNote {
164    type Verified = (usize, miden_protocol::transaction::OutputNote);
165    type Error = VerificationError;
166    fn verify(self) -> Result<Self::Verified, Self::Error> {
167        Ok((self.note_index_in_batch.try_into()?, self.note.verify()?))
168    }
169}
170
171pub use proto::blockchain::DecodedOutputNoteBatch as OutputNoteBatch;
172
173impl Verify for OutputNoteBatch {
174    type Verified = miden_protocol::block::OutputNoteBatch;
175    type Error = VerificationError;
176    fn verify(self) -> Result<Self::Verified, Self::Error> {
177        self.notes.into_iter().map(Verify::verify).collect()
178    }
179}
180
181pub use proto::blockchain::DecodedBlockBody as BlockBody;
182
183/// Checks body invariants, but trusts transaction ordering and unchecked input-note commitments.
184impl crate::BuildUnchecked for BlockBody {
185    type Output = miden_protocol::block::BlockBody;
186    type Error = VerificationError;
187    fn build_unchecked(self) -> Result<Self::Output, Self::Error> {
188        let updates = self
189            .updated_accounts
190            .into_iter()
191            .map(Verify::verify)
192            .collect::<Result<_, _>>()?;
193        let notes = self
194            .output_note_batches
195            .into_iter()
196            .map(Verify::verify)
197            .collect::<Result<_, _>>()?;
198        let nullifiers = self
199            .created_nullifiers
200            .into_iter()
201            .map(miden_protocol::note::Nullifier::from_raw)
202            .collect();
203        let transactions = self
204            .transactions
205            .into_iter()
206            .map(crate::BuildUnchecked::build_unchecked)
207            .collect::<Result<_, _>>()?;
208        Ok(Self::Output::new(
209            updates,
210            notes,
211            nullifiers,
212            miden_protocol::transaction::OrderedTransactionHeaders::new_unchecked(transactions),
213        )?)
214    }
215}
216
217pub use proto::blockchain::DecodedSignedBlock as SignedBlock;
218
219/// Checks header/body consistency but does not authenticate against a trusted parent.
220impl crate::BuildUnchecked for SignedBlock {
221    type Output = miden_protocol::block::SignedBlock;
222    type Error = VerificationError;
223    fn build_unchecked(self) -> Result<Self::Output, Self::Error> {
224        self.build(None)
225    }
226}
227
228impl SignedBlock {
229    fn build(
230        self,
231        parent: Option<&miden_protocol::block::BlockHeader>,
232    ) -> Result<miden_protocol::block::SignedBlock, VerificationError> {
233        use crate::BuildUnchecked;
234
235        let header = self.header.build_unchecked()?;
236        let body = self.body.build_unchecked()?;
237        let signatures = self
238            .signatures
239            .into_iter()
240            .map(|signature| unwrap_infallible(signature.verify()))
241            .collect();
242        let signatures = miden_protocol::block::BlockSignatures::new(signatures)
243            .map_err(VerificationError::new)?;
244        let block = miden_protocol::block::SignedBlock::new_unchecked(header, body, signatures);
245        block.validate(parent)?;
246        Ok(block)
247    }
248}
249
250/// Authenticates the block against an already-trusted parent, in addition to self-consistency.
251/// This does not re-execute transactions or validate the account/nullifier state transition.
252impl crate::VerifyWith<&miden_protocol::block::BlockHeader> for SignedBlock {
253    type Verified = miden_protocol::block::SignedBlock;
254    type Error = VerificationError;
255    fn verify_with(
256        self,
257        parent: &miden_protocol::block::BlockHeader,
258    ) -> Result<Self::Verified, Self::Error> {
259        self.build(Some(parent))
260    }
261}