miden_objects/conversion/
transaction_inputs.rs1use alloc::collections::BTreeMap;
2use alloc::format;
3use alloc::string::String;
4use alloc::vec::Vec;
5
6use miden_protocol::account::{AccountCode, StorageSlotId, StorageSlotName};
7use miden_protocol::note::{Note, NoteId, NoteInclusionProof};
8use miden_protocol::transaction::{InputNote, InputNotes, TransactionInputs};
9
10use super::{MessageDecodeExt, required};
11use crate::{ConversionError, ConversionResultExt, proto};
12
13impl From<&InputNote> for proto::transaction::InputNote {
14 fn from(value: &InputNote) -> Self {
15 use proto::transaction::input_note::Note as ProtoInputNote;
16
17 let note = match value {
18 InputNote::Authenticated { note, proof } => {
19 ProtoInputNote::Authenticated(proto::transaction::AuthenticatedInputNote {
20 note: Some(note.clone().into()),
21 proof: Some((¬e.id(), proof).into()),
22 })
23 },
24 InputNote::Unauthenticated { note } => {
25 ProtoInputNote::Unauthenticated(note.clone().into())
26 },
27 };
28
29 Self { note: Some(note) }
30 }
31}
32
33impl TryFrom<proto::transaction::InputNote> for InputNote {
34 type Error = ConversionError;
35
36 fn try_from(value: proto::transaction::InputNote) -> Result<Self, Self::Error> {
37 use proto::transaction::input_note::Note as ProtoInputNote;
38
39 match value.note {
40 Some(ProtoInputNote::Authenticated(authenticated)) => {
41 decode_authenticated_input_note(authenticated).context("authenticated")
42 },
43 Some(ProtoInputNote::Unauthenticated(note)) => {
44 Note::try_from(note).map(InputNote::unauthenticated).context("unauthenticated")
45 },
46 None => Err(ConversionError::missing_field::<proto::transaction::InputNote>("note")),
47 }
48 }
49}
50
51fn decode_authenticated_input_note(
52 authenticated: proto::transaction::AuthenticatedInputNote,
53) -> Result<InputNote, ConversionError> {
54 let decoder = authenticated.decoder();
55 let note: Note = required!(decoder, authenticated.note)?;
56 let proof_message: proto::note::NoteInclusionProof = required!(decoder, authenticated.proof)?;
57 let (proof_note_id, proof): (NoteId, NoteInclusionProof) =
58 (&proof_message).try_into().context("proof")?;
59 if proof_note_id != note.id() {
60 return Err(ConversionError::message(format!(
61 "note ID mismatch: transmitted {proof_note_id}, decoded {}",
62 note.id()
63 ))
64 .context("proof.note_id"));
65 }
66
67 Ok(InputNote::authenticated(note, proof))
68}
69
70impl From<&InputNotes<InputNote>> for proto::transaction::InputNotes {
71 fn from(value: &InputNotes<InputNote>) -> Self {
72 Self {
73 notes: value.iter().map(Into::into).collect(),
74 }
75 }
76}
77
78impl TryFrom<proto::transaction::InputNotes> for InputNotes<InputNote> {
79 type Error = ConversionError;
80
81 fn try_from(value: proto::transaction::InputNotes) -> Result<Self, Self::Error> {
82 let notes = value
83 .notes
84 .into_iter()
85 .enumerate()
86 .map(|(index, note)| InputNote::try_from(note).context(format!("notes[{index}]")))
87 .collect::<Result<Vec<_>, _>>()?;
88
89 Self::new(notes).map_err(ConversionError::new)
90 }
91}
92
93impl From<&TransactionInputs> for proto::transaction::TransactionInputsV1 {
94 fn from(value: &TransactionInputs) -> Self {
95 Self {
96 account: Some(value.account().into()),
97 block_header: Some(value.block_header().into()),
98 protocol_config: Some(value.protocol_config().into()),
99 partial_blockchain: Some(value.blockchain().into()),
100 input_notes: Some(value.input_notes().into()),
101 tx_args: Some(value.tx_args().into()),
102 advice_inputs: Some(value.advice_inputs().into()),
103 foreign_account_code: value.foreign_account_code().iter().map(Into::into).collect(),
104 foreign_account_slot_names: value
105 .foreign_account_slot_names()
106 .iter()
107 .map(|(slot_id, slot_name)| proto::transaction::ForeignAccountSlotName {
108 slot_id: Some(slot_id.into()),
109 slot_name: String::from(slot_name.as_str()),
110 })
111 .collect(),
112 }
113 }
114}
115
116impl From<&TransactionInputs> for proto::transaction::TransactionInputs {
117 fn from(value: &TransactionInputs) -> Self {
118 use proto::transaction::transaction_inputs::Version;
119
120 Self { version: Some(Version::V1(value.into())) }
121 }
122}
123
124impl From<TransactionInputs> for proto::transaction::TransactionInputs {
125 fn from(value: TransactionInputs) -> Self {
126 (&value).into()
127 }
128}
129
130impl TryFrom<proto::transaction::TransactionInputsV1> for TransactionInputs {
131 type Error = ConversionError;
132
133 fn try_from(value: proto::transaction::TransactionInputsV1) -> Result<Self, Self::Error> {
134 let decoder = value.decoder();
135 let account = required!(decoder, value.account)?;
136 let block_header = required!(decoder, value.block_header)?;
137 let protocol_config = required!(decoder, value.protocol_config)?;
138 let partial_blockchain = required!(decoder, value.partial_blockchain)?;
139 let input_notes = required!(decoder, value.input_notes)?;
140 let tx_args = required!(decoder, value.tx_args)?;
141 let advice_inputs = required!(decoder, value.advice_inputs)?;
142 let foreign_account_code = value
143 .foreign_account_code
144 .into_iter()
145 .enumerate()
146 .map(|(index, code)| {
147 AccountCode::try_from(code).context(format!("foreign_account_code[{index}]"))
148 })
149 .collect::<Result<Vec<_>, _>>()?;
150
151 let mut foreign_account_slot_names = BTreeMap::new();
152 for (index, entry) in value.foreign_account_slot_names.into_iter().enumerate() {
153 let decoder = entry.decoder();
154 let slot_name_context = format!("foreign_account_slot_names[{index}]");
155 let slot_id: StorageSlotId =
156 required!(decoder, entry.slot_id).context(&slot_name_context)?;
157 let slot_name = StorageSlotName::new(entry.slot_name)
158 .map_err(ConversionError::new)
159 .context(format!("{slot_name_context}.slot_name"))?;
160 if slot_name.id() != slot_id {
161 return Err(ConversionError::message("storage slot ID does not match slot name")
162 .context(format!("{slot_name_context}.slot_id")));
163 }
164 if foreign_account_slot_names.insert(slot_id, slot_name).is_some() {
165 return Err(ConversionError::message("duplicate foreign account storage slot ID")
166 .context(format!("{slot_name_context}.slot_id")));
167 }
168 }
169
170 TransactionInputs::try_from_parts(
171 account,
172 block_header,
173 protocol_config,
174 partial_blockchain,
175 input_notes,
176 tx_args,
177 advice_inputs,
178 foreign_account_code,
179 foreign_account_slot_names,
180 )
181 .map_err(ConversionError::new)
182 }
183}
184
185impl TryFrom<proto::transaction::TransactionInputs> for TransactionInputs {
186 type Error = ConversionError;
187
188 fn try_from(value: proto::transaction::TransactionInputs) -> Result<Self, Self::Error> {
189 use proto::transaction::transaction_inputs::Version;
190
191 match value.version {
192 Some(Version::V1(v1)) => Self::try_from(v1).context("v1"),
193 None => Err(ConversionError::missing_field::<proto::transaction::TransactionInputs>(
194 "version",
195 )),
196 }
197 }
198}