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