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