Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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
13 changes: 6 additions & 7 deletions CHANGELOG.md

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There does not seem to be any entry here for PR 2475

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added entries in 20dc251

Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,10 @@

* [BREAKING][behavior][rust,web] `TransactionRequest` serialization now carries the pinned input notes, so a request serialized by an earlier version cannot be deserialized by this one and vice versa. Rebuild any request that is stored or in flight across the upgrade ([#2437](https://github.com/0xMiden/rust-sdk/pull/2437)).
* [BREAKING][removal][rust] Removed `Client::try_get_account`. Use `Client::get_account` and handle the `None` case, or `Client::account_reader` for existence checks and single-field reads that don't need the full materialized account ([#2362](https://github.com/0xMiden/rust-sdk/pull/2362)).
* [BREAKING][behavior][rpc] The `GetAccount` response no longer carries one SMT opening per requested storage map key. A slot queried with specific keys now comes back as a single partial SMT covering all of them, alongside the original unhashed keys, so the client requires a node that speaks this format ([#2362](https://github.com/0xMiden/rust-sdk/pull/2362)).
* [BREAKING][type][rust] `StorageMapEntries::EntriesWithProofs(Vec<SmtProof>)` is replaced by `StorageMapEntries::PartialMap { map_keys, partial_smt }`, which carries the values only inside the tree: read one by hashing its raw key and calling `PartialSmt::get_value`. The enum also gained a `LimitExceeded` variant and `AccountStorageMapDetails::too_many_entries` was removed in its favor ([#2362](https://github.com/0xMiden/rust-sdk/pull/2362)).
* [BREAKING][type][rust] `SyncedNote` splits the content it carries into two fields, `details: Option<NoteDetails>` and `attachments: NoteAttachments`, replacing the previous `content: Option<ResolvedNoteContent>`; `SyncedNote::new` takes them as separate arguments. `ResolvedNoteContent` is removed. Attachments are no longer optional, a note whose metadata advertises none carries an empty set, so "no attachments" and "attachments not resolved" are no longer the same value ([#2431](https://github.com/0xMiden/rust-sdk/pull/2431)).
* [BREAKING][param][rust] `NoteObserver::observe` takes `&NoteAttachments` instead of `Option<&NoteAttachments>`. A note that carries no attachments is reported with an empty set ([#2431](https://github.com/0xMiden/rust-sdk/pull/2431)).
* [BREAKING][removal][rust] `impl TryFrom<proto::rpc::AccountResponse> for AccountProof` is removed. A partial map can only be checked against the keys that were requested, which that conversion has no way to receive, so it rejected every response answering a specific-key query. Use `proto::rpc::account_response::AccountDetails::into_domain` with the `AccountStorageRequirements` the request was built from ([#2431](https://github.com/0xMiden/rust-sdk/issues/2431)).
* [BREAKING][type][rust] `StorageMapEntries::EntriesWithProofs(Vec<SmtProof>)` is replaced by `StorageMapEntries::PartialMap { map_keys, partial_smt }`. Read a value by hashing its raw key and calling `PartialSmt::get_value`. The enum also gained a `LimitExceeded` variant, replacing `AccountStorageMapDetails::too_many_entries` ([#2431](https://github.com/0xMiden/rust-sdk/pull/2431)).
* [BREAKING][type][rust] `SyncedNote` is flattened: it now carries `note_id`, `metadata`, `inclusion_proof`, `details: Option<NoteDetails>` and `attachments: NoteAttachments` directly, replacing the nested `committed: CommittedNote` and `content: Option<ResolvedNoteContent>` fields. `ResolvedNoteContent` is removed. Attachments are no longer optional, a note whose metadata advertises none carries an empty set, so "no attachments" and "attachments not resolved" are no longer the same value ([#2431](https://github.com/0xMiden/rust-sdk/pull/2431)).
* [BREAKING][param][rust] `NoteObserver::observe` takes a single `&SyncedNote` instead of the committed note and its optional attachments. The synced note bundles the committed record, the resolved attachments (empty for a note that carries none) and, for a fetched public note, its body ([#2431](https://github.com/0xMiden/rust-sdk/pull/2431)).
* [BREAKING][removal][rust] `impl TryFrom<proto::rpc::AccountResponse> for AccountProof` is removed. Use `proto::rpc::account_response::AccountDetails::into_domain` with the `AccountStorageRequirements` the request was built from ([#2431](https://github.com/0xMiden/rust-sdk/pull/2431)).
* [BREAKING][behavior][rust] Foreign `AccountInputs` keep a fetched asset list only when it hashes to the account header's vault root; otherwise (omitted because unchanged, capped as oversize, or malformed) they carry a root-only partial vault, and any assets the foreign code reads are resolved during execution as per-asset witnesses — served from the local store first, falling back to fetching the vault via RPC at the transaction reference block and verifying it against the required root ([#2417](https://github.com/0xMiden/rust-sdk/pull/2417)).
* [BREAKING][behavior][rust] Foreign `AccountInputs` likewise keep a fetched storage-map entry list only when it hashes to the slot's root in the storage header; otherwise (capped as oversize, or malformed) the map is carried root-only and any keys the foreign code reads are resolved during execution as lazy per-key witnesses, instead of syncing an oversized map's full history from genesis before executing ([#2417](https://github.com/0xMiden/rust-sdk/pull/2417)).
* [BREAKING][arch][store] The account SMT forest now persists in SQLite (new `forest_trees`, `forest_entries`, `forest_subtrees` and `forest_revision` tables) through a `LargeSmtForest` backend scoped to the store's own transaction, so forest mutations commit or roll back atomically with the account tables and opening the store no longer rebuilds the forest from account data. Tree inner nodes are persisted as packed subtree blobs, so witness reads load a single leaf plus eight blobs instead of rebuilding the account's tree, making their cost independent of the account's map size at the price of a larger store file. Tree updates are computed path-locally from the persisted leaves and subtree blobs, so committed update cost scales with the size of the change set rather than with the map size. Existing stores are not compatible and must be recreated ([#2333](https://github.com/0xMiden/rust-sdk/pull/2333)).
Expand Down Expand Up @@ -62,8 +61,8 @@
* [FEATURE][rust] Added `AuthGuardedMultisig`, `AuthGuardedMultisigConfig`, `GuardianConfig` and `ApproverSet` to `miden_client::auth`, which previously exposed only the single- and multisig components. Building a guarded multisig account no longer means reaching past the client into `miden_standards` ([#2465](https://github.com/0xMiden/rust-sdk/pull/2465)).
* [FEATURE][rust] `Client::sync_state` now issues its independent gRPC calls concurrently instead of one after another, reducing the total time a sync takes. `NodeRpcClient::sync_notes_with_content` and `NodeRpcClient::sync_transactions` are now called concurrently rather than in sequence, and the per-account `NodeRpcClient::get_account` requests are issued in parallel instead of one at a time ([#2420](https://github.com/0xMiden/rust-sdk/pull/2420)).
* [store] Added `SqliteStore::database_filepath`, which returns the backing database path losslessly as a `&Path` ([#2363](https://github.com/0xMiden/rust-sdk/pull/2363)).
* [FEATURE][rust] Syncing no longer issues a `GetNotesById` request for a note whose attachments the `SyncNotes` response already carried. The node sends an attachment that fits in a single word verbatim, so the client reconstructs it locally: a private note whose attachments are all single-word is resolved with no follow-up request during state sync, as is a note of either type while checking expected notes. Both standard attachment schemes in `miden-standards` (`NetworkAccountTarget` and the PSWAP attachment) are single-word, so the round trip disappears from the common case. A caller-supplied attachment spanning more than one word arrives as a commitment and is still fetched. Public notes are unaffected during state sync, since their bodies are requested regardless ([#2360](https://github.com/0xMiden/rust-sdk/issues/2360)).
* [FEATURE][rust] `CommittedNote` now carries the attachment content its source reported. `attachments()` returns it, `needs_attachment_fetch()` reports whether the content still has to be fetched via `GetNotesById`, and `with_attachments()` records it, rejecting content that does not hash to the metadata's attachments commitment ([#2360](https://github.com/0xMiden/rust-sdk/issues/2360)).
* [FEATURE][rust] Syncing no longer issues a `GetNotesById` request for a note whose attachments the `SyncNotes` response already carried verbatim. Both standard attachment schemes in `miden-standards` fit in a single word and arrive inline, so the round trip disappears from the common case. An attachment spanning more than one word arrives as a commitment and is still fetched ([#2431](https://github.com/0xMiden/rust-sdk/pull/2431)).
* [FEATURE][rust] `CommittedNote` now carries the attachment content its source reported, exposed through `attachments()`, `needs_attachment_fetch()` and `with_attachments()`, which rejects content that does not hash to the metadata's attachments commitment ([#2431](https://github.com/0xMiden/rust-sdk/pull/2431)).
* [FEATURE][rust] On a chain whose reference block charges a non-zero `verification_base_fee`, the client now commits fee conversion info paying the fee in the chain's native fee asset at rate 1/1, so a transaction against an `AuthSingleSig` account no longer has to opt in per request. `AuthMultisig` reuses the commitment's salt as its replay guard, so a request against such an account declares a fresh salt with the new `TransactionRequestBuilder::fee_conversion_salt` and the client commits the native info under it ([#2446](https://github.com/0xMiden/rust-sdk/issues/2446)).
* [FEATURE][rust] Added `ChainAnchor` with `Client::execute_transaction_at` and `Client::chain_anchor_for_request` to capture and execute against a pinned reference block instead of the sync height, so a transaction summary signed at one block — which binds the reference block commitment since protocol 0.16 — can be reproduced and executed later on any client ([#2421](https://github.com/0xMiden/rust-sdk/pull/2421)).
* [FEATURE][rust] A client that only watches a public account now recovers notes the account consumed authenticated, even when it never tracked them by tag. During sync it reads the note references the node attaches to the account's transactions, fetches each note body by id, and surfaces it through `InputNoteReader`. Requires node `0.15.1` ([#2300](https://github.com/0xMiden/rust-sdk/pull/2300)).
Expand Down
2 changes: 1 addition & 1 deletion crates/rust-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ pub use miden_protocol::{
Word,
ZERO,
};
pub use miden_tx::ExecutionOptions;
pub use miden_tx::{ExecutionOptions, NetworkNotePricer};
#[cfg(feature = "tonic")]
pub use remote_prover::RemoteTransactionProver;

Expand Down
18 changes: 9 additions & 9 deletions crates/rust-client/src/note/import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@ where

// Notes the node has not reported as committed keep their expected record untouched.
let Some(SyncedNote {
committed: committed_note, attachments, ..
metadata, inclusion_proof, attachments, ..
}) = committed_notes_data.remove(&note_record.details_commitment())
else {
note_records.push(note_record);
Expand All @@ -381,12 +381,14 @@ where
let attachments = (!attachments.is_empty()).then_some(attachments);

let block_header = self
.get_and_store_authenticated_block(committed_note.block_num(), &mut partial_mmr)
.get_and_store_authenticated_block(
inclusion_proof.location().block_num(),
&mut partial_mmr,
)
.await?;

let metadata = *committed_note.metadata();
let mut note_changed = note_record
.inclusion_proof_received(committed_note.inclusion_proof().clone(), metadata)?;
let mut note_changed =
note_record.inclusion_proof_received(inclusion_proof, metadata)?;

if let Some(attachments) = attachments {
note_changed |= note_record.attachments_received(attachments);
Expand Down Expand Up @@ -455,18 +457,16 @@ where
}

for sync_note in block.notes.into_values() {
let committed = &sync_note.committed;

// The note carries its own commit height in its inclusion proof, which is a
// separate field from the block header checked above. Authenticating the note later
// looks that height up in the partial MMR, so a height beyond our synced view has
// to be dropped here rather than trusted.
if committed.block_num() > current_block_num {
if sync_note.block_num() > current_block_num {
continue;
}

let Some((commitment, _)) = expected_notes.iter().find(|(commitment, _)| {
NoteId::new(*commitment, committed.metadata()) == *committed.note_id()
NoteId::new(*commitment, &sync_note.metadata) == sync_note.note_id
}) else {
continue;
};
Expand Down
18 changes: 7 additions & 11 deletions crates/rust-client/src/pswap/observer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use tracing::warn;
use crate::ClientError;
use crate::pswap::discovery::discover_pswap_rounds;
use crate::pswap::lineage::ObservedPswapNote;
use crate::rpc::domain::note::CommittedNote;
use crate::rpc::domain::note::SyncedNote;
use crate::store::Store;
use crate::sync::NoteObserver;
use crate::utils::RwLock;
Expand Down Expand Up @@ -52,23 +52,19 @@ impl NoteObserver for PswapChainObserver {
"PswapChainObserver"
}

async fn observe(
&self,
committed_note: &CommittedNote,
attachments: &NoteAttachments,
) -> Result<bool, ClientError> {
async fn observe(&self, note: &SyncedNote) -> Result<bool, ClientError> {
// Notes without a PSWAP attachment are the common case; `extract_pswap_attachment`
// fast-rejects them. Foreign-order filtering happens later in `discovery`.
let Some(attachment) = extract_pswap_attachment(attachments) else {
let Some(attachment) = extract_pswap_attachment(&note.attachments) else {
return Ok(false);
};

let inclusion_proof = committed_note.inclusion_proof().clone();
let inclusion_proof = note.inclusion_proof.clone();
self.chain_note_updates.write().push(ObservedPswapNote {
note_id: *committed_note.note_id(),
note_id: note.note_id,
attachment,
sender: committed_note.sender(),
tag: committed_note.metadata().tag(),
sender: note.metadata.sender(),
tag: note.metadata.tag(),
block_num: inclusion_proof.location().block_num(),
inclusion_proof,
});
Expand Down
9 changes: 4 additions & 5 deletions crates/rust-client/src/rpc/domain/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ impl AccountStorageDetails {
/// Checks that every partial map covers exactly the keys that were requested for its slot.
///
/// # Errors
/// - If a partial map covers a different number of keys than were requested for its slot.
/// - If a partial map does not cover a key that was requested for its slot.
/// - If a partial map covers a key that was not requested.
pub fn validate_against_request(
&self,
Expand All @@ -363,12 +363,11 @@ impl AccountStorageDetails {
};

let requested_keys = storage_requirements.keys_for_slot(&map_detail.slot_name);
if map_keys.len() != requested_keys.len() {
if let Some(key) = requested_keys.iter().find(|key| !map_keys.contains(key)) {
return Err(RpcError::InvalidResponse(format!(
"expected {} keys for storage map slot '{}', got {}",
requested_keys.len(),
"partial storage map for slot '{}' does not cover requested key {}",
map_detail.slot_name,
map_keys.len(),
key.to_hex(),
)));
}
if let Some(key) = map_keys.iter().find(|key| !requested_keys.contains(key)) {
Expand Down
Loading
Loading