miden_objects/conversion/
block.rs1use 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
32impl 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
47impl 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
148impl 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
238impl 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
319impl 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
389impl 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
439impl 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}