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