Skip to main content

miden_client/sync/
tag.rs

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