miden_client/rpc/domain/
transaction.rs1use alloc::collections::BTreeMap;
2use alloc::string::ToString;
3use alloc::vec::Vec;
4
5use miden_protocol::Word;
6use miden_protocol::block::BlockNumber;
7use miden_protocol::note::{NoteHeader, NoteId, NoteInclusionProof, Nullifier};
8use miden_protocol::transaction::{
9 InputNoteCommitment,
10 InputNotes,
11 TransactionHeader,
12 TransactionId,
13};
14
15use super::note::CommittedNote;
16use crate::rpc::{RpcConversionError, RpcError, generated as proto};
17
18impl TryFrom<proto::primitives::Digest> for TransactionId {
22 type Error = RpcConversionError;
23
24 fn try_from(value: proto::primitives::Digest) -> Result<Self, Self::Error> {
25 let word: Word = value.try_into()?;
26 Ok(Self::from_raw(word))
27 }
28}
29
30impl TryFrom<proto::transaction::TransactionId> for TransactionId {
31 type Error = RpcConversionError;
32
33 fn try_from(value: proto::transaction::TransactionId) -> Result<Self, Self::Error> {
34 value
35 .id
36 .ok_or(RpcConversionError::MissingFieldInProtobufRepresentation {
37 entity: "TransactionId",
38 field_name: "id",
39 })?
40 .try_into()
41 }
42}
43
44impl From<TransactionId> for proto::transaction::TransactionId {
45 fn from(value: TransactionId) -> Self {
46 Self { id: Some(value.as_word().into()) }
47 }
48}
49
50#[derive(Debug, Clone)]
56pub struct TransactionRecord {
57 pub block_num: BlockNumber,
59 pub transaction_header: TransactionHeader,
61 pub output_notes: Vec<CommittedNote>,
64 pub erased_output_notes: Vec<NoteHeader>,
66}
67
68impl TryFrom<proto::rpc::TransactionRecord> for TransactionRecord {
69 type Error = RpcError;
70
71 fn try_from(value: proto::rpc::TransactionRecord) -> Result<Self, Self::Error> {
72 let block_num = value.block_num.into();
73 let proto_header =
74 value.header.ok_or(RpcConversionError::MissingFieldInProtobufRepresentation {
75 entity: "TransactionRecord",
76 field_name: "transaction_header",
77 })?;
78
79 let (transaction_header, output_notes, erased_output_notes) =
80 convert_transaction_header(proto_header, value.output_note_proofs)?;
81
82 Ok(Self {
83 block_num,
84 transaction_header,
85 output_notes,
86 erased_output_notes,
87 })
88 }
89}
90
91fn convert_transaction_header(
99 value: proto::transaction::TransactionHeader,
100 output_note_proofs: Vec<proto::note::NoteInclusionInBlockProof>,
101) -> Result<(TransactionHeader, Vec<CommittedNote>, Vec<NoteHeader>), RpcError> {
102 let account_id =
103 value
104 .account_id
105 .ok_or(RpcConversionError::MissingFieldInProtobufRepresentation {
106 entity: "TransactionHeader",
107 field_name: "account_id",
108 })?;
109
110 let initial_state_commitment = value.initial_state_commitment.ok_or(
111 RpcConversionError::MissingFieldInProtobufRepresentation {
112 entity: "TransactionHeader",
113 field_name: "initial_state_commitment",
114 },
115 )?;
116
117 let final_state_commitment = value.final_state_commitment.ok_or(
118 RpcConversionError::MissingFieldInProtobufRepresentation {
119 entity: "TransactionHeader",
120 field_name: "final_state_commitment",
121 },
122 )?;
123
124 let note_commitments = value
125 .input_notes
126 .into_iter()
127 .map(|d| {
128 let word: Word = d
129 .nullifier
130 .ok_or(RpcError::ExpectedDataMissing("nullifier".into()))?
131 .try_into()
132 .map_err(|e: RpcConversionError| RpcError::InvalidResponse(e.to_string()))?;
133 Ok(InputNoteCommitment::from(Nullifier::from_raw(word)))
134 })
135 .collect::<Result<Vec<_>, RpcError>>()?;
136 let input_notes = InputNotes::new_unchecked(note_commitments);
137
138 let output_note_headers: Vec<NoteHeader> = value
140 .output_notes
141 .into_iter()
142 .map(|proto_header| {
143 proto_header
144 .try_into()
145 .map_err(|e: RpcConversionError| RpcError::InvalidResponse(e.to_string()))
146 })
147 .collect::<Result<Vec<_>, RpcError>>()?;
148
149 let mut proof_map: BTreeMap<NoteId, NoteInclusionProof> = BTreeMap::new();
151 for mut proto_proof in output_note_proofs {
152 let note_id: NoteId = proto_proof
153 .note_id
154 .take()
155 .ok_or(RpcError::ExpectedDataMissing("output_note_proofs.note_id".into()))?
156 .try_into()
157 .map_err(|e: RpcConversionError| RpcError::InvalidResponse(e.to_string()))?;
158 let inclusion_proof: NoteInclusionProof = proto_proof
159 .try_into()
160 .map_err(|e: RpcConversionError| RpcError::InvalidResponse(e.to_string()))?;
161 proof_map.insert(note_id, inclusion_proof);
162 }
163
164 let mut committed_output_notes = Vec::with_capacity(proof_map.len());
166 let mut erased_output_notes =
167 Vec::with_capacity(output_note_headers.len().saturating_sub(proof_map.len()));
168
169 for header in &output_note_headers {
170 let note_id = header.id();
171 if let Some(proof) = proof_map.remove(¬e_id) {
172 committed_output_notes.push(CommittedNote::new(note_id, *header.metadata(), proof));
173 } else {
174 erased_output_notes.push(*header);
175 }
176 }
177
178 let transaction_header = TransactionHeader::new(
179 account_id.try_into()?,
180 initial_state_commitment.try_into()?,
181 final_state_commitment.try_into()?,
182 input_notes,
183 output_note_headers,
184 );
185 Ok((transaction_header, committed_output_notes, erased_output_notes))
186}