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