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