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