miden_client/rpc/domain/
note.rs

1use alloc::vec::Vec;
2
3use miden_objects::{
4    Digest, Felt,
5    block::BlockHeader,
6    crypto::merkle::MerklePath,
7    note::{Note, NoteExecutionHint, NoteId, NoteInclusionProof, NoteMetadata, NoteTag, NoteType},
8};
9
10use super::{MissingFieldHelper, RpcConversionError};
11use crate::rpc::{
12    RpcError,
13    generated::{note::NoteMetadata as ProtoNoteMetadata, responses::SyncNoteResponse},
14};
15
16impl TryFrom<ProtoNoteMetadata> for NoteMetadata {
17    type Error = RpcConversionError;
18
19    fn try_from(value: ProtoNoteMetadata) -> Result<Self, Self::Error> {
20        let sender = value
21            .sender
22            .ok_or_else(|| ProtoNoteMetadata::missing_field("Sender"))?
23            .try_into()?;
24        let note_type = NoteType::try_from(u64::from(value.note_type))?;
25        let tag = NoteTag::from(value.tag);
26        let execution_hint_tag = (value.execution_hint & 0xff) as u8;
27        let execution_hint_payload = ((value.execution_hint >> 8) & 0x00ff_ffff) as u32;
28        let execution_hint =
29            NoteExecutionHint::from_parts(execution_hint_tag, execution_hint_payload)?;
30
31        let aux = Felt::try_from(value.aux).map_err(|_| RpcConversionError::NotAValidFelt)?;
32
33        Ok(NoteMetadata::new(sender, note_type, tag, execution_hint, aux)?)
34    }
35}
36
37impl From<NoteMetadata> for ProtoNoteMetadata {
38    fn from(value: NoteMetadata) -> Self {
39        ProtoNoteMetadata {
40            sender: Some(value.sender().into()),
41            note_type: value.note_type() as u32,
42            tag: value.tag().into(),
43            execution_hint: value.execution_hint().into(),
44            aux: value.aux().into(),
45        }
46    }
47}
48
49// SYNC NOTE
50// ================================================================================================
51
52/// Represents a `SyncNoteResponse` with fields converted into domain types.
53#[derive(Debug)]
54pub struct NoteSyncInfo {
55    /// Number of the latest block in the chain.
56    pub chain_tip: u32,
57    /// Block header of the block with the first note matching the specified criteria.
58    pub block_header: BlockHeader,
59    /// Proof for block header's MMR with respect to the chain tip.
60    ///
61    /// More specifically, the full proof consists of `forest`, `position` and `path` components.
62    /// This value constitutes the `path`. The other two components can be obtained as follows:
63    ///    - `position` is simply `resopnse.block_header.block_num`.
64    ///    - `forest` is the same as `response.chain_tip + 1`.
65    pub mmr_path: MerklePath,
66    /// List of all notes together with the Merkle paths from `response.block_header.note_root`.
67    pub notes: Vec<CommittedNote>,
68}
69
70impl TryFrom<SyncNoteResponse> for NoteSyncInfo {
71    type Error = RpcError;
72
73    fn try_from(value: SyncNoteResponse) -> Result<Self, Self::Error> {
74        let chain_tip = value.chain_tip;
75
76        // Validate and convert block header
77        let block_header = value
78            .block_header
79            .ok_or(RpcError::ExpectedDataMissing("BlockHeader".into()))?
80            .try_into()?;
81
82        let mmr_path = value
83            .mmr_path
84            .ok_or(RpcError::ExpectedDataMissing("MmrPath".into()))?
85            .try_into()?;
86
87        // Validate and convert account note inclusions into an (AccountId, Digest) tuple
88        let mut notes = vec![];
89        for note in value.notes {
90            let note_id: Digest = note
91                .note_id
92                .ok_or(RpcError::ExpectedDataMissing("Notes.Id".into()))?
93                .try_into()?;
94
95            let note_id: NoteId = note_id.into();
96
97            let merkle_path = note
98                .merkle_path
99                .ok_or(RpcError::ExpectedDataMissing("Notes.MerklePath".into()))?
100                .try_into()?;
101
102            let metadata = note
103                .metadata
104                .ok_or(RpcError::ExpectedDataMissing("Metadata".into()))?
105                .try_into()?;
106
107            let committed_note = CommittedNote::new(
108                note_id,
109                u16::try_from(note.note_index).expect("note index out of range"),
110                merkle_path,
111                metadata,
112            );
113
114            notes.push(committed_note);
115        }
116
117        Ok(NoteSyncInfo { chain_tip, block_header, mmr_path, notes })
118    }
119}
120
121// COMMITTED NOTE
122// ================================================================================================
123
124/// Represents a committed note, returned as part of a `SyncStateResponse`.
125#[derive(Debug, Clone)]
126pub struct CommittedNote {
127    /// Note ID of the committed note.
128    note_id: NoteId,
129    /// Note index for the note merkle tree.
130    note_index: u16,
131    /// Merkle path for the note merkle tree up to the block's note root.
132    merkle_path: MerklePath,
133    /// Note metadata.
134    metadata: NoteMetadata,
135}
136
137impl CommittedNote {
138    pub fn new(
139        note_id: NoteId,
140        note_index: u16,
141        merkle_path: MerklePath,
142        metadata: NoteMetadata,
143    ) -> Self {
144        Self {
145            note_id,
146            note_index,
147            merkle_path,
148            metadata,
149        }
150    }
151
152    pub fn note_id(&self) -> &NoteId {
153        &self.note_id
154    }
155
156    pub fn note_index(&self) -> u16 {
157        self.note_index
158    }
159
160    pub fn merkle_path(&self) -> &MerklePath {
161        &self.merkle_path
162    }
163
164    pub fn metadata(&self) -> NoteMetadata {
165        self.metadata
166    }
167}
168
169// NETWORK NOTE
170// ================================================================================================
171
172/// Describes the possible responses from  the `GetNotesById` endpoint for a single note.
173#[allow(clippy::large_enum_variant)]
174pub enum NetworkNote {
175    /// Details for a private note only include its [`NoteMetadata`] and [`NoteInclusionProof`].
176    /// Other details needed to consume the note are expected to be stored locally, off-chain.
177    Private(NoteId, NoteMetadata, NoteInclusionProof),
178    /// Contains the full [`Note`] object alongside its [`NoteInclusionProof`].
179    Public(Note, NoteInclusionProof),
180}
181
182impl NetworkNote {
183    /// Returns the note's inclusion details.
184    pub fn inclusion_proof(&self) -> &NoteInclusionProof {
185        match self {
186            NetworkNote::Private(_, _, inclusion_proof)
187            | NetworkNote::Public(_, inclusion_proof) => inclusion_proof,
188        }
189    }
190
191    /// Returns the note's metadata.
192    pub fn metadata(&self) -> &NoteMetadata {
193        match self {
194            NetworkNote::Private(_, metadata, _) => metadata,
195            NetworkNote::Public(note, _) => note.metadata(),
196        }
197    }
198
199    /// Returns the note's ID.
200    pub fn id(&self) -> NoteId {
201        match self {
202            NetworkNote::Private(id, ..) => *id,
203            NetworkNote::Public(note, _) => note.id(),
204        }
205    }
206}