miden_client/sync/
tag.rs

1use alloc::{string::ToString, vec::Vec};
2
3use miden_objects::{
4    NoteError,
5    account::{Account, AccountId},
6    note::{NoteExecutionMode, NoteId, NoteTag},
7};
8use miden_tx::utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable};
9use tracing::warn;
10
11use crate::{
12    Client,
13    errors::ClientError,
14    store::{InputNoteRecord, NoteRecordError},
15};
16
17/// Tag management methods
18impl Client {
19    /// Returns the list of note tags tracked by the client along with their source.
20    ///
21    /// When syncing the state with the node, these tags will be added to the sync request and
22    /// note-related information will be retrieved for notes that have matching tags.
23    ///  The source of the tag indicates its origin. It helps distinguish between:
24    ///  - Tags added manually by the user.
25    ///  - Tags automatically added by the client to track notes.
26    ///  - Tags added for accounts tracked by the client.
27    ///
28    /// Note: Tags for accounts that are being tracked by the client are managed automatically by
29    /// the client and don't need to be added here. That is, notes for managed accounts will be
30    /// retrieved automatically by the client when syncing.
31    pub async fn get_note_tags(&self) -> Result<Vec<NoteTagRecord>, ClientError> {
32        self.store.get_note_tags().await.map_err(Into::into)
33    }
34
35    /// Adds a note tag for the client to track. This tag's source will be marked as `User`.
36    pub async fn add_note_tag(&mut self, tag: NoteTag) -> Result<(), ClientError> {
37        match self
38            .store
39            .add_note_tag(NoteTagRecord { tag, source: NoteTagSource::User })
40            .await
41            .map_err(Into::into)
42        {
43            Ok(true) => Ok(()),
44            Ok(false) => {
45                warn!("Tag {} is already being tracked", tag);
46                Ok(())
47            },
48            Err(err) => Err(err),
49        }
50    }
51
52    /// Removes a note tag for the client to track. Only tags added by the user can be removed.
53    pub async fn remove_note_tag(&mut self, tag: NoteTag) -> Result<(), ClientError> {
54        if self
55            .store
56            .remove_note_tag(NoteTagRecord { tag, source: NoteTagSource::User })
57            .await?
58            == 0
59        {
60            warn!("Tag {} wasn't being tracked", tag);
61        }
62
63        Ok(())
64    }
65}
66
67/// Represents a note tag of which the Store can keep track and retrieve.
68#[derive(Debug, PartialEq, Eq, Clone, Copy)]
69pub struct NoteTagRecord {
70    pub tag: NoteTag,
71    pub source: NoteTagSource,
72}
73
74/// Represents the source of the tag. This is used to differentiate between tags that are added by
75/// the user and tags that are added automatically by the client to track notes .
76#[derive(Debug, PartialEq, Eq, Clone, Copy)]
77pub enum NoteTagSource {
78    /// Tag for notes directed to a tracked account.
79    Account(AccountId),
80    /// Tag for tracked expected notes.
81    Note(NoteId),
82    /// Tag manually added by the user.
83    User,
84}
85
86impl NoteTagRecord {
87    pub fn with_note_source(tag: NoteTag, note_id: NoteId) -> Self {
88        Self {
89            tag,
90            source: NoteTagSource::Note(note_id),
91        }
92    }
93
94    pub fn with_account_source(tag: NoteTag, account_id: AccountId) -> Self {
95        Self {
96            tag,
97            source: NoteTagSource::Account(account_id),
98        }
99    }
100}
101
102impl Serializable for NoteTagSource {
103    fn write_into<W: ByteWriter>(&self, target: &mut W) {
104        match self {
105            NoteTagSource::Account(account_id) => {
106                target.write_u8(0);
107                account_id.write_into(target);
108            },
109            NoteTagSource::Note(note_id) => {
110                target.write_u8(1);
111                note_id.write_into(target);
112            },
113            NoteTagSource::User => target.write_u8(2),
114        }
115    }
116}
117
118impl Deserializable for NoteTagSource {
119    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
120        match source.read_u8()? {
121            0 => Ok(NoteTagSource::Account(AccountId::read_from(source)?)),
122            1 => Ok(NoteTagSource::Note(NoteId::read_from(source)?)),
123            2 => Ok(NoteTagSource::User),
124            val => Err(DeserializationError::InvalidValue(format!("Invalid tag source: {val}"))),
125        }
126    }
127}
128
129impl PartialEq<NoteTag> for NoteTagRecord {
130    fn eq(&self, other: &NoteTag) -> bool {
131        self.tag == *other
132    }
133}
134
135impl TryInto<NoteTagRecord> for &InputNoteRecord {
136    type Error = NoteRecordError;
137
138    fn try_into(self) -> Result<NoteTagRecord, Self::Error> {
139        match self.metadata() {
140            Some(metadata) => Ok(NoteTagRecord::with_note_source(metadata.tag(), self.id())),
141            None => Err(NoteRecordError::ConversionError(
142                "Input Note Record does not contain tag".to_string(),
143            )),
144        }
145    }
146}
147
148impl TryInto<NoteTagRecord> for &Account {
149    type Error = NoteError;
150    fn try_into(self) -> Result<NoteTagRecord, Self::Error> {
151        Ok(NoteTagRecord::with_account_source(
152            NoteTag::from_account_id(self.id(), NoteExecutionMode::Local)?,
153            self.id(),
154        ))
155    }
156}