Skip to main content

miden_client/note_transport/
mod.rs

1pub mod errors;
2pub mod generated;
3#[cfg(feature = "tonic")]
4pub mod grpc;
5
6use alloc::boxed::Box;
7use alloc::sync::Arc;
8use alloc::vec::Vec;
9
10use futures::Stream;
11use miden_protocol::address::Address;
12use miden_protocol::note::{Note, NoteDetails, NoteFile, NoteHeader, NoteTag};
13use miden_protocol::utils::Serializable;
14use miden_tx::auth::TransactionAuthenticator;
15use miden_tx::utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, SliceReader};
16
17pub use self::errors::NoteTransportError;
18use crate::{Client, ClientError};
19
20pub const NOTE_TRANSPORT_DEFAULT_ENDPOINT: &str = "https://transport.miden.io";
21pub const NOTE_TRANSPORT_CURSOR_STORE_SETTING: &str = "note_transport_cursor";
22
23/// Client note transport methods.
24impl<AUTH> Client<AUTH> {
25    /// Check if note transport connection is configured
26    pub fn is_note_transport_enabled(&self) -> bool {
27        self.note_transport_api.is_some()
28    }
29
30    /// Returns the Note Transport client
31    ///
32    /// Errors if the note transport is not configured.
33    pub(crate) fn get_note_transport_api(
34        &self,
35    ) -> Result<Arc<dyn NoteTransportClient>, NoteTransportError> {
36        self.note_transport_api.clone().ok_or(NoteTransportError::Disabled)
37    }
38
39    /// Send a note through the note transport network.
40    ///
41    /// The note will be end-to-end encrypted (unimplemented, currently plaintext)
42    /// using the provided recipient's `address` details.
43    /// The recipient will be able to retrieve this note through the note's [`NoteTag`].
44    pub async fn send_private_note(
45        &mut self,
46        note: Note,
47        _address: &Address,
48    ) -> Result<(), ClientError> {
49        let api = self.get_note_transport_api()?;
50
51        let header = note.header().clone();
52        let details = NoteDetails::from(note);
53        let details_bytes = details.to_bytes();
54        // e2ee impl hint:
55        // address.key().encrypt(details_bytes)
56        api.send_note(header, details_bytes).await?;
57
58        Ok(())
59    }
60}
61
62impl<AUTH> Client<AUTH>
63where
64    AUTH: TransactionAuthenticator + Sync + 'static,
65{
66    /// Fetch notes for tracked note tags.
67    ///
68    /// The client will query the configured note transport node for all tracked note tags.
69    /// To list tracked tags please use [`Client::get_note_tags`]. To add a new note tag please use
70    /// [`Client::add_note_tag`].
71    /// Only notes directed at your addresses will be stored and readable given the use of
72    /// end-to-end encryption (unimplemented).
73    /// Fetched notes will be stored into the client's store.
74    ///
75    /// An internal pagination mechanism is employed to reduce the number of downloaded notes.
76    /// To fetch the full history of private notes for the tracked tags, use
77    /// [`Client::fetch_all_private_notes`].
78    pub async fn fetch_private_notes(&mut self) -> Result<(), ClientError> {
79        // Unique tags
80        let note_tags = self.store.get_unique_note_tags().await?;
81        // Get global cursor
82        let cursor = self.store.get_note_transport_cursor().await?;
83
84        self.fetch_transport_notes(cursor, note_tags).await?;
85
86        Ok(())
87    }
88
89    /// Fetches all notes for tracked note tags.
90    ///
91    /// Similar to [`Client::fetch_private_notes`] however does not employ pagination,
92    /// fetching all notes stored in the note transport network for the tracked tags.
93    /// Please prefer using [`Client::fetch_private_notes`] to avoid downloading repeated notes.
94    pub async fn fetch_all_private_notes(&mut self) -> Result<(), ClientError> {
95        let note_tags = self.store.get_unique_note_tags().await?;
96
97        self.fetch_transport_notes(NoteTransportCursor::init(), note_tags).await?;
98
99        Ok(())
100    }
101
102    /// Fetch notes from the note transport network for provided note tags
103    ///
104    /// Pagination is employed, where only notes after the provided cursor are requested.
105    /// Downloaded notes are imported.
106    pub(crate) async fn fetch_transport_notes<I>(
107        &mut self,
108        cursor: NoteTransportCursor,
109        tags: I,
110    ) -> Result<(), ClientError>
111    where
112        I: IntoIterator<Item = NoteTag>,
113    {
114        let mut notes = Vec::new();
115        // Fetch notes
116        let (note_infos, rcursor) = self
117            .get_note_transport_api()?
118            .fetch_notes(&tags.into_iter().collect::<Vec<_>>(), cursor)
119            .await?;
120        for note_info in &note_infos {
121            // e2ee impl hint:
122            // for key in self.store.decryption_keys() try
123            // key.decrypt(details_bytes_encrypted)
124            let note = rejoin_note(&note_info.header, &note_info.details_bytes)?;
125            notes.push(note);
126        }
127
128        let sync_height = self.get_sync_height().await?;
129        // Import fetched notes
130        let mut note_requests = Vec::with_capacity(notes.len());
131        for note in notes {
132            let tag = note.metadata().tag();
133            let note_file = NoteFile::NoteDetails {
134                details: note.into(),
135                after_block_num: sync_height,
136                tag: Some(tag),
137            };
138            note_requests.push(note_file);
139        }
140        self.import_notes(&note_requests).await?;
141
142        // Update cursor (pagination)
143        self.store.update_note_transport_cursor(rcursor).await?;
144
145        Ok(())
146    }
147}
148
149/// Note transport cursor
150///
151/// Pagination integer used to reduce the number of fetched notes from the note transport network,
152/// avoiding duplicate downloads.
153#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord)]
154pub struct NoteTransportCursor(u64);
155
156/// Note Transport update
157pub struct NoteTransportUpdate {
158    /// Pagination cursor for next fetch
159    pub cursor: NoteTransportCursor,
160    /// Fetched notes
161    pub notes: Vec<Note>,
162}
163
164impl NoteTransportCursor {
165    pub fn new(value: u64) -> Self {
166        Self(value)
167    }
168
169    pub fn init() -> Self {
170        Self::new(0)
171    }
172
173    pub fn value(&self) -> u64 {
174        self.0
175    }
176}
177
178impl From<u64> for NoteTransportCursor {
179    fn from(value: u64) -> Self {
180        Self::new(value)
181    }
182}
183
184/// The main transport client trait for sending and receiving encrypted notes
185#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
186#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
187pub trait NoteTransportClient: Send + Sync {
188    /// Send a note with optionally encrypted details
189    async fn send_note(
190        &self,
191        header: NoteHeader,
192        details: Vec<u8>,
193    ) -> Result<(), NoteTransportError>;
194
195    /// Fetch notes for given tags
196    ///
197    /// Downloads notes for given tags.
198    /// Returns notes labelled after the provided cursor (pagination), and an updated cursor.
199    async fn fetch_notes(
200        &self,
201        tag: &[NoteTag],
202        cursor: NoteTransportCursor,
203    ) -> Result<(Vec<NoteInfo>, NoteTransportCursor), NoteTransportError>;
204
205    /// Stream notes for a given tag
206    async fn stream_notes(
207        &self,
208        tag: NoteTag,
209        cursor: NoteTransportCursor,
210    ) -> Result<Box<dyn NoteStream>, NoteTransportError>;
211}
212
213/// Stream trait for note streaming
214pub trait NoteStream:
215    Stream<Item = Result<Vec<NoteInfo>, NoteTransportError>> + Send + Unpin
216{
217}
218
219/// Information about a note fetched from the note transport network
220#[derive(Debug, Clone)]
221pub struct NoteInfo {
222    /// Note header
223    pub header: NoteHeader,
224    /// Note details, can be encrypted
225    pub details_bytes: Vec<u8>,
226}
227
228// SERIALIZATION
229// ================================================================================================
230
231impl Serializable for NoteInfo {
232    fn write_into<W: ByteWriter>(&self, target: &mut W) {
233        self.header.write_into(target);
234        self.details_bytes.write_into(target);
235    }
236}
237
238impl Deserializable for NoteInfo {
239    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
240        let header = NoteHeader::read_from(source)?;
241        let details_bytes = Vec::<u8>::read_from(source)?;
242        Ok(NoteInfo { header, details_bytes })
243    }
244}
245
246impl Serializable for NoteTransportCursor {
247    fn write_into<W: ByteWriter>(&self, target: &mut W) {
248        self.0.write_into(target);
249    }
250}
251
252impl Deserializable for NoteTransportCursor {
253    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
254        let value = u64::read_from(source)?;
255        Ok(Self::new(value))
256    }
257}
258
259fn rejoin_note(header: &NoteHeader, details_bytes: &[u8]) -> Result<Note, DeserializationError> {
260    let mut reader = SliceReader::new(details_bytes);
261    let details = NoteDetails::read_from(&mut reader)?;
262    let metadata = header.metadata().clone();
263    Ok(Note::new(details.assets().clone(), metadata, details.recipient().clone()))
264}