Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
* [FEATURE][rust,store] Added `NoteFilter::ScriptRoots` to query input notes by their note script root directly at the store level, without loading and screening unrelated notes. The filter doesn't apply to output notes: querying output notes with it returns an empty list ([#2335](https://github.com/0xMiden/rust-sdk/pull/2335)).
* [rust] Added `PartialBlockchainUpdates::block_headers_to_store`, which narrows the staged headers to the ones a sync must persist: those marked as relevant, genesis, and the block at the sync height. `block_headers` still yields all staged headers ([#2297](https://github.com/0xMiden/rust-sdk/pull/2297)).
* [rust] State sync now authenticates every relevant note block but only persists block headers and MMR authentication nodes for blocks containing notes that remain unspent or that a `NoteObserver` explicitly marks as relevant ([#2297](https://github.com/0xMiden/rust-sdk/pull/2297)).
* [rust,store] `BatchBuilder` now stacks in-batch account state as a `PartialAccount` updated with each transaction's `AccountPatch` instead of reconstructing the full `Account` after every push. Witnesses for keys no prior in-batch transaction touched are served by the new `Store::vault_asset_witnesses_after_patch` and `Store::storage_map_witness_after_patch` methods, which stage the accumulated patch onto the store's committed Merkle forest without persisting it (default implementations return `UnsupportedOperation`; `SqliteStore` implements them) ([#2277](https://github.com/0xMiden/rust-sdk/pull/2277)).
* Improved the output of the `miden-client init` command when a configuration already exists ([#](https://github.com/0xMiden/rust-sdk/pull/2357)).

## 0.16.0-alpha.1 (2026-07-17)
Expand Down
10 changes: 10 additions & 0 deletions crates/rust-client/src/account/account_reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use alloc::vec::Vec;
use miden_protocol::account::{
AccountHeader,
AccountId,
PartialAccount,
StorageMapKey,
StorageMapWitness,
StorageSlotName,
Expand Down Expand Up @@ -100,6 +101,15 @@ impl AccountReader {
.ok_or(ClientError::AccountDataNotFound(self.account_id))
}

/// Retrieves the minimal partial account representation for this account.
pub(crate) async fn partial_account(&self) -> Result<PartialAccount, ClientError> {
self.store
.get_minimal_partial_account(self.account_id)
.await?
.ok_or(ClientError::AccountDataNotFound(self.account_id))?
.try_into()
}

/// Retrieves the addresses associated with this account.
pub async fn addresses(&self) -> Result<Vec<Address>, ClientError> {
self.store
Expand Down
10 changes: 9 additions & 1 deletion crates/rust-client/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@ use core::fmt;
use miden_protocol::Word;
use miden_protocol::account::AccountId;
use miden_protocol::crypto::merkle::MerkleError;
pub use miden_protocol::errors::{AccountError, AccountIdError, AssetError, NetworkIdError};
pub use miden_protocol::errors::{
AccountError,
AccountIdError,
AccountPatchError,
AssetError,
NetworkIdError,
};
use miden_protocol::errors::{
NoteError,
PartialBlockchainError,
Expand Down Expand Up @@ -78,6 +84,8 @@ pub enum ClientError {
AccountAlreadyTracked(AccountId),
#[error("account error")]
AccountError(#[from] AccountError),
#[error("account patch error")]
AccountPatchError(#[from] AccountPatchError),
#[error("account {0} is locked because the local state may be out of date with the network")]
AccountLocked(AccountId),
#[error(
Expand Down
6 changes: 6 additions & 0 deletions crates/rust-client/src/store/data_store/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ impl ClientDataStore {
self.cache.mast_store.clone()
}

/// Returns the underlying [`Store`], so callers (e.g. the batch builder) can serve witnesses
/// against in-batch state without reconstructing accounts.
pub fn store(&self) -> &Arc<dyn Store> {
&self.store
}

/// Stores the provided foreign account inputs so they can be served to the executor upon
/// request.
pub fn register_foreign_account_inputs(
Expand Down
2 changes: 2 additions & 0 deletions crates/rust-client/src/store/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ pub enum StoreError {
VaultKeyNotTracked(AssetId, Word),
#[error("failed to parse word")]
WordError(#[from] WordError),
#[error("operation `{0}` is not supported by this store backend")]
UnsupportedOperation(&'static str),
}

impl From<StoreError> for DataStoreError {
Expand Down
43 changes: 43 additions & 0 deletions crates/rust-client/src/store/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ use miden_protocol::account::{
AccountCode,
AccountHeader,
AccountId,
AccountPatch,
AccountStorage,
StorageMapKey,
StorageMapWitness,
Expand Down Expand Up @@ -709,6 +710,48 @@ pub trait Store: Send + Sync {
}
}

// IN-BATCH (STAGED) WITNESSES
// --------------------------------------------------------------------------------------------

/// Returns vault asset witnesses for `asset_ids` as the account's vault would look after
/// applying `patch` to its committed state, *without* persisting the change. `vault_root` is
/// the in-batch vault root the witnesses are expected to be valid against.
///
/// This lets [`crate::transaction::BatchBuilder`] serve witnesses for keys a prior in-batch
/// transaction never touched (and so are absent from its execution advice) against the
/// stacked account state, without reconstructing the full account.
///
/// The default implementation returns [`StoreError::UnsupportedOperation`]; backends that
/// keep an in-memory Merkle forest (e.g. `SqliteStore`) override it by staging the patch on
/// that forest.
async fn vault_asset_witnesses_after_patch(
&self,
account_id: AccountId,
patch: AccountPatch,
vault_root: Word,
asset_ids: BTreeSet<AssetId>,
) -> Result<Vec<AssetWitness>, StoreError> {
let _ = (account_id, patch, vault_root, asset_ids);
Err(StoreError::UnsupportedOperation("vault_asset_witnesses_after_patch"))
}

/// Returns the storage map witness for `map_key` as the account's storage would look after
/// applying `patch` to its committed state, *without* persisting the change. `map_root` is
/// the in-batch root of the map slot the witness is expected to be valid against.
///
/// See [`Store::vault_asset_witnesses_after_patch`] for the rationale and the default
/// behavior.
async fn storage_map_witness_after_patch(
&self,
account_id: AccountId,
patch: AccountPatch,
map_root: Word,
map_key: StorageMapKey,
) -> Result<StorageMapWitness, StoreError> {
let _ = (account_id, patch, map_root, map_key);
Err(StoreError::UnsupportedOperation("storage_map_witness_after_patch"))
}

// PARTIAL ACCOUNTS
// --------------------------------------------------------------------------------------------

Expand Down
58 changes: 58 additions & 0 deletions crates/rust-client/src/store/smt_forest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,64 @@ impl AccountSmtForest {
Ok(StorageMapWitness::new(proof, [key])?)
}

// STAGED WITNESS QUERIES
// --------------------------------------------------------------------------------------------

/// Serves vault asset witnesses for `query_keys` against the vault root obtained by staging
/// the supplied asset updates onto `committed_vault_root`, *without* persisting the staged
/// tree.
///
/// The staged tree is inserted into the forest (sharing nodes with the committed tree),
/// queried, then dropped: popping it decrements reference counts back to their committed
/// values, so the committed tree's nodes are left intact. The staged root is returned
/// alongside the witnesses so the caller can verify it matches the expected in-batch root.
///
/// Unlike [`Self::get_asset_and_witness`], this also returns a witness for absent keys (an
/// emptiness proof), which the executor needs when an asset is being added to the vault.
pub fn staged_vault_asset_witnesses(
&mut self,
committed_vault_root: Word,
new_assets: impl Iterator<Item = Asset>,
removed_vault_keys: impl Iterator<Item = AssetId>,
query_keys: impl IntoIterator<Item = AssetId>,
) -> Result<(Word, Vec<AssetWitness>), StoreError> {
let staged_root =
self.update_asset_nodes(committed_vault_root, new_assets, removed_vault_keys)?;

let witnesses = query_keys
.into_iter()
.map(|asset_id| {
let proof = self.forest.open(staged_root, asset_id.hash().into())?;
Ok(AssetWitness::new(proof, [asset_id])?)
})
.collect::<Result<Vec<_>, StoreError>>();

// `update_asset_nodes` only adds a new root when the delta actually changed the tree;
// popping that staged root restores the committed tree's ref counts exactly.
if staged_root != committed_vault_root {
self.safe_pop_smts([staged_root]);
}

witnesses.map(|witnesses| (staged_root, witnesses))
}

/// Serves a storage map witness for `query_key` against the map root obtained by staging the
/// supplied entries onto `committed_map_root`, *without* persisting the staged tree. See
/// [`Self::staged_vault_asset_witnesses`] for the staging / ref-count contract.
pub fn staged_storage_map_witness(
&mut self,
committed_map_root: Word,
entries: impl Iterator<Item = (StorageMapKey, Word)>,
query_key: StorageMapKey,
) -> Result<(Word, StorageMapWitness), StoreError> {
let staged_root = self.update_storage_map_nodes(committed_map_root, entries)?;
let witness = self.get_storage_map_item_witness(staged_root, query_key);
if staged_root != committed_map_root {
self.safe_pop_smts([staged_root]);
}
witness.map(|witness| (staged_root, witness))
}

// ROOT LIFECYCLE
// --------------------------------------------------------------------------------------------

Expand Down
Loading
Loading