Skip to main content

miden_objects/conversion/
transaction.rs

1use alloc::collections::BTreeMap;
2use alloc::format;
3use alloc::sync::Arc;
4use alloc::vec::Vec;
5
6use miden_protocol::account::{AccountId, AccountUpdateDetails};
7use miden_protocol::note::{NoteHeader, NoteId, Nullifier};
8use miden_protocol::transaction::{
9    InputNoteCommitment,
10    InputNotes,
11    OutputNote,
12    PrivateOutputNote,
13    ProvenTransaction,
14    PublicOutputNote,
15    TransactionArgs,
16    TransactionHeader,
17    TransactionId,
18    TransactionScript,
19    TxAccountUpdate,
20};
21use miden_protocol::{MastForest, MastNodeId, Word};
22
23use super::{MessageDecodeExt, required};
24use crate::{ConversionError, ConversionResultExt, proto};
25
26// TRANSACTION ARGUMENTS
27// ================================================================================================
28
29impl From<&TransactionScript> for proto::transaction::TransactionScript {
30    fn from(value: &TransactionScript) -> Self {
31        Self {
32            entrypoint: value.entrypoint().into(),
33            mast: Some(value.mast().as_ref().into()),
34        }
35    }
36}
37
38impl TryFrom<proto::transaction::TransactionScript> for TransactionScript {
39    type Error = ConversionError;
40
41    fn try_from(value: proto::transaction::TransactionScript) -> Result<Self, Self::Error> {
42        let decoder = value.decoder();
43        let mast: MastForest = required!(decoder, value.mast)?;
44        let entrypoint = MastNodeId::from_u32_safe(value.entrypoint, &mast).map_err(|error| {
45            ConversionError::deserialization("transaction_script.entrypoint", error)
46        })?;
47
48        Self::from_parts(Arc::new(mast), entrypoint).map_err(ConversionError::new)
49    }
50}
51
52impl From<&TransactionArgs> for proto::transaction::TransactionArgs {
53    fn from(value: &TransactionArgs) -> Self {
54        Self {
55            tx_script: value.tx_script().map(Into::into),
56            tx_script_args: Some(value.tx_script_args().into()),
57            note_args: value
58                .note_args()
59                .iter()
60                .map(|(note_id, args)| proto::transaction::NoteArgument {
61                    note_id: Some(note_id.into()),
62                    args: Some(args.into()),
63                })
64                .collect(),
65            advice_inputs: Some(value.advice_inputs().into()),
66            auth_args: Some(value.auth_args().into()),
67        }
68    }
69}
70
71impl From<TransactionArgs> for proto::transaction::TransactionArgs {
72    fn from(value: TransactionArgs) -> Self {
73        (&value).into()
74    }
75}
76
77impl TryFrom<proto::transaction::TransactionArgs> for TransactionArgs {
78    type Error = ConversionError;
79
80    fn try_from(value: proto::transaction::TransactionArgs) -> Result<Self, Self::Error> {
81        let decoder = value.decoder();
82        let tx_script = value.tx_script.map(TryInto::try_into).transpose()?;
83        let tx_script_args = required!(decoder, value.tx_script_args)?;
84        let mut note_args = BTreeMap::new();
85        for (index, note_arg) in value.note_args.into_iter().enumerate() {
86            let decoder = note_arg.decoder();
87            let note_arg_context = format!("note_args[{index}]");
88            let note_id_word: Word =
89                required!(decoder, note_arg.note_id).context(&note_arg_context)?;
90            let note_id = NoteId::from_raw(note_id_word);
91            let args = required!(decoder, note_arg.args).context(&note_arg_context)?;
92            if note_args.insert(note_id, args).is_some() {
93                return Err(ConversionError::message("duplicate note argument")
94                    .context(format!("{note_arg_context}.note_id")));
95            }
96        }
97        let advice_inputs = required!(decoder, value.advice_inputs)?;
98        let auth_args = required!(decoder, value.auth_args)?;
99
100        Ok(Self::from_parts(tx_script, tx_script_args, note_args, advice_inputs, auth_args))
101    }
102}
103
104// TX ACCOUNT UPDATE
105// ================================================================================================
106
107impl From<&TxAccountUpdate> for proto::transaction::TxAccountUpdate {
108    fn from(value: &TxAccountUpdate) -> Self {
109        Self {
110            account_id: Some(value.account_id().into()),
111            initial_state_commitment: Some(value.initial_state_commitment().into()),
112            final_state_commitment: Some(value.final_state_commitment().into()),
113            account_patch_commitment: Some(value.account_patch_commitment().into()),
114            details: Some(value.details().into()),
115        }
116    }
117}
118
119impl TryFrom<proto::transaction::TxAccountUpdate> for TxAccountUpdate {
120    type Error = ConversionError;
121
122    fn try_from(value: proto::transaction::TxAccountUpdate) -> Result<Self, Self::Error> {
123        let decoder = value.decoder();
124        let account_id: AccountId = required!(decoder, value.account_id)?;
125        let initial_state_commitment = required!(decoder, value.initial_state_commitment)?;
126        let final_state_commitment = required!(decoder, value.final_state_commitment)?;
127        let account_patch_commitment = required!(decoder, value.account_patch_commitment)?;
128        let details: AccountUpdateDetails = required!(decoder, value.details)?;
129        Self::new(
130            account_id,
131            initial_state_commitment,
132            final_state_commitment,
133            account_patch_commitment,
134            details,
135        )
136        .map_err(ConversionError::new)
137    }
138}
139
140// PROVEN TRANSACTION
141// ================================================================================================
142
143impl From<&ProvenTransaction> for proto::transaction::ProvenTransaction {
144    fn from(value: &ProvenTransaction) -> Self {
145        Self {
146            account_update: Some(value.account_update().into()),
147            input_notes: value.input_notes().iter().map(Into::into).collect(),
148            output_notes: value.output_notes().iter().map(Into::into).collect(),
149            reference_block_num: Some(value.ref_block_num().into()),
150            reference_block_commitment: Some(value.ref_block_commitment().into()),
151            expiration_block_num: Some(value.expiration_block_num().into()),
152            proof: Some(value.proof().into()),
153        }
154    }
155}
156
157impl From<ProvenTransaction> for proto::transaction::ProvenTransaction {
158    fn from(value: ProvenTransaction) -> Self {
159        Self::from(&value)
160    }
161}
162
163impl TryFrom<proto::transaction::ProvenTransaction> for ProvenTransaction {
164    type Error = ConversionError;
165
166    fn try_from(value: proto::transaction::ProvenTransaction) -> Result<Self, Self::Error> {
167        let decoder = value.decoder();
168        let account_update = required!(decoder, value.account_update)?;
169        let input_notes = value
170            .input_notes
171            .into_iter()
172            .enumerate()
173            .map(|(index, note)| {
174                InputNoteCommitment::try_from(note).context(format!("input_notes[{index}]"))
175            })
176            .collect::<Result<Vec<_>, _>>()?;
177        let output_notes = value
178            .output_notes
179            .into_iter()
180            .enumerate()
181            .map(|(index, note)| {
182                OutputNote::try_from(note).context(format!("output_notes[{index}]"))
183            })
184            .collect::<Result<Vec<_>, _>>()?;
185        let reference_block_commitment = required!(decoder, value.reference_block_commitment)?;
186        let reference_block_num =
187            required!(decoder, value.reference_block_num).context("reference_block_num")?;
188        let expiration_block_num =
189            required!(decoder, value.expiration_block_num).context("expiration_block_num")?;
190        let proof = required!(decoder, value.proof)?;
191
192        Self::new(
193            account_update,
194            input_notes,
195            output_notes,
196            reference_block_num,
197            reference_block_commitment,
198            expiration_block_num,
199            proof,
200        )
201        .map_err(ConversionError::new)
202    }
203}
204
205// FROM TRANSACTION ID
206// ================================================================================================
207
208impl From<&TransactionId> for proto::transaction::TransactionId {
209    fn from(value: &TransactionId) -> Self {
210        proto::transaction::TransactionId { id: Some(value.as_word().into()) }
211    }
212}
213
214impl From<TransactionId> for proto::transaction::TransactionId {
215    fn from(value: TransactionId) -> Self {
216        (&value).into()
217    }
218}
219
220// INTO TRANSACTION ID
221// ================================================================================================
222
223impl TryFrom<proto::transaction::TransactionId> for TransactionId {
224    type Error = ConversionError;
225
226    fn try_from(value: proto::transaction::TransactionId) -> Result<Self, Self::Error> {
227        let decoder = value.decoder();
228        let id: Word = required!(decoder, value.id)?;
229        Ok(TransactionId::from_raw(id))
230    }
231}
232
233// INPUT NOTE COMMITMENT
234// ================================================================================================
235
236impl From<InputNoteCommitment> for proto::transaction::InputNoteCommitment {
237    fn from(value: InputNoteCommitment) -> Self {
238        Self::from(&value)
239    }
240}
241
242impl From<&InputNoteCommitment> for proto::transaction::InputNoteCommitment {
243    fn from(value: &InputNoteCommitment) -> Self {
244        Self {
245            nullifier: Some(value.nullifier().as_word().into()),
246            header: value.header().copied().map(Into::into),
247        }
248    }
249}
250
251impl TryFrom<proto::transaction::InputNoteCommitment> for InputNoteCommitment {
252    type Error = ConversionError;
253
254    fn try_from(value: proto::transaction::InputNoteCommitment) -> Result<Self, Self::Error> {
255        let decoder = value.decoder();
256        let nullifier = Nullifier::from_raw(required!(decoder, value.nullifier)?);
257
258        let header = value.header.map(TryInto::try_into).transpose().context("header")?;
259
260        Ok(InputNoteCommitment::from_parts_unchecked(nullifier, header))
261    }
262}
263
264// TRANSACTION HEADER
265// ================================================================================================
266
267impl From<&TransactionHeader> for proto::transaction::TransactionHeader {
268    fn from(header: &TransactionHeader) -> Self {
269        Self {
270            transaction_id: Some(header.id().into()),
271            account_id: Some(header.account_id().into()),
272            initial_state_commitment: Some(header.initial_state_commitment().into()),
273            final_state_commitment: Some(header.final_state_commitment().into()),
274            input_notes: header.input_notes().iter().map(Into::into).collect(),
275            output_notes: header.output_notes().iter().copied().map(Into::into).collect(),
276        }
277    }
278}
279
280impl From<TransactionHeader> for proto::transaction::TransactionHeader {
281    fn from(header: TransactionHeader) -> Self {
282        Self::from(&header)
283    }
284}
285
286impl TryFrom<proto::transaction::TransactionHeader> for TransactionHeader {
287    type Error = ConversionError;
288
289    fn try_from(header: proto::transaction::TransactionHeader) -> Result<Self, Self::Error> {
290        let decoder = header.decoder();
291        let transmitted_id = required!(decoder, header.transaction_id)?;
292        let account_id = required!(decoder, header.account_id)?;
293        let initial_state_commitment = required!(decoder, header.initial_state_commitment)?;
294        let final_state_commitment = required!(decoder, header.final_state_commitment)?;
295        let input_notes = header
296            .input_notes
297            .into_iter()
298            .enumerate()
299            .map(|(index, note)| {
300                InputNoteCommitment::try_from(note).context(format!("input_notes[{index}]"))
301            })
302            .collect::<Result<Vec<_>, _>>()?;
303        let input_notes = InputNotes::new(input_notes)
304            .map_err(ConversionError::new)
305            .context("input_notes")?;
306        let output_notes = header
307            .output_notes
308            .into_iter()
309            .enumerate()
310            .map(|(index, note)| {
311                NoteHeader::try_from(note).context(format!("output_notes[{index}]"))
312            })
313            .collect::<Result<Vec<_>, _>>()?;
314
315        let header = TransactionHeader::new(
316            account_id,
317            initial_state_commitment,
318            final_state_commitment,
319            input_notes,
320            output_notes,
321        )
322        .map_err(ConversionError::new)?;
323        if header.id() != transmitted_id {
324            return Err(ConversionError::message(format!(
325                "transaction ID mismatch: transmitted {transmitted_id}, recomputed {}",
326                header.id()
327            ))
328            .context("transaction_id"));
329        }
330
331        Ok(header)
332    }
333}
334
335// OUTPUT NOTES
336// ================================================================================================
337
338impl From<&PublicOutputNote> for proto::transaction::PublicOutputNote {
339    fn from(note: &PublicOutputNote) -> Self {
340        Self {
341            note: Some(note.as_note().clone().into()),
342        }
343    }
344}
345
346impl From<PublicOutputNote> for proto::transaction::PublicOutputNote {
347    fn from(note: PublicOutputNote) -> Self {
348        Self::from(&note)
349    }
350}
351
352impl TryFrom<proto::transaction::PublicOutputNote> for PublicOutputNote {
353    type Error = ConversionError;
354
355    fn try_from(note: proto::transaction::PublicOutputNote) -> Result<Self, Self::Error> {
356        let decoder = note.decoder();
357        let domain_note = required!(decoder, note.note)?;
358        PublicOutputNote::new(domain_note).map_err(ConversionError::new)
359    }
360}
361
362impl From<&PrivateOutputNote> for proto::transaction::PrivateOutputNote {
363    fn from(note: &PrivateOutputNote) -> Self {
364        Self {
365            header: Some((*note.header()).into()),
366            attachments: Some(note.attachments().into()),
367        }
368    }
369}
370
371impl From<PrivateOutputNote> for proto::transaction::PrivateOutputNote {
372    fn from(note: PrivateOutputNote) -> Self {
373        Self::from(&note)
374    }
375}
376
377impl TryFrom<proto::transaction::PrivateOutputNote> for PrivateOutputNote {
378    type Error = ConversionError;
379
380    fn try_from(note: proto::transaction::PrivateOutputNote) -> Result<Self, Self::Error> {
381        let decoder = note.decoder();
382        let header = required!(decoder, note.header)?;
383        let attachments = required!(decoder, note.attachments)?;
384        PrivateOutputNote::new(header, attachments).map_err(ConversionError::new)
385    }
386}
387
388impl From<&OutputNote> for proto::transaction::OutputNote {
389    fn from(note: &OutputNote) -> Self {
390        use proto::transaction::output_note::Note;
391
392        let note = match note {
393            OutputNote::Public(note) => Note::Public(note.into()),
394            OutputNote::Private(note) => Note::Private(note.into()),
395        };
396        Self { note: Some(note) }
397    }
398}
399
400impl From<OutputNote> for proto::transaction::OutputNote {
401    fn from(note: OutputNote) -> Self {
402        Self::from(&note)
403    }
404}
405
406impl TryFrom<proto::transaction::OutputNote> for OutputNote {
407    type Error = ConversionError;
408
409    fn try_from(note: proto::transaction::OutputNote) -> Result<Self, Self::Error> {
410        use proto::transaction::output_note::Note;
411
412        match note.note {
413            Some(Note::Public(note)) => note.try_into().map(OutputNote::Public).context("public"),
414            Some(Note::Private(note)) => {
415                note.try_into().map(OutputNote::Private).context("private")
416            },
417            None => Err(ConversionError::missing_field::<proto::transaction::OutputNote>("note")),
418        }
419    }
420}