Skip to main content

miden_objects/conversion/
block.rs

1use alloc::format;
2use alloc::vec::Vec;
3
4use miden_protocol::Word;
5use miden_protocol::account::AccountUpdateDetails;
6use miden_protocol::block::{
7    BlockAccountUpdate,
8    BlockBody,
9    BlockHeader,
10    BlockNumber,
11    BlockSignatures,
12    FeeParameters,
13    OutputNoteBatch,
14    SignedBlock,
15    ValidatorConfig,
16};
17use miden_protocol::crypto::dsa::ecdsa_k256_keccak::{PublicKey, Signature};
18use miden_protocol::crypto::merkle::MerklePath;
19use miden_protocol::crypto::merkle::mmr::{Forest, MmrPeaks, PartialMmr};
20use miden_protocol::note::Nullifier;
21use miden_protocol::protocol_config::NextProtocolConfig;
22use miden_protocol::transaction::{
23    OrderedTransactionHeaders,
24    OutputNote,
25    PartialBlockchain,
26    TransactionHeader,
27};
28
29use super::{MessageDecodeExt, required};
30use crate::{ConversionError, ConversionResultExt, proto};
31
32// BLOCK NUMBER
33// ================================================================================================
34
35impl From<BlockNumber> for proto::blockchain::BlockNumber {
36    fn from(value: BlockNumber) -> Self {
37        Self { block_num: value.as_u32() }
38    }
39}
40
41impl From<proto::blockchain::BlockNumber> for BlockNumber {
42    fn from(value: proto::blockchain::BlockNumber) -> Self {
43        value.block_num.into()
44    }
45}
46
47// PARTIAL BLOCKCHAIN
48// ================================================================================================
49
50impl From<&PartialBlockchain> for proto::blockchain::PartialBlockchain {
51    fn from(value: &PartialBlockchain) -> Self {
52        let mmr = value.mmr();
53        let tracked_leaves = mmr
54            .leaves()
55            .map(|(position, leaf)| {
56                let proof = mmr
57                    .open(position)
58                    .expect("tracked MMR position must be in bounds")
59                    .expect("tracked MMR leaf must have an opening");
60                proto::blockchain::TrackedMmrLeaf {
61                    position: position as u64,
62                    leaf: Some(leaf.into()),
63                    path: proof.merkle_path().nodes().iter().map(Into::into).collect(),
64                }
65            })
66            .collect();
67        Self {
68            forest: mmr.forest().num_leaves() as u64,
69            peaks: mmr.peaks().peaks().iter().map(Into::into).collect(),
70            tracked_leaves,
71            block_headers: value.block_headers().map(Into::into).collect(),
72        }
73    }
74}
75
76impl TryFrom<proto::blockchain::PartialBlockchain> for PartialBlockchain {
77    type Error = ConversionError;
78
79    fn try_from(value: proto::blockchain::PartialBlockchain) -> Result<Self, Self::Error> {
80        let forest_size = usize::try_from(value.forest).context("forest")?;
81        let forest = Forest::new(forest_size).map_err(ConversionError::new).context("forest")?;
82        let peaks = value
83            .peaks
84            .into_iter()
85            .enumerate()
86            .map(|(index, peak)| Word::try_from(peak).context(format!("peaks[{index}]")))
87            .collect::<Result<Vec<_>, _>>()?;
88        let peaks = MmrPeaks::new(forest, peaks).map_err(ConversionError::new).context("peaks")?;
89        let mut mmr = PartialMmr::from_peaks(peaks);
90
91        let mut previous_position = None;
92        for (index, tracked) in value.tracked_leaves.into_iter().enumerate() {
93            let position = usize::try_from(tracked.position)
94                .context(format!("tracked_leaves[{index}].position"))?;
95            if position >= forest_size {
96                return Err(ConversionError::message(format!(
97                    "tracked leaf position {position} is outside forest of size {forest_size}"
98                ))
99                .context(format!("tracked_leaves[{index}].position")));
100            }
101            if previous_position.is_some_and(|previous| position <= previous) {
102                return Err(ConversionError::message(
103                    "tracked leaf positions must be unique and strictly increasing",
104                )
105                .context(format!("tracked_leaves[{index}].position")));
106            }
107            previous_position = Some(position);
108
109            let decoder = tracked.decoder();
110            let leaf = required!(decoder, tracked.leaf)?;
111            let path = tracked
112                .path
113                .into_iter()
114                .enumerate()
115                .map(|(path_index, node)| {
116                    Word::try_from(node)
117                        .context(format!("tracked_leaves[{index}].path[{path_index}]"))
118                })
119                .collect::<Result<Vec<_>, _>>()?;
120            mmr.track(position, leaf, &MerklePath::new(path))
121                .map_err(ConversionError::new)
122                .context(format!("tracked_leaves[{index}]"))?;
123        }
124
125        let mut previous_block_num = None;
126        let block_headers = value
127            .block_headers
128            .into_iter()
129            .enumerate()
130            .map(|(index, header)| {
131                let header =
132                    BlockHeader::try_from(header).context(format!("block_headers[{index}]"))?;
133                if previous_block_num.is_some_and(|previous| header.block_num() <= previous) {
134                    return Err(ConversionError::message(
135                        "block headers must be unique and ordered by ascending block number",
136                    )
137                    .context(format!("block_headers[{index}].block_num")));
138                }
139                previous_block_num = Some(header.block_num());
140                Ok(header)
141            })
142            .collect::<Result<Vec<_>, ConversionError>>()?;
143
144        Self::new(mmr, block_headers).map_err(ConversionError::new)
145    }
146}
147
148// BLOCK HEADER
149// ================================================================================================
150
151impl From<&BlockHeader> for proto::blockchain::BlockHeader {
152    fn from(header: &BlockHeader) -> Self {
153        Self {
154            version: proto::blockchain::BlockVersion::V1 as i32,
155            timestamp: header.timestamp(),
156            block_num: Some(header.block_num().into()),
157            prev_block_commitment: Some(header.prev_block_commitment().into()),
158            chain_commitment: Some(header.chain_commitment().into()),
159            account_root: Some(header.account_root().into()),
160            nullifier_root: Some(header.nullifier_root().into()),
161            note_root: Some(header.note_root().into()),
162            tx_commitment: Some(header.tx_commitment().into()),
163            validator_config: Some(header.validator_config().into()),
164            fee_parameters: Some(header.fee_parameters().into()),
165            protocol_config_commitment: Some(header.protocol_config_commitment().into()),
166            next_protocol_config: header.next_protocol_config().map(Into::into),
167        }
168    }
169}
170
171impl From<BlockHeader> for proto::blockchain::BlockHeader {
172    fn from(header: BlockHeader) -> Self {
173        (&header).into()
174    }
175}
176
177impl TryFrom<&proto::blockchain::BlockHeader> for BlockHeader {
178    type Error = ConversionError;
179
180    fn try_from(value: &proto::blockchain::BlockHeader) -> Result<Self, Self::Error> {
181        value.clone().try_into()
182    }
183}
184
185impl TryFrom<proto::blockchain::BlockHeader> for BlockHeader {
186    type Error = ConversionError;
187
188    fn try_from(header: proto::blockchain::BlockHeader) -> Result<Self, Self::Error> {
189        decode_block_version(header.version).context("version")?;
190
191        let decoder = header.decoder();
192        let block_num = required!(decoder, header.block_num).context("block_num")?;
193        let prev_block_commitment = required!(decoder, header.prev_block_commitment)?;
194        let chain_commitment = required!(decoder, header.chain_commitment)?;
195        let account_root = required!(decoder, header.account_root)?;
196        let nullifier_root = required!(decoder, header.nullifier_root)?;
197        let note_root = required!(decoder, header.note_root)?;
198        let tx_commitment = required!(decoder, header.tx_commitment)?;
199        let validator_config = required!(decoder, header.validator_config)?;
200        let fee_parameters = required!(decoder, header.fee_parameters)?;
201        let protocol_config_commitment = required!(decoder, header.protocol_config_commitment)?;
202        let next_protocol_config = header
203            .next_protocol_config
204            .map(TryInto::try_into)
205            .transpose()
206            .context("next_protocol_config")?;
207
208        Ok(BlockHeader::new(
209            prev_block_commitment,
210            block_num,
211            chain_commitment,
212            account_root,
213            nullifier_root,
214            note_root,
215            tx_commitment,
216            validator_config,
217            fee_parameters,
218            protocol_config_commitment,
219            next_protocol_config,
220            header.timestamp,
221        ))
222    }
223}
224
225fn decode_block_version(version: i32) -> Result<(), ConversionError> {
226    match proto::blockchain::BlockVersion::try_from(version) {
227        Ok(proto::blockchain::BlockVersion::V1) => Ok(()),
228        Ok(proto::blockchain::BlockVersion::Unspecified) => {
229            Err(ConversionError::message("block header version is unspecified"))
230        },
231        Err(error) => Err(ConversionError::with_source(
232            format!("unknown block header version {version}"),
233            error,
234        )),
235    }
236}
237
238// BLOCK BODY
239// ================================================================================================
240
241impl From<&BlockBody> for proto::blockchain::BlockBody {
242    fn from(body: &BlockBody) -> Self {
243        Self {
244            updated_accounts: body.updated_accounts().iter().map(Into::into).collect(),
245            output_note_batches: body.output_note_batches().iter().map(Into::into).collect(),
246            created_nullifiers: body
247                .created_nullifiers()
248                .iter()
249                .map(|nullifier| nullifier.as_word().into())
250                .collect(),
251            transactions: body.transactions().as_slice().iter().map(Into::into).collect(),
252        }
253    }
254}
255
256impl From<BlockBody> for proto::blockchain::BlockBody {
257    fn from(body: BlockBody) -> Self {
258        (&body).into()
259    }
260}
261
262impl TryFrom<proto::blockchain::BlockBody> for BlockBody {
263    type Error = ConversionError;
264
265    fn try_from(value: proto::blockchain::BlockBody) -> Result<Self, Self::Error> {
266        let updated_accounts = value
267            .updated_accounts
268            .into_iter()
269            .enumerate()
270            .map(|(index, update)| {
271                BlockAccountUpdate::try_from(update).context(format!("updated_accounts[{index}]"))
272            })
273            .collect::<Result<Vec<_>, _>>()?;
274        let output_note_batches = value
275            .output_note_batches
276            .into_iter()
277            .enumerate()
278            .map(|(index, batch)| {
279                OutputNoteBatch::try_from(batch).context(format!("output_note_batches[{index}]"))
280            })
281            .collect::<Result<Vec<_>, _>>()?;
282        let created_nullifiers = value
283            .created_nullifiers
284            .into_iter()
285            .enumerate()
286            .map(|(index, nullifier)| {
287                Word::try_from(nullifier)
288                    .map(Nullifier::from_raw)
289                    .context(format!("created_nullifiers[{index}]"))
290            })
291            .collect::<Result<Vec<_>, _>>()?;
292        let transactions = value
293            .transactions
294            .into_iter()
295            .enumerate()
296            .map(|(index, transaction)| {
297                TransactionHeader::try_from(transaction).context(format!("transactions[{index}]"))
298            })
299            .collect::<Result<Vec<_>, _>>()?;
300
301        BlockBody::new(
302            updated_accounts,
303            output_note_batches,
304            created_nullifiers,
305            OrderedTransactionHeaders::new_unchecked(transactions),
306        )
307        .map_err(ConversionError::new)
308    }
309}
310
311impl TryFrom<&proto::blockchain::BlockBody> for BlockBody {
312    type Error = ConversionError;
313
314    fn try_from(value: &proto::blockchain::BlockBody) -> Result<Self, Self::Error> {
315        value.clone().try_into()
316    }
317}
318
319// BLOCK BODY COMPONENTS
320// ================================================================================================
321
322impl From<&BlockAccountUpdate> for proto::blockchain::BlockAccountUpdate {
323    fn from(update: &BlockAccountUpdate) -> Self {
324        Self {
325            account_id: Some(update.account_id().into()),
326            final_state_commitment: Some(update.final_state_commitment().into()),
327            details: Some(update.details().into()),
328        }
329    }
330}
331
332impl TryFrom<proto::blockchain::BlockAccountUpdate> for BlockAccountUpdate {
333    type Error = ConversionError;
334
335    fn try_from(update: proto::blockchain::BlockAccountUpdate) -> Result<Self, Self::Error> {
336        let decoder = update.decoder();
337        let account_id = required!(decoder, update.account_id)?;
338        let final_state_commitment = required!(decoder, update.final_state_commitment)?;
339        let details: AccountUpdateDetails = required!(decoder, update.details)?;
340        BlockAccountUpdate::new(account_id, final_state_commitment, details)
341            .map_err(ConversionError::new)
342    }
343}
344
345impl From<&(usize, OutputNote)> for proto::blockchain::IndexedOutputNote {
346    fn from((index, note): &(usize, OutputNote)) -> Self {
347        Self {
348            note_index_in_batch: u32::try_from(*index)
349                .expect("valid output note indices fit into u32"),
350            note: Some(note.into()),
351        }
352    }
353}
354
355impl TryFrom<proto::blockchain::IndexedOutputNote> for (usize, OutputNote) {
356    type Error = ConversionError;
357
358    fn try_from(note: proto::blockchain::IndexedOutputNote) -> Result<Self, Self::Error> {
359        let decoder = note.decoder();
360        let index = usize::try_from(note.note_index_in_batch).context("note_index_in_batch")?;
361        let output_note = required!(decoder, note.note)?;
362        Ok((index, output_note))
363    }
364}
365
366impl From<&OutputNoteBatch> for proto::blockchain::OutputNoteBatch {
367    fn from(batch: &OutputNoteBatch) -> Self {
368        Self {
369            notes: batch.iter().map(Into::into).collect(),
370        }
371    }
372}
373
374impl TryFrom<proto::blockchain::OutputNoteBatch> for OutputNoteBatch {
375    type Error = ConversionError;
376
377    fn try_from(batch: proto::blockchain::OutputNoteBatch) -> Result<Self, Self::Error> {
378        batch
379            .notes
380            .into_iter()
381            .enumerate()
382            .map(|(position, note)| {
383                <(usize, OutputNote)>::try_from(note).context(format!("notes[{position}]"))
384            })
385            .collect()
386    }
387}
388
389// SIGNED BLOCK
390// ================================================================================================
391
392impl From<&SignedBlock> for proto::blockchain::SignedBlock {
393    fn from(block: &SignedBlock) -> Self {
394        Self {
395            header: Some(block.header().into()),
396            body: Some(block.body().into()),
397            signatures: block.signatures().as_signatures().iter().map(Into::into).collect(),
398        }
399    }
400}
401
402impl From<SignedBlock> for proto::blockchain::SignedBlock {
403    fn from(block: SignedBlock) -> Self {
404        (&block).into()
405    }
406}
407
408impl TryFrom<proto::blockchain::SignedBlock> for SignedBlock {
409    type Error = ConversionError;
410
411    fn try_from(value: proto::blockchain::SignedBlock) -> Result<Self, Self::Error> {
412        let decoder = value.decoder();
413        let header = required!(decoder, value.header)?;
414        let body = required!(decoder, value.body)?;
415        let signatures = value
416            .signatures
417            .into_iter()
418            .map(Signature::try_from)
419            .collect::<Result<Vec<_>, _>>()
420            .context("signatures")?;
421        let signatures = BlockSignatures::new(signatures)
422            .map_err(ConversionError::new)
423            .context("signatures")?;
424
425        SignedBlock::new(header, body, signatures)
426            .map_err(ConversionError::new)
427            .context("body")
428    }
429}
430
431impl TryFrom<&proto::blockchain::SignedBlock> for SignedBlock {
432    type Error = ConversionError;
433
434    fn try_from(value: &proto::blockchain::SignedBlock) -> Result<Self, Self::Error> {
435        value.clone().try_into()
436    }
437}
438
439// VALIDATOR AND PROTOCOL CONFIGURATION
440// ================================================================================================
441
442impl TryFrom<proto::blockchain::ValidatorConfig> for ValidatorConfig {
443    type Error = ConversionError;
444
445    fn try_from(value: proto::blockchain::ValidatorConfig) -> Result<Self, Self::Error> {
446        let keys = value
447            .keys
448            .into_iter()
449            .enumerate()
450            .map(|(index, key)| PublicKey::try_from(key).context(format!("keys[{index}]")))
451            .collect::<Result<Vec<_>, _>>()?;
452        let quorum = u16::try_from(value.quorum).context("quorum")?;
453
454        Self::new(keys, quorum).map_err(ConversionError::new)
455    }
456}
457
458impl From<&ValidatorConfig> for proto::blockchain::ValidatorConfig {
459    fn from(value: &ValidatorConfig) -> Self {
460        Self {
461            keys: value.keys().iter().map(Into::into).collect(),
462            quorum: u32::from(value.quorum()),
463        }
464    }
465}
466
467impl From<ValidatorConfig> for proto::blockchain::ValidatorConfig {
468    fn from(value: ValidatorConfig) -> Self {
469        (&value).into()
470    }
471}
472
473impl TryFrom<proto::blockchain::NextProtocolConfig> for NextProtocolConfig {
474    type Error = ConversionError;
475
476    fn try_from(value: proto::blockchain::NextProtocolConfig) -> Result<Self, Self::Error> {
477        let decoder = value.decoder();
478        let effective_from = required!(decoder, value.effective_from)?;
479        let protocol_config = required!(decoder, value.protocol_config)?;
480
481        Self::new(effective_from, protocol_config).map_err(ConversionError::new)
482    }
483}
484
485impl From<&NextProtocolConfig> for proto::blockchain::NextProtocolConfig {
486    fn from(value: &NextProtocolConfig) -> Self {
487        Self {
488            effective_from: Some(value.effective_from().into()),
489            protocol_config: Some(value.protocol_config().into()),
490        }
491    }
492}
493
494impl From<NextProtocolConfig> for proto::blockchain::NextProtocolConfig {
495    fn from(value: NextProtocolConfig) -> Self {
496        (&value).into()
497    }
498}
499
500impl From<proto::blockchain::FeeParameters> for FeeParameters {
501    fn from(value: proto::blockchain::FeeParameters) -> Self {
502        Self::new(value.verification_base_fee)
503    }
504}
505
506impl From<&FeeParameters> for proto::blockchain::FeeParameters {
507    fn from(value: &FeeParameters) -> Self {
508        Self {
509            verification_base_fee: value.verification_base_fee(),
510        }
511    }
512}
513
514impl From<FeeParameters> for proto::blockchain::FeeParameters {
515    fn from(value: FeeParameters) -> Self {
516        (&value).into()
517    }
518}