miden_objects/conversion/
transaction.rs1use 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
26impl 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(¬e_arg_context)?;
90 let note_id = NoteId::from_raw(note_id_word);
91 let args = required!(decoder, note_arg.args).context(¬e_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
104impl 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
140impl 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
205impl 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
220impl 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
233impl 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
264impl 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
335impl 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(¬e)
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(¬e)
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(¬e)
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}