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