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