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