Skip to main content

miden_objects/conversion/
transaction_inputs.rs

1use 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((&note.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}