miden_client/store/
mod.rs

1//! Defines the storage interfaces used by the Miden client.
2//!
3//! It provides mechanisms for persisting and retrieving data, such as account states, transaction
4//! history, block headers, notes, and MMR nodes.
5//!
6//! ## Overview
7//!
8//! The storage module is central to the Miden client’s persistence layer. It defines the
9//! [`Store`] trait which abstracts over any concrete storage implementation. The trait exposes
10//! methods to (among others):
11//!
12//! - Retrieve and update transactions, notes, and accounts.
13//! - Store and query block headers along with MMR peaks and authentication nodes.
14//! - Manage note tags for synchronizing with the node.
15//!
16//! These are all used by the Miden client to provide transaction execution in the correct contexts.
17//!
18//! In addition to the main [`Store`] trait, the module provides types for filtering queries, such
19//! as [`TransactionFilter`] and [`NoteFilter`], to narrow down the set of returned transactions or
20//! notes. For more advanced usage, see the documentation of individual methods in the [`Store`]
21//! trait.
22
23use alloc::boxed::Box;
24use alloc::collections::{BTreeMap, BTreeSet};
25use alloc::vec::Vec;
26use core::fmt::Debug;
27
28use miden_objects::Word;
29use miden_objects::account::{Account, AccountCode, AccountHeader, AccountId};
30use miden_objects::block::{BlockHeader, BlockNumber};
31use miden_objects::crypto::merkle::{InOrderIndex, MmrPeaks};
32use miden_objects::note::{NoteId, NoteTag, Nullifier};
33use miden_objects::transaction::TransactionId;
34
35use crate::sync::{NoteTagRecord, StateSyncUpdate};
36use crate::transaction::{TransactionRecord, TransactionStoreUpdate};
37
38/// Contains [`ClientDataStore`] to automatically implement [`DataStore`] for anything that
39/// implements [`Store`]. This isn't public because it's an implementation detail to instantiate the
40/// executor.
41///
42/// The user is tasked with creating a [`Store`] which the client will wrap into a
43/// [`ClientDataStore`] at creation time.
44pub(crate) mod data_store;
45
46mod errors;
47pub use errors::*;
48
49#[cfg(all(feature = "sqlite", feature = "idxdb"))]
50compile_error!("features `sqlite` and `idxdb` are mutually exclusive");
51
52#[cfg(feature = "sqlite")]
53pub mod sqlite_store;
54
55#[cfg(feature = "idxdb")]
56pub mod web_store;
57
58mod account;
59pub use account::{AccountRecord, AccountStatus, AccountUpdates};
60mod note_record;
61pub use note_record::{
62    InputNoteRecord,
63    InputNoteState,
64    NoteExportType,
65    NoteRecordError,
66    OutputNoteRecord,
67    OutputNoteState,
68    input_note_states,
69};
70
71// STORE TRAIT
72// ================================================================================================
73
74/// The [`Store`] trait exposes all methods that the client store needs in order to track the
75/// current state.
76///
77/// All update functions are implied to be atomic. That is, if multiple entities are meant to be
78/// updated as part of any single function and an error is returned during its execution, any
79/// changes that might have happened up to that point need to be rolled back and discarded.
80///
81/// Because the [`Store`]'s ownership is shared between the executor and the client, interior
82/// mutability is expected to be implemented, which is why all methods receive `&self` and
83/// not `&mut self`.
84#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
85#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
86pub trait Store: Send + Sync {
87    /// Returns the current timestamp tracked by the store, measured in non-leap seconds since
88    /// Unix epoch. If the store implementation is incapable of tracking time, it should return
89    /// `None`.
90    ///
91    /// This method is used to add time metadata to notes' states. This information doesn't have a
92    /// functional impact on the client's operation, it's shown to the user for informational
93    /// purposes.
94    fn get_current_timestamp(&self) -> Option<u64>;
95
96    // TRANSACTIONS
97    // --------------------------------------------------------------------------------------------
98
99    /// Retrieves stored transactions, filtered by [`TransactionFilter`].
100    async fn get_transactions(
101        &self,
102        filter: TransactionFilter,
103    ) -> Result<Vec<TransactionRecord>, StoreError>;
104
105    /// Applies a transaction, atomically updating the current state based on the
106    /// [`TransactionStoreUpdate`].
107    ///
108    /// An update involves:
109    /// - Updating the stored account which is being modified by the transaction.
110    /// - Storing new input/output notes and payback note details as a result of the transaction
111    ///   execution.
112    /// - Updating the input notes that are being processed by the transaction.
113    /// - Inserting the new tracked tags into the store.
114    /// - Inserting the transaction into the store to track.
115    async fn apply_transaction(&self, tx_update: TransactionStoreUpdate) -> Result<(), StoreError>;
116
117    // NOTES
118    // --------------------------------------------------------------------------------------------
119
120    /// Retrieves the input notes from the store.
121    async fn get_input_notes(&self, filter: NoteFilter)
122    -> Result<Vec<InputNoteRecord>, StoreError>;
123
124    /// Retrieves the output notes from the store.
125    async fn get_output_notes(
126        &self,
127        filter: NoteFilter,
128    ) -> Result<Vec<OutputNoteRecord>, StoreError>;
129
130    /// Returns the nullifiers of all unspent input notes.
131    ///
132    /// The default implementation of this method uses [`Store::get_input_notes`].
133    async fn get_unspent_input_note_nullifiers(&self) -> Result<Vec<Nullifier>, StoreError> {
134        self.get_input_notes(NoteFilter::Unspent)
135            .await?
136            .iter()
137            .map(|input_note| Ok(input_note.nullifier()))
138            .collect::<Result<Vec<_>, _>>()
139    }
140
141    /// Inserts the provided input notes into the database. If a note with the same ID already
142    /// exists, it will be replaced.
143    async fn upsert_input_notes(&self, notes: &[InputNoteRecord]) -> Result<(), StoreError>;
144
145    // CHAIN DATA
146    // --------------------------------------------------------------------------------------------
147
148    /// Retrieves a vector of [`BlockHeader`]s filtered by the provided block numbers.
149    ///
150    /// The returned vector may not contain some or all of the requested block headers. It's up to
151    /// the callee to check whether all requested block headers were found.
152    ///
153    /// For each block header an additional boolean value is returned representing whether the block
154    /// contains notes relevant to the client.
155    async fn get_block_headers(
156        &self,
157        block_numbers: &BTreeSet<BlockNumber>,
158    ) -> Result<Vec<(BlockHeader, BlockRelevance)>, StoreError>;
159
160    /// Retrieves a [`BlockHeader`] corresponding to the provided block number and a boolean value
161    /// that represents whether the block contains notes relevant to the client. Returns `None` if
162    /// the block is not found.
163    ///
164    /// The default implementation of this method uses [`Store::get_block_headers`].
165    async fn get_block_header_by_num(
166        &self,
167        block_number: BlockNumber,
168    ) -> Result<Option<(BlockHeader, BlockRelevance)>, StoreError> {
169        self.get_block_headers(&[block_number].into_iter().collect())
170            .await
171            .map(|mut block_headers_list| block_headers_list.pop())
172    }
173
174    /// Retrieves a list of [`BlockHeader`] that include relevant notes to the client.
175    async fn get_tracked_block_headers(&self) -> Result<Vec<BlockHeader>, StoreError>;
176
177    /// Retrieves all MMR authentication nodes based on [`PartialBlockchainFilter`].
178    async fn get_partial_blockchain_nodes(
179        &self,
180        filter: PartialBlockchainFilter,
181    ) -> Result<BTreeMap<InOrderIndex, Word>, StoreError>;
182
183    /// Inserts blockchain MMR authentication nodes.
184    ///
185    /// In the case where the [`InOrderIndex`] already exists on the table, the insertion is
186    /// ignored.
187    async fn insert_partial_blockchain_nodes(
188        &self,
189        nodes: &[(InOrderIndex, Word)],
190    ) -> Result<(), StoreError>;
191
192    /// Returns peaks information from the blockchain by a specific block number.
193    ///
194    /// If there is no partial blockchain info stored for the provided block returns an empty
195    /// [`MmrPeaks`].
196    async fn get_partial_blockchain_peaks_by_block_num(
197        &self,
198        block_num: BlockNumber,
199    ) -> Result<MmrPeaks, StoreError>;
200
201    /// Inserts a block header into the store, alongside peaks information at the block's height.
202    ///
203    /// `has_client_notes` describes whether the block has relevant notes to the client; this means
204    /// the client might want to authenticate merkle paths based on this value.
205    /// If the block header exists and `has_client_notes` is `true` then the `has_client_notes`
206    /// column is updated to `true` to signify that the block now contains a relevant note.
207    async fn insert_block_header(
208        &self,
209        block_header: &BlockHeader,
210        partial_blockchain_peaks: MmrPeaks,
211        has_client_notes: bool,
212    ) -> Result<(), StoreError>;
213
214    /// Removes block headers that do not contain any client notes and aren't the genesis or last
215    /// block.
216    async fn prune_irrelevant_blocks(&self) -> Result<(), StoreError>;
217
218    // ACCOUNT
219    // --------------------------------------------------------------------------------------------
220
221    /// Returns the account IDs of all accounts stored in the database.
222    async fn get_account_ids(&self) -> Result<Vec<AccountId>, StoreError>;
223
224    /// Returns a list of [`AccountHeader`] of all accounts stored in the database along with their
225    /// statuses.
226    ///
227    /// Said accounts' state is the state after the last performed sync.
228    async fn get_account_headers(&self) -> Result<Vec<(AccountHeader, AccountStatus)>, StoreError>;
229
230    /// Retrieves an [`AccountHeader`] object for the specified [`AccountId`] along with its status.
231    /// Returns `None` if the account is not found.
232    ///
233    /// Said account's state is the state according to the last sync performed.
234    async fn get_account_header(
235        &self,
236        account_id: AccountId,
237    ) -> Result<Option<(AccountHeader, AccountStatus)>, StoreError>;
238
239    /// Returns an [`AccountHeader`] corresponding to the stored account state that matches the
240    /// given commitment. If no account state matches the provided commitment, `None` is returned.
241    async fn get_account_header_by_commitment(
242        &self,
243        account_commitment: Word,
244    ) -> Result<Option<AccountHeader>, StoreError>;
245
246    /// Retrieves a full [`AccountRecord`] object, this contains the account's latest state along
247    /// with its status. Returns `None` if the account is not found.
248    async fn get_account(&self, account_id: AccountId)
249    -> Result<Option<AccountRecord>, StoreError>;
250
251    /// Inserts an [`Account`] along with the seed used to create it.
252    async fn insert_account(
253        &self,
254        account: &Account,
255        account_seed: Option<Word>,
256    ) -> Result<(), StoreError>;
257
258    /// Upserts the account code for a foreign account. This value will be used as a cache of known
259    /// script roots and added to the `GetForeignAccountCode` request.
260    async fn upsert_foreign_account_code(
261        &self,
262        account_id: AccountId,
263        code: AccountCode,
264    ) -> Result<(), StoreError>;
265
266    /// Retrieves the cached account code for various foreign accounts.
267    async fn get_foreign_account_code(
268        &self,
269        account_ids: Vec<AccountId>,
270    ) -> Result<BTreeMap<AccountId, AccountCode>, StoreError>;
271
272    /// Updates an existing [`Account`] with a new state.
273    ///
274    /// # Errors
275    ///
276    /// Returns a `StoreError::AccountDataNotFound` if there is no account for the provided ID.
277    async fn update_account(&self, new_account_state: &Account) -> Result<(), StoreError>;
278
279    // SYNC
280    // --------------------------------------------------------------------------------------------
281
282    /// Returns the note tag records that the client is interested in.
283    async fn get_note_tags(&self) -> Result<Vec<NoteTagRecord>, StoreError>;
284
285    /// Returns the unique note tags (without source) that the client is interested in.
286    async fn get_unique_note_tags(&self) -> Result<BTreeSet<NoteTag>, StoreError> {
287        Ok(self.get_note_tags().await?.into_iter().map(|r| r.tag).collect())
288    }
289
290    /// Adds a note tag to the list of tags that the client is interested in.
291    ///
292    /// If the tag was already being tracked, returns false since no new tags were actually added.
293    /// Otherwise true.
294    async fn add_note_tag(&self, tag: NoteTagRecord) -> Result<bool, StoreError>;
295
296    /// Removes a note tag from the list of tags that the client is interested in.
297    ///
298    /// If the tag wasn't present in the store returns false since no tag was actually removed.
299    /// Otherwise returns true.
300    async fn remove_note_tag(&self, tag: NoteTagRecord) -> Result<usize, StoreError>;
301
302    /// Returns the block number of the last state sync block.
303    async fn get_sync_height(&self) -> Result<BlockNumber, StoreError>;
304
305    /// Applies the state sync update to the store. An update involves:
306    ///
307    /// - Inserting the new block header to the store alongside new MMR peaks information.
308    /// - Updating the corresponding tracked input/output notes.
309    /// - Removing note tags that are no longer relevant.
310    /// - Updating transactions in the store, marking as `committed` or `discarded`.
311    ///   - In turn, validating private account's state transitions. If a private account's
312    ///     commitment locally does not match the `StateSyncUpdate` information, the account may be
313    ///     locked.
314    /// - Storing new MMR authentication nodes.
315    /// - Updating the tracked public accounts.
316    async fn apply_state_sync(&self, state_sync_update: StateSyncUpdate) -> Result<(), StoreError>;
317}
318
319// PARTIAL BLOCKCHAIN NODE FILTER
320// ================================================================================================
321
322/// Filters for searching specific MMR nodes.
323// TODO: Should there be filters for specific blocks instead of nodes?
324pub enum PartialBlockchainFilter {
325    /// Return all nodes.
326    All,
327    /// Filter by the specified in-order indices.
328    List(Vec<InOrderIndex>),
329}
330
331// TRANSACTION FILTERS
332// ================================================================================================
333
334/// Filters for narrowing the set of transactions returned by the client's store.
335#[derive(Debug, Clone)]
336pub enum TransactionFilter {
337    /// Return all transactions.
338    All,
339    /// Filter by transactions that haven't yet been committed to the blockchain as per the last
340    /// sync.
341    Uncommitted,
342    /// Return a list of the transaction that matches the provided [`TransactionId`]s.
343    Ids(Vec<TransactionId>),
344    /// Return a list of the expired transactions that were executed before the provided
345    /// [`BlockNumber`]. Transactions created after the provided block number are not
346    /// considered.
347    ///
348    /// A transaction is considered expired if is uncommitted and the transaction's block number
349    /// is less than the provided block number.
350    ExpiredBefore(BlockNumber),
351}
352
353// NOTE FILTER
354// ================================================================================================
355
356/// Filters for narrowing the set of notes returned by the client's store.
357#[derive(Debug, Clone)]
358pub enum NoteFilter {
359    /// Return a list of all notes ([`InputNoteRecord`] or [`OutputNoteRecord`]).
360    All,
361    /// Return a list of committed notes ([`InputNoteRecord`] or [`OutputNoteRecord`]). These
362    /// represent notes that the blockchain has included in a block.
363    Committed,
364    /// Filter by consumed notes ([`InputNoteRecord`] or [`OutputNoteRecord`]). notes that have
365    /// been used as inputs in transactions.
366    Consumed,
367    /// Return a list of expected notes ([`InputNoteRecord`] or [`OutputNoteRecord`]). These
368    /// represent notes for which the store doesn't have anchor data.
369    Expected,
370    /// Return a list containing any notes that match with the provided [`NoteId`] vector.
371    List(Vec<NoteId>),
372    /// Return a list containing any notes that match the provided [`Nullifier`] vector.
373    Nullifiers(Vec<Nullifier>),
374    /// Return a list of notes that are currently being processed. This filter doesn't apply to
375    /// output notes.
376    Processing,
377    /// Return a list containing the note that matches with the provided [`NoteId`]. The query will
378    /// return an error if the note isn't found.
379    Unique(NoteId),
380    /// Return a list containing notes that haven't been nullified yet, this includes expected,
381    /// committed, processing and unverified notes.
382    Unspent,
383    /// Return a list containing notes with unverified inclusion proofs. This filter doesn't apply
384    /// to output notes.
385    Unverified,
386}
387
388// BLOCK RELEVANCE
389// ================================================================================================
390
391/// Expresses metadata about the block header.
392#[derive(Debug, Clone)]
393pub enum BlockRelevance {
394    /// The block header includes notes that the client may consume.
395    HasNotes,
396    /// The block header does not contain notes relevant to the client.
397    Irrelevant,
398}
399
400impl From<BlockRelevance> for bool {
401    fn from(val: BlockRelevance) -> Self {
402        match val {
403            BlockRelevance::HasNotes => true,
404            BlockRelevance::Irrelevant => false,
405        }
406    }
407}
408
409impl From<bool> for BlockRelevance {
410    fn from(has_notes: bool) -> Self {
411        if has_notes {
412            BlockRelevance::HasNotes
413        } else {
414            BlockRelevance::Irrelevant
415        }
416    }
417}