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