Skip to main content

miden_client/rpc/domain/
transaction.rs

1use alloc::string::ToString;
2use alloc::vec::Vec;
3
4use miden_protocol::Word;
5use miden_protocol::account::AccountId;
6use miden_protocol::asset::FungibleAsset;
7use miden_protocol::block::BlockNumber;
8use miden_protocol::note::{NoteHeader, Nullifier};
9use miden_protocol::transaction::{
10    InputNoteCommitment,
11    InputNotes,
12    TransactionHeader,
13    TransactionId,
14};
15
16use crate::rpc::{RpcConversionError, RpcError, generated as proto};
17
18// TODO: Remove this when we turn on fees and the node informs the correct asset account ID
19
20/// A native asset faucet ID for use in testing scenarios.
21pub const ACCOUNT_ID_NATIVE_ASSET_FAUCET: u128 = 0xab00_0000_0000_cd20_0000_ac00_0000_de00_u128;
22
23// INTO TRANSACTION ID
24// ================================================================================================
25
26impl TryFrom<proto::primitives::Digest> for TransactionId {
27    type Error = RpcConversionError;
28
29    fn try_from(value: proto::primitives::Digest) -> Result<Self, Self::Error> {
30        let word: Word = value.try_into()?;
31        Ok(Self::from_raw(word))
32    }
33}
34
35impl TryFrom<proto::transaction::TransactionId> for TransactionId {
36    type Error = RpcConversionError;
37
38    fn try_from(value: proto::transaction::TransactionId) -> Result<Self, Self::Error> {
39        value
40            .id
41            .ok_or(RpcConversionError::MissingFieldInProtobufRepresentation {
42                entity: "TransactionId",
43                field_name: "id",
44            })?
45            .try_into()
46    }
47}
48
49impl From<TransactionId> for proto::transaction::TransactionId {
50    fn from(value: TransactionId) -> Self {
51        Self { id: Some(value.as_word().into()) }
52    }
53}
54
55// TRANSACTION INCLUSION
56// ================================================================================================
57
58/// Represents a transaction that was included in the node at a certain block.
59#[derive(Debug, Clone, PartialEq, Eq)]
60pub struct TransactionInclusion {
61    /// The transaction identifier.
62    pub transaction_id: TransactionId,
63    /// The number of the block in which the transaction was included.
64    pub block_num: BlockNumber,
65    /// The account that the transaction was executed against.
66    pub account_id: AccountId,
67}
68
69// TRANSACTIONS INFO
70// ================================================================================================
71
72/// Represent a list of transaction records that were included in a range of blocks.
73#[derive(Debug, Clone, PartialEq, Eq)]
74pub struct TransactionsInfo {
75    /// Current chain tip
76    pub chain_tip: BlockNumber,
77    /// The block number of the last check included in this response.
78    pub block_num: BlockNumber,
79    /// List of transaction records.
80    pub transaction_records: Vec<TransactionRecord>,
81}
82
83impl TryFrom<proto::rpc::SyncTransactionsResponse> for TransactionsInfo {
84    type Error = RpcError;
85
86    fn try_from(value: proto::rpc::SyncTransactionsResponse) -> Result<Self, Self::Error> {
87        let pagination_info = value.pagination_info.ok_or(
88            RpcConversionError::MissingFieldInProtobufRepresentation {
89                entity: "SyncTransactionsResponse",
90                field_name: "pagination_info",
91            },
92        )?;
93
94        let chain_tip = pagination_info.chain_tip.into();
95        let block_num = pagination_info.block_num.into();
96
97        let transaction_records = value
98            .transactions
99            .into_iter()
100            .map(TryInto::try_into)
101            .collect::<Result<Vec<TransactionRecord>, RpcError>>()?;
102
103        Ok(Self {
104            chain_tip,
105            block_num,
106            transaction_records,
107        })
108    }
109}
110
111// TRANSACTION RECORD
112// ================================================================================================
113
114/// Contains information about a transaction that got included in the chain at a specific block
115/// number.
116#[derive(Debug, Clone, PartialEq, Eq)]
117pub struct TransactionRecord {
118    /// Block number in which the transaction was included.
119    pub block_num: BlockNumber,
120    /// A transaction header.
121    pub transaction_header: TransactionHeader,
122}
123
124impl TryFrom<proto::rpc::TransactionRecord> for TransactionRecord {
125    type Error = RpcError;
126
127    fn try_from(value: proto::rpc::TransactionRecord) -> Result<Self, Self::Error> {
128        let block_num = value.block_num.into();
129        let transaction_header =
130            value.header.ok_or(RpcConversionError::MissingFieldInProtobufRepresentation {
131                entity: "TransactionRecord",
132                field_name: "transaction_header",
133            })?;
134
135        Ok(Self {
136            block_num,
137            transaction_header: transaction_header.try_into()?,
138        })
139    }
140}
141
142impl TryFrom<proto::transaction::TransactionHeader> for TransactionHeader {
143    type Error = RpcError;
144
145    fn try_from(value: proto::transaction::TransactionHeader) -> Result<Self, Self::Error> {
146        let account_id =
147            value
148                .account_id
149                .ok_or(RpcConversionError::MissingFieldInProtobufRepresentation {
150                    entity: "TransactionHeader",
151                    field_name: "account_id",
152                })?;
153
154        let initial_state_commitment = value.initial_state_commitment.ok_or(
155            RpcConversionError::MissingFieldInProtobufRepresentation {
156                entity: "TransactionHeader",
157                field_name: "initial_state_commitment",
158            },
159        )?;
160
161        let final_state_commitment = value.final_state_commitment.ok_or(
162            RpcConversionError::MissingFieldInProtobufRepresentation {
163                entity: "TransactionHeader",
164                field_name: "final_state_commitment",
165            },
166        )?;
167
168        let note_commitments = value
169            .nullifiers
170            .into_iter()
171            .map(|d| {
172                Nullifier::from_hex(&d.to_string())
173                    .map(InputNoteCommitment::from)
174                    .map_err(|e| RpcError::InvalidResponse(e.to_string()))
175            })
176            .collect::<Result<Vec<_>, _>>()?;
177        let input_notes = InputNotes::new_unchecked(note_commitments);
178
179        let output_notes = value
180            .output_notes
181            .into_iter()
182            .map(TryInto::try_into)
183            .collect::<Result<Vec<NoteHeader>, RpcError>>()?;
184
185        let transaction_header = TransactionHeader::new(
186            account_id.try_into()?,
187            initial_state_commitment.try_into()?,
188            final_state_commitment.try_into()?,
189            input_notes,
190            output_notes,
191            // TODO: handle this; should we open an issue in miden-node?
192            FungibleAsset::new(ACCOUNT_ID_NATIVE_ASSET_FAUCET.try_into().expect("is valid"), 0u64)
193                .unwrap(),
194        );
195        Ok(transaction_header)
196    }
197}
198
199impl TryFrom<proto::note::NoteSyncRecord> for NoteHeader {
200    type Error = RpcError;
201
202    fn try_from(value: proto::note::NoteSyncRecord) -> Result<Self, Self::Error> {
203        let note_id = value
204            .note_id
205            .ok_or(RpcConversionError::MissingFieldInProtobufRepresentation {
206                entity: "NoteSyncRecord",
207                field_name: "note_id",
208            })?
209            .try_into()?;
210
211        let note_metadata = value
212            .metadata
213            .ok_or(RpcConversionError::MissingFieldInProtobufRepresentation {
214                entity: "NoteSyncRecord",
215                field_name: "metadata",
216            })?
217            .try_into()?;
218
219        let note_header = Self::new(note_id, note_metadata);
220        Ok(note_header)
221    }
222}