Skip to main content

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`], [`NoteFilter`], `StorageFilter` to narrow down the set of returned
20//! transactions, account data, or notes. For more advanced usage, see the documentation of
21//! individual methods in the [`Store`] trait.
22
23use alloc::boxed::Box;
24use alloc::collections::{BTreeMap, BTreeSet};
25use alloc::string::{String, ToString};
26use alloc::vec::Vec;
27use core::fmt::Debug;
28
29use miden_protocol::account::{
30    Account,
31    AccountCode,
32    AccountHeader,
33    AccountId,
34    AccountStorage,
35    StorageMapKey,
36    StorageMapWitness,
37    StorageSlot,
38    StorageSlotContent,
39    StorageSlotName,
40};
41use miden_protocol::address::Address;
42use miden_protocol::asset::{Asset, AssetVault, AssetVaultKey, AssetWitness};
43use miden_protocol::block::{BlockHeader, BlockNumber};
44use miden_protocol::crypto::merkle::mmr::{Forest, InOrderIndex, MmrPeaks, PartialMmr};
45use miden_protocol::errors::AccountError;
46use miden_protocol::note::{NoteId, NoteScript, NoteTag, Nullifier};
47use miden_protocol::transaction::TransactionId;
48use miden_protocol::{Felt, Word};
49use miden_tx::utils::serde::{Deserializable, Serializable};
50
51use crate::note_transport::{NOTE_TRANSPORT_CURSOR_STORE_SETTING, NoteTransportCursor};
52use crate::rpc::{RPC_LIMITS_STORE_SETTING, RpcLimits};
53use crate::sync::{NoteTagRecord, StateSyncUpdate};
54use crate::transaction::{TransactionRecord, TransactionStatusVariant, TransactionStoreUpdate};
55
56/// Contains [`ClientDataStore`] to automatically implement [`DataStore`] for anything that
57/// implements [`Store`]. This isn't public because it's an implementation detail to instantiate the
58/// executor.
59///
60/// The user is tasked with creating a [`Store`] which the client will wrap into a
61/// [`ClientDataStore`] at creation time.
62pub(crate) mod data_store;
63
64mod errors;
65pub use errors::*;
66
67mod smt_forest;
68pub use smt_forest::AccountSmtForest;
69
70mod account;
71pub use account::{AccountRecord, AccountRecordData, AccountStatus, AccountUpdates};
72mod note_record;
73pub use note_record::{
74    InputNoteRecord,
75    InputNoteState,
76    NoteExportType,
77    NoteRecordError,
78    OutputNoteRecord,
79    OutputNoteState,
80    input_note_states,
81};
82
83// STORE TRAIT
84// ================================================================================================
85
86/// The [`Store`] trait exposes all methods that the client store needs in order to track the
87/// current state.
88///
89/// All update functions are implied to be atomic. That is, if multiple entities are meant to be
90/// updated as part of any single function and an error is returned during its execution, any
91/// changes that might have happened up to that point need to be rolled back and discarded.
92///
93/// Because the [`Store`]'s ownership is shared between the executor and the client, interior
94/// mutability is expected to be implemented, which is why all methods receive `&self` and
95/// not `&mut self`.
96#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
97#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
98pub trait Store: Send + Sync {
99    /// Returns an identifier for this store (e.g. `IndexedDB` database name, `SQLite` file path).
100    ///
101    /// This allows callers to retrieve store-specific identity information (such as the `IndexedDB`
102    /// database name) for standalone operations like `exportStore`/`importStore`, without making
103    /// import/export a responsibility of the client.
104    fn identifier(&self) -> &str;
105
106    /// Returns the current timestamp tracked by the store, measured in non-leap seconds since
107    /// Unix epoch. If the store implementation is incapable of tracking time, it should return
108    /// `None`.
109    ///
110    /// This method is used to add time metadata to notes' states. This information doesn't have a
111    /// functional impact on the client's operation, it's shown to the user for informational
112    /// purposes.
113    fn get_current_timestamp(&self) -> Option<u64>;
114
115    // TRANSACTIONS
116    // --------------------------------------------------------------------------------------------
117
118    /// Retrieves stored transactions, filtered by [`TransactionFilter`].
119    async fn get_transactions(
120        &self,
121        filter: TransactionFilter,
122    ) -> Result<Vec<TransactionRecord>, StoreError>;
123
124    /// Applies a transaction, atomically updating the current state based on the
125    /// [`TransactionStoreUpdate`].
126    ///
127    /// An update involves:
128    /// - Updating the stored account which is being modified by the transaction.
129    /// - Storing new input/output notes and payback note details as a result of the transaction
130    ///   execution.
131    /// - Updating the input notes that are being processed by the transaction.
132    /// - Inserting the new tracked tags into the store.
133    /// - Inserting the transaction into the store to track.
134    async fn apply_transaction(&self, tx_update: TransactionStoreUpdate) -> Result<(), StoreError>;
135
136    // NOTES
137    // --------------------------------------------------------------------------------------------
138
139    /// Retrieves the input notes from the store.
140    ///
141    /// When `filter` is [`NoteFilter::Consumed`], notes are sorted by their on-chain execution
142    /// order.
143    async fn get_input_notes(&self, filter: NoteFilter)
144    -> Result<Vec<InputNoteRecord>, StoreError>;
145
146    /// Retrieves the output notes from the store.
147    async fn get_output_notes(
148        &self,
149        filter: NoteFilter,
150    ) -> Result<Vec<OutputNoteRecord>, StoreError>;
151
152    /// Retrieves a single input note at the given offset from the filtered set for the given
153    /// consumer account. Optionally restricts to a block range via `block_start` and
154    /// `block_end`. Returns `None` when the offset is past the end of the matching notes.
155    ///
156    /// # Ordering
157    ///
158    /// Notes are sorted by their per-account on-chain execution order.
159    async fn get_input_note_by_offset(
160        &self,
161        filter: NoteFilter,
162        consumer: AccountId,
163        block_start: Option<BlockNumber>,
164        block_end: Option<BlockNumber>,
165        offset: u32,
166    ) -> Result<Option<InputNoteRecord>, StoreError>;
167
168    /// Returns the nullifiers of all unspent input notes.
169    ///
170    /// The default implementation of this method uses [`Store::get_input_notes`].
171    async fn get_unspent_input_note_nullifiers(&self) -> Result<Vec<Nullifier>, StoreError> {
172        self.get_input_notes(NoteFilter::Unspent)
173            .await?
174            .iter()
175            .map(|input_note| Ok(input_note.nullifier()))
176            .collect::<Result<Vec<_>, _>>()
177    }
178
179    /// Inserts the provided input notes into the database. If a note with the same ID already
180    /// exists, it will be replaced.
181    async fn upsert_input_notes(&self, notes: &[InputNoteRecord]) -> Result<(), StoreError>;
182
183    /// Returns the note script associated with the given root.
184    async fn get_note_script(&self, script_root: Word) -> Result<NoteScript, StoreError>;
185
186    /// Inserts the provided note scripts into the database. If a script with the same root already
187    /// exists, it will be replaced.
188    async fn upsert_note_scripts(&self, note_scripts: &[NoteScript]) -> Result<(), StoreError>;
189
190    // CHAIN DATA
191    // --------------------------------------------------------------------------------------------
192
193    /// Retrieves a vector of [`BlockHeader`]s filtered by the provided block numbers.
194    ///
195    /// The returned vector may not contain some or all of the requested block headers. It's up to
196    /// the callee to check whether all requested block headers were found.
197    ///
198    /// For each block header an additional boolean value is returned representing whether the block
199    /// contains notes relevant to the client.
200    async fn get_block_headers(
201        &self,
202        block_numbers: &BTreeSet<BlockNumber>,
203    ) -> Result<Vec<(BlockHeader, BlockRelevance)>, StoreError>;
204
205    /// Retrieves a [`BlockHeader`] corresponding to the provided block number and a boolean value
206    /// that represents whether the block contains notes relevant to the client. Returns `None` if
207    /// the block is not found.
208    ///
209    /// The default implementation of this method uses [`Store::get_block_headers`].
210    async fn get_block_header_by_num(
211        &self,
212        block_number: BlockNumber,
213    ) -> Result<Option<(BlockHeader, BlockRelevance)>, StoreError> {
214        self.get_block_headers(&[block_number].into_iter().collect())
215            .await
216            .map(|mut block_headers_list| block_headers_list.pop())
217    }
218
219    /// Retrieves a list of [`BlockHeader`] that include relevant notes to the client.
220    async fn get_tracked_block_headers(&self) -> Result<Vec<BlockHeader>, StoreError>;
221
222    /// Retrieves the block numbers of block headers that include relevant notes to the client.
223    ///
224    /// This is a lightweight alternative to [`Store::get_tracked_block_headers`] that avoids
225    /// deserializing full block headers when only the block numbers are needed.
226    async fn get_tracked_block_header_numbers(&self) -> Result<BTreeSet<usize>, StoreError>;
227
228    /// Retrieves all MMR authentication nodes based on [`PartialBlockchainFilter`].
229    async fn get_partial_blockchain_nodes(
230        &self,
231        filter: PartialBlockchainFilter,
232    ) -> Result<BTreeMap<InOrderIndex, Word>, StoreError>;
233
234    /// Inserts blockchain MMR authentication nodes.
235    ///
236    /// In the case where the [`InOrderIndex`] already exists on the table, the insertion is
237    /// ignored.
238    async fn insert_partial_blockchain_nodes(
239        &self,
240        nodes: &[(InOrderIndex, Word)],
241    ) -> Result<(), StoreError>;
242
243    /// Returns peaks information from the blockchain by a specific block number.
244    ///
245    /// If there is no partial blockchain info stored for the provided block returns an empty
246    /// [`MmrPeaks`].
247    async fn get_partial_blockchain_peaks_by_block_num(
248        &self,
249        block_num: BlockNumber,
250    ) -> Result<MmrPeaks, StoreError>;
251
252    /// Inserts a block header into the store, alongside peaks information at the block's height.
253    ///
254    /// `has_client_notes` describes whether the block has relevant notes to the client; this means
255    /// the client might want to authenticate merkle paths based on this value.
256    /// If the block header exists and `has_client_notes` is `true` then the `has_client_notes`
257    /// column is updated to `true` to signify that the block now contains a relevant note.
258    async fn insert_block_header(
259        &self,
260        block_header: &BlockHeader,
261        partial_blockchain_peaks: MmrPeaks,
262        has_client_notes: bool,
263    ) -> Result<(), StoreError>;
264
265    /// Removes block headers that do not contain any client notes and aren't the genesis or last
266    /// block.
267    async fn prune_irrelevant_blocks(&self) -> Result<(), StoreError>;
268
269    /// Prunes historical account states for the specified account up to the given nonce.
270    ///
271    /// Deletes all historical entries with `replaced_at_nonce <= up_to_nonce` from the
272    /// historical tables (headers, storage, storage map entries, and assets).
273    ///
274    /// Also removes orphaned `account_code` entries that are no longer referenced by any
275    /// account header.
276    ///
277    /// Returns the total number of rows deleted, including historical entries and orphaned
278    /// account code.
279    async fn prune_account_history(
280        &self,
281        account_id: AccountId,
282        up_to_nonce: Felt,
283    ) -> Result<usize, StoreError>;
284
285    // ACCOUNT
286    // --------------------------------------------------------------------------------------------
287
288    /// Returns the account IDs of all accounts stored in the database.
289    async fn get_account_ids(&self) -> Result<Vec<AccountId>, StoreError>;
290
291    /// Returns a list of [`AccountHeader`] of all accounts stored in the database along with their
292    /// statuses.
293    ///
294    /// Said accounts' state is the state after the last performed sync.
295    async fn get_account_headers(&self) -> Result<Vec<(AccountHeader, AccountStatus)>, StoreError>;
296
297    /// Retrieves an [`AccountHeader`] object for the specified [`AccountId`] along with its status.
298    /// Returns `None` if the account is not found.
299    ///
300    /// Said account's state is the state according to the last sync performed.
301    async fn get_account_header(
302        &self,
303        account_id: AccountId,
304    ) -> Result<Option<(AccountHeader, AccountStatus)>, StoreError>;
305
306    /// Returns an [`AccountHeader`] corresponding to the stored account state that matches the
307    /// given commitment. If no account state matches the provided commitment, `None` is returned.
308    async fn get_account_header_by_commitment(
309        &self,
310        account_commitment: Word,
311    ) -> Result<Option<AccountHeader>, StoreError>;
312
313    /// Retrieves a full [`AccountRecord`] object, this contains the account's latest state along
314    /// with its status. Returns `None` if the account is not found.
315    async fn get_account(&self, account_id: AccountId)
316    -> Result<Option<AccountRecord>, StoreError>;
317
318    /// Retrieves the [`AccountCode`] for the specified account.
319    /// Returns `None` if the account is not found.
320    async fn get_account_code(
321        &self,
322        account_id: AccountId,
323    ) -> Result<Option<AccountCode>, StoreError>;
324
325    /// Inserts an [`Account`] to the store.
326    /// Receives an [`Address`] as the initial address to associate with the account. This address
327    /// will be tracked for incoming notes and its derived note tag will be monitored.
328    ///
329    /// # Errors
330    ///
331    /// - If the account is new and does not contain a seed
332    async fn insert_account(
333        &self,
334        account: &Account,
335        initial_address: Address,
336    ) -> Result<(), StoreError>;
337
338    /// Upserts the account code for a foreign account. This value will be used as a cache of known
339    /// script roots and added to the `GetForeignAccountCode` request.
340    async fn upsert_foreign_account_code(
341        &self,
342        account_id: AccountId,
343        code: AccountCode,
344    ) -> Result<(), StoreError>;
345
346    /// Retrieves the cached account code for various foreign accounts.
347    async fn get_foreign_account_code(
348        &self,
349        account_ids: Vec<AccountId>,
350    ) -> Result<BTreeMap<AccountId, AccountCode>, StoreError>;
351
352    /// Retrieves all [`Address`] objects that correspond to the provided account ID.
353    async fn get_addresses_by_account_id(
354        &self,
355        account_id: AccountId,
356    ) -> Result<Vec<Address>, StoreError>;
357
358    /// Updates an existing [`Account`] with a new state.
359    ///
360    /// # Errors
361    ///
362    /// Returns a `StoreError::AccountDataNotFound` if there is no account for the provided ID.
363    async fn update_account(&self, new_account_state: &Account) -> Result<(), StoreError>;
364
365    /// Adds an [`Address`] to an [`Account`], alongside its derived note tag.
366    async fn insert_address(
367        &self,
368        address: Address,
369        account_id: AccountId,
370    ) -> Result<(), StoreError>;
371
372    /// Removes an [`Address`] from an [`Account`], alongside its derived note tag.
373    async fn remove_address(
374        &self,
375        address: Address,
376        account_id: AccountId,
377    ) -> Result<(), StoreError>;
378
379    // SETTINGS
380    // --------------------------------------------------------------------------------------------
381
382    /// Adds a value to the `settings` table.
383    async fn set_setting(&self, key: String, value: Vec<u8>) -> Result<(), StoreError>;
384
385    /// Retrieves a value from the `settings` table.
386    async fn get_setting(&self, key: String) -> Result<Option<Vec<u8>>, StoreError>;
387
388    /// Deletes a value from the `settings` table.
389    async fn remove_setting(&self, key: String) -> Result<(), StoreError>;
390
391    /// Returns all the keys from the `settings` table.
392    async fn list_setting_keys(&self) -> Result<Vec<String>, StoreError>;
393
394    // SYNC
395    // --------------------------------------------------------------------------------------------
396
397    /// Returns the note tag records that the client is interested in.
398    async fn get_note_tags(&self) -> Result<Vec<NoteTagRecord>, StoreError>;
399
400    /// Returns the unique note tags (without source) that the client is interested in.
401    async fn get_unique_note_tags(&self) -> Result<BTreeSet<NoteTag>, StoreError> {
402        Ok(self.get_note_tags().await?.into_iter().map(|r| r.tag).collect())
403    }
404
405    /// Adds a note tag to the list of tags that the client is interested in.
406    ///
407    /// If the tag was already being tracked, returns false since no new tags were actually added.
408    /// Otherwise true.
409    async fn add_note_tag(&self, tag: NoteTagRecord) -> Result<bool, StoreError>;
410
411    /// Removes a note tag from the list of tags that the client is interested in.
412    ///
413    /// If the tag wasn't present in the store returns false since no tag was actually removed.
414    /// Otherwise returns true.
415    async fn remove_note_tag(&self, tag: NoteTagRecord) -> Result<usize, StoreError>;
416
417    /// Returns the block number of the last state sync block.
418    async fn get_sync_height(&self) -> Result<BlockNumber, StoreError>;
419
420    /// Applies the state sync update to the store. An update involves:
421    ///
422    /// - Inserting the new block header to the store alongside new MMR peaks information.
423    /// - Updating the corresponding tracked input/output notes. Consumed notes carry consumption
424    ///   metadata — `consumed_block_height`, `consumed_tx_order`, and `consumer_account_id` — in
425    ///   their note state. Implementations must persist these fields so that ordered queries (see
426    ///   [`Store::get_input_note_by_offset`]) work correctly.
427    /// - Removing note tags that are no longer relevant.
428    /// - Updating transactions in the store, marking as `committed` or `discarded`.
429    ///   - In turn, validating private account's state transitions. If a private account's
430    ///     commitment locally does not match the `StateSyncUpdate` information, the account may be
431    ///     locked.
432    /// - Storing new MMR authentication nodes.
433    /// - Updating the tracked public accounts.
434    async fn apply_state_sync(&self, state_sync_update: StateSyncUpdate) -> Result<(), StoreError>;
435
436    // TRANSPORT
437    // --------------------------------------------------------------------------------------------
438
439    /// Gets the note transport cursor.
440    ///
441    /// This is used to reduce the number of fetched notes from the note transport network.
442    /// If no cursor exists, initializes it to 0.
443    async fn get_note_transport_cursor(&self) -> Result<NoteTransportCursor, StoreError> {
444        let cursor_bytes = if let Some(bytes) =
445            self.get_setting(NOTE_TRANSPORT_CURSOR_STORE_SETTING.into()).await?
446        {
447            bytes
448        } else {
449            // Lazy initialization: create cursor if not present
450            let initial = 0u64.to_be_bytes().to_vec();
451            self.set_setting(NOTE_TRANSPORT_CURSOR_STORE_SETTING.into(), initial.clone())
452                .await?;
453            initial
454        };
455        let array: [u8; 8] = cursor_bytes
456            .as_slice()
457            .try_into()
458            .map_err(|e: core::array::TryFromSliceError| StoreError::ParsingError(e.to_string()))?;
459        let cursor = u64::from_be_bytes(array);
460        Ok(cursor.into())
461    }
462
463    /// Updates the note transport cursor.
464    ///
465    /// This is used to track the last cursor position when fetching notes from the note transport
466    /// network.
467    async fn update_note_transport_cursor(
468        &self,
469        cursor: NoteTransportCursor,
470    ) -> Result<(), StoreError> {
471        let cursor_bytes = cursor.value().to_be_bytes().to_vec();
472        self.set_setting(NOTE_TRANSPORT_CURSOR_STORE_SETTING.into(), cursor_bytes)
473            .await?;
474        Ok(())
475    }
476
477    // RPC LIMITS
478    // --------------------------------------------------------------------------------------------
479
480    /// Gets persisted RPC limits. Returns `None` if not stored.
481    async fn get_rpc_limits(&self) -> Result<Option<RpcLimits>, StoreError> {
482        let Some(bytes) = self.get_setting(RPC_LIMITS_STORE_SETTING.into()).await? else {
483            return Ok(None);
484        };
485        let limits = RpcLimits::read_from_bytes(&bytes)?;
486        Ok(Some(limits))
487    }
488
489    /// Persists RPC limits to the store.
490    async fn set_rpc_limits(&self, limits: RpcLimits) -> Result<(), StoreError> {
491        self.set_setting(RPC_LIMITS_STORE_SETTING.into(), limits.to_bytes()).await
492    }
493
494    // PARTIAL MMR
495    // --------------------------------------------------------------------------------------------
496
497    /// Builds the current view of the chain's [`PartialMmr`]. Because we want to add all new
498    /// authentication nodes that could come from applying the MMR updates, we need to track all
499    /// known leaves thus far.
500    ///
501    /// The default implementation is based on [`Store::get_partial_blockchain_nodes`],
502    /// [`Store::get_partial_blockchain_peaks_by_block_num`] and [`Store::get_block_header_by_num`]
503    async fn get_current_partial_mmr(&self) -> Result<PartialMmr, StoreError> {
504        let current_block_num = self.get_sync_height().await?;
505
506        let current_peaks =
507            self.get_partial_blockchain_peaks_by_block_num(current_block_num).await?;
508
509        let (current_block, has_client_notes) = self
510            .get_block_header_by_num(current_block_num)
511            .await?
512            .ok_or(StoreError::BlockHeaderNotFound(current_block_num))?;
513
514        let mut current_partial_mmr = PartialMmr::from_peaks(current_peaks);
515        let has_client_notes = has_client_notes.into();
516        current_partial_mmr.add(current_block.commitment(), has_client_notes);
517
518        // Build tracked_leaves from blocks that have client notes.
519        let mut tracked_leaves = self.get_tracked_block_header_numbers().await?;
520
521        // Also track the latest leaf if it is relevant (it has client notes) _and_ the forest
522        // actually has a single leaf tree bit.
523        if has_client_notes && current_partial_mmr.forest().has_single_leaf_tree() {
524            let latest_leaf = current_partial_mmr.forest().num_leaves().saturating_sub(1);
525            tracked_leaves.insert(latest_leaf);
526        }
527
528        let tracked_nodes = self
529            .get_partial_blockchain_nodes(PartialBlockchainFilter::Forest(
530                current_partial_mmr.forest(),
531            ))
532            .await?;
533
534        let current_partial_mmr =
535            PartialMmr::from_parts(current_partial_mmr.peaks(), tracked_nodes, tracked_leaves)?;
536
537        Ok(current_partial_mmr)
538    }
539
540    // ACCOUNT VAULT AND STORE
541    // --------------------------------------------------------------------------------------------
542
543    /// Retrieves the asset vault for a specific account.
544    async fn get_account_vault(&self, account_id: AccountId) -> Result<AssetVault, StoreError>;
545
546    /// Retrieves a specific asset (by vault key) from the account's vault along with its Merkle
547    /// witness.
548    ///
549    /// The default implementation of this method uses [`Store::get_account_vault`].
550    async fn get_account_asset(
551        &self,
552        account_id: AccountId,
553        vault_key: AssetVaultKey,
554    ) -> Result<Option<(Asset, AssetWitness)>, StoreError> {
555        let vault = self.get_account_vault(account_id).await?;
556        let Some(asset) = vault.assets().find(|a| a.vault_key() == vault_key) else {
557            return Ok(None);
558        };
559
560        let witness = AssetWitness::new(vault.open(vault_key).into())?;
561
562        Ok(Some((asset, witness)))
563    }
564
565    /// Retrieves the storage for a specific account.
566    ///
567    /// Can take an optional map root to retrieve only part of the storage,
568    /// If it does, it will either return an account storage with a single
569    /// slot (the one requested), or an error if not found.
570    async fn get_account_storage(
571        &self,
572        account_id: AccountId,
573        filter: AccountStorageFilter,
574    ) -> Result<AccountStorage, StoreError>;
575
576    /// Retrieves a storage slot value by name.
577    ///
578    /// For `Value` slots, returns the stored word.
579    /// For `Map` slots, returns the map root.
580    ///
581    /// The default implementation of this method uses [`Store::get_account_storage`].
582    async fn get_account_storage_item(
583        &self,
584        account_id: AccountId,
585        slot_name: StorageSlotName,
586    ) -> Result<Word, StoreError> {
587        let storage = self
588            .get_account_storage(account_id, AccountStorageFilter::SlotName(slot_name.clone()))
589            .await?;
590        storage
591            .get(&slot_name)
592            .map(StorageSlot::value)
593            .ok_or(StoreError::AccountError(AccountError::StorageSlotNameNotFound { slot_name }))
594    }
595
596    /// Retrieves a specific item from the account's storage map along with its Merkle proof.
597    ///
598    /// The default implementation of this method uses [`Store::get_account_storage`].
599    async fn get_account_map_item(
600        &self,
601        account_id: AccountId,
602        slot_name: StorageSlotName,
603        key: StorageMapKey,
604    ) -> Result<(Word, StorageMapWitness), StoreError> {
605        let storage = self
606            .get_account_storage(account_id, AccountStorageFilter::SlotName(slot_name.clone()))
607            .await?;
608        match storage.get(&slot_name).map(StorageSlot::content) {
609            Some(StorageSlotContent::Map(map)) => {
610                let value = map.get(&key);
611                let witness = map.open(&key);
612
613                Ok((value, witness))
614            },
615            Some(_) => Err(StoreError::AccountError(AccountError::StorageSlotNotMap(slot_name))),
616            None => {
617                Err(StoreError::AccountError(AccountError::StorageSlotNameNotFound { slot_name }))
618            },
619        }
620    }
621
622    // PARTIAL ACCOUNTS
623    // --------------------------------------------------------------------------------------------
624
625    /// Retrieves an [`AccountRecord`] object, this contains the account's latest partial
626    /// state along with its status. Returns `None` if the partial account is not found.
627    async fn get_minimal_partial_account(
628        &self,
629        account_id: AccountId,
630    ) -> Result<Option<AccountRecord>, StoreError>;
631}
632
633// PARTIAL BLOCKCHAIN NODE FILTER
634// ================================================================================================
635
636/// Filters for searching specific MMR nodes.
637// TODO: Should there be filters for specific blocks instead of nodes?
638pub enum PartialBlockchainFilter {
639    /// Return all nodes.
640    All,
641    /// Filter by the specified in-order indices.
642    List(Vec<InOrderIndex>),
643    /// Return nodes with in-order indices within the specified forest.
644    Forest(Forest),
645}
646
647// TRANSACTION FILTERS
648// ================================================================================================
649
650/// Filters for narrowing the set of transactions returned by the client's store.
651#[derive(Debug, Clone)]
652pub enum TransactionFilter {
653    /// Return all transactions.
654    All,
655    /// Filter by transactions that haven't yet been committed to the blockchain as per the last
656    /// sync.
657    Uncommitted,
658    /// Return a list of the transaction that matches the provided [`TransactionId`]s.
659    Ids(Vec<TransactionId>),
660    /// Return a list of the expired transactions that were executed before the provided
661    /// [`BlockNumber`]. Transactions created after the provided block number are not
662    /// considered.
663    ///
664    /// A transaction is considered expired if is uncommitted and the transaction's block number
665    /// is less than the provided block number.
666    ExpiredBefore(BlockNumber),
667}
668
669// TRANSACTIONS FILTER HELPERS
670// ================================================================================================
671
672impl TransactionFilter {
673    /// Returns a [String] containing the query for this Filter.
674    pub fn to_query(&self) -> String {
675        const QUERY: &str = "SELECT tx.id, script.script, tx.details, tx.status \
676            FROM transactions AS tx LEFT JOIN transaction_scripts AS script ON tx.script_root = script.script_root";
677        match self {
678            TransactionFilter::All => QUERY.to_string(),
679            TransactionFilter::Uncommitted => format!(
680                "{QUERY} WHERE tx.status_variant = {}",
681                TransactionStatusVariant::Pending as u8,
682            ),
683            TransactionFilter::Ids(_) => {
684                // Use SQLite's array parameter binding
685                format!("{QUERY} WHERE tx.id IN rarray(?)")
686            },
687            TransactionFilter::ExpiredBefore(block_num) => {
688                format!(
689                    "{QUERY} WHERE tx.block_num < {} AND tx.status_variant != {} AND tx.status_variant != {}",
690                    block_num.as_u32(),
691                    TransactionStatusVariant::Discarded as u8,
692                    TransactionStatusVariant::Committed as u8
693                )
694            },
695        }
696    }
697}
698
699// NOTE FILTER
700// ================================================================================================
701
702/// Filters for narrowing the set of notes returned by the client's store.
703#[derive(Debug, Clone)]
704pub enum NoteFilter {
705    /// Return a list of all notes ([`InputNoteRecord`] or [`OutputNoteRecord`]).
706    All,
707    /// Return a list of committed notes ([`InputNoteRecord`] or [`OutputNoteRecord`]). These
708    /// represent notes that the blockchain has included in a block.
709    Committed,
710    /// Filter by consumed notes ([`InputNoteRecord`] or [`OutputNoteRecord`]). notes that have
711    /// been used as inputs in transactions.
712    Consumed,
713    /// Return a list of expected notes ([`InputNoteRecord`] or [`OutputNoteRecord`]). These
714    /// represent notes for which the store doesn't have anchor data.
715    Expected,
716    /// Return a list containing any notes that match with the provided [`NoteId`] vector.
717    List(Vec<NoteId>),
718    /// Return a list containing any notes that match the provided [`Nullifier`] vector.
719    Nullifiers(Vec<Nullifier>),
720    /// Return a list of notes that are currently being processed. This filter doesn't apply to
721    /// output notes.
722    Processing,
723    /// Return a list containing the note that matches with the provided [`NoteId`]. The query will
724    /// return an error if the note isn't found.
725    Unique(NoteId),
726    /// Return a list containing notes that haven't been nullified yet, this includes expected,
727    /// committed, processing and unverified notes.
728    Unspent,
729    /// Return a list containing notes with unverified inclusion proofs. This filter doesn't apply
730    /// to output notes.
731    Unverified,
732}
733
734// BLOCK RELEVANCE
735// ================================================================================================
736
737/// Expresses metadata about the block header.
738#[derive(Debug, Clone)]
739pub enum BlockRelevance {
740    /// The block header includes notes that the client may consume.
741    HasNotes,
742    /// The block header does not contain notes relevant to the client.
743    Irrelevant,
744}
745
746impl From<BlockRelevance> for bool {
747    fn from(val: BlockRelevance) -> Self {
748        match val {
749            BlockRelevance::HasNotes => true,
750            BlockRelevance::Irrelevant => false,
751        }
752    }
753}
754
755impl From<bool> for BlockRelevance {
756    fn from(has_notes: bool) -> Self {
757        if has_notes {
758            BlockRelevance::HasNotes
759        } else {
760            BlockRelevance::Irrelevant
761        }
762    }
763}
764
765// STORAGE FILTER
766// ================================================================================================
767
768/// Filters for narrowing the storage slots returned by the client's store.
769#[derive(Debug, Clone)]
770pub enum AccountStorageFilter {
771    /// Return an [`AccountStorage`] with all available slots.
772    All,
773    /// Return an [`AccountStorage`] with a single slot that matches the provided [`Word`] map root.
774    Root(Word),
775    /// Return an [`AccountStorage`] with a single slot that matches the provided slot name.
776    SlotName(StorageSlotName),
777}