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