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}