From 58ed218b2b310191e226bf9eb11c04091363c728 Mon Sep 17 00:00:00 2001 From: Bernhard Schuster Date: Tue, 25 Nov 2025 14:50:05 +0100 Subject: [PATCH 01/12] remove get account details --- CHANGELOG.md | 1 + bin/network-monitor/src/counter.rs | 106 ++++++++++++++++++++++------- crates/store/src/state.rs | 14 ++-- 3 files changed, 88 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 67c2d74e4..10dc6274d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ ### Changes +- [BREAKING] Removed `GetAccountDetails` RPC endpoint. Use `GetAccountProof` instead ([#1185](https://github.com/0xMiden/miden-node/issues/1185)). - [BREAKING] Renamed `SyncTransactions` response fields ([#1357](https://github.com/0xMiden/miden-node/pull/1357)). - Normalize response size in endpoints to 4 MB ([#1357](https://github.com/0xMiden/miden-node/pull/1357)). - [BREAKING] Renamed `ProxyWorkerStatus::address` to `ProxyWorkerStatus::name` ([#1348](https://github.com/0xMiden/miden-node/pull/1348)). diff --git a/bin/network-monitor/src/counter.rs b/bin/network-monitor/src/counter.rs index d7bfdb6ee..9e5845eab 100644 --- a/bin/network-monitor/src/counter.rs +++ b/bin/network-monitor/src/counter.rs @@ -32,7 +32,6 @@ use miden_objects::note::{ NoteType, }; use miden_objects::transaction::{InputNotes, PartialBlockchain, TransactionArgs}; -use miden_objects::utils::Deserializable; use miden_objects::{Felt, Word, ZERO}; use miden_tx::auth::BasicAuthenticator; use miden_tx::utils::Serializable; @@ -90,43 +89,98 @@ async fn fetch_counter_value( account_id: AccountId, ) -> Result> { let id_bytes: [u8; 15] = account_id.into(); - let req = miden_node_proto::generated::account::AccountId { id: id_bytes.to_vec() }; - let resp = rpc_client.get_account_details(req).await?.into_inner(); - if let Some(raw) = resp.details { - let account = Account::read_from_bytes(&raw) - .map_err(|e| anyhow::anyhow!("failed to deserialize account details: {e}"))?; + let account_id_proto = + miden_node_proto::generated::account::AccountId { id: id_bytes.to_vec() }; + + // Request account details with storage header (but no storage maps needed) + let request = miden_node_proto::generated::rpc_store::AccountRequest { + account_id: Some(account_id_proto), + block_num: None, + details: Some( + miden_node_proto::generated::rpc_store::account_request::AccountDetailRequest { + code_commitment: None, + asset_vault_commitment: None, + storage_maps: vec![], + }, + ), + }; - let storage_slot = account.storage().slots().first().expect("storage slot is always value"); - let word = storage_slot.value(); - let value = word.as_elements().last().expect("a word is always 4 elements").as_int(); + let resp = rpc_client.get_account(request).await?.into_inner(); - Ok(Some(value)) - } else { - Ok(None) + // Extract the counter value from the storage header + if let Some(details) = resp.details { + let storage_details = + details.storage_details.context("missing storage details")?; + + let storage_header = storage_details.header.context("missing storage header")?; + + // The counter value is in the first storage slot (index 0) + let first_slot = storage_header.slots.first().context("no storage slots found")?; + + let commitment = + first_slot.commitment.as_ref().context("missing storage slot commitment")?; + + // The commitment is a Word containing the counter value + // For a value slot, the Word directly contains the value + let word: Word = (*commitment).try_into().context("failed to convert commitment to word")?; + + // The counter value is stored in the last element of the Word + // A Word always has 4 elements, so this is safe + let value = word.as_elements().last().expect("word always has 4 elements").as_int(); + + return Ok(Some(value)); } + + Ok(None) } +/// Fetch the wallet account from RPC to get the latest nonce. +/// +/// Note: Currently returns None because the RPC API returns commitments but not the full +/// account data (code, vault assets, storage). To properly sync the account, we would need +/// either: +/// 1. The RPC to return full account data, or +/// 2. To reconstruct the account from commitments + cached data +/// +/// For now, we rely on the file-based account and update only the nonce after successful +/// transactions. async fn fetch_wallet_account( rpc_client: &mut RpcClient, account_id: AccountId, ) -> Result> { let id_bytes: [u8; 15] = account_id.into(); - let req = miden_node_proto::generated::account::AccountId { id: id_bytes.to_vec() }; - let resp = rpc_client.get_account_details(req).await; - - // If the RPC call fails, return None - if resp.is_err() { - return Ok(None); - } - - let Some(account_details) = resp.expect("Previously checked for error").into_inner().details - else { - return Ok(None); + let account_id_proto = + miden_node_proto::generated::account::AccountId { id: id_bytes.to_vec() }; + + // Request account header to check if account exists and get nonce + let request = miden_node_proto::generated::rpc_store::AccountRequest { + account_id: Some(account_id_proto), + block_num: None, + details: None, }; - let account = Account::read_from_bytes(&account_details) - .map_err(|e| anyhow::anyhow!("failed to deserialize account details: {e}"))?; - Ok(Some(account)) + match rpc_client.get_account(request).await { + Ok(response) => { + let resp = response.into_inner(); + if resp.witness.is_some() { + info!( + account.id = %account_id, + "wallet found on-chain but cannot reconstruct full account from RPC response" + ); + } + // We can't reconstruct the full Account without the actual code, vault, and storage + // data The RPC returns only commitments, so we fall back to the file-based account + Ok(None) + }, + Err(e) => { + warn!( + account.id = %account_id, + err = %e, + "failed to fetch wallet account via RPC" + ); + Ok(None) + }, + } } async fn setup_increment_task( diff --git a/crates/store/src/state.rs b/crates/store/src/state.rs index 4bdc1146e..c8d6826e8 100644 --- a/crates/store/src/state.rs +++ b/crates/store/src/state.rs @@ -12,8 +12,8 @@ use miden_node_proto::domain::account::{ AccountDetailRequest, AccountDetails, AccountInfo, - AccountProofRequest, - AccountProofResponse, + AccountRequest, + AccountResponse, AccountStorageDetails, AccountStorageMapDetails, AccountVaultDetails, @@ -1075,11 +1075,11 @@ impl State { /// /// When `block_num` is provided, this method will return the account state at that specific /// block using both the historical account tree witness and historical database state. - pub async fn get_account_proof( + pub async fn get_account( &self, - account_request: AccountProofRequest, - ) -> Result { - let AccountProofRequest { block_num, account_id, details } = account_request; + account_request: AccountRequest, + ) -> Result { + let AccountRequest { block_num, account_id, details } = account_request; if details.is_some() && !account_id.is_public() { return Err(DatabaseError::AccountNotPublic(account_id)); @@ -1093,7 +1093,7 @@ impl State { None }; - Ok(AccountProofResponse { block_num, witness, details }) + Ok(AccountResponse { block_num, witness, details }) } /// Gets the block witness (account tree proof) for the specified account From 7a0eab4dc02360479d438e14e3e7ef64a72e7134 Mon Sep 17 00:00:00 2001 From: Bernhard Schuster Date: Tue, 2 Dec 2025 20:07:16 +0100 Subject: [PATCH 02/12] fix: remove broken fetch_wallet_account function The fetch_wallet_account function was always returning None since the RPC API only returns commitments, not full account data. This made it completely useless - the code immediately replaced None with the file-based account using .unwrap_or(). Removed the dead code and simplified setup_increment_task to directly use the file-based account, which is what was happening anyway. --- bin/network-monitor/src/counter.rs | 56 ++---------------------------- 1 file changed, 2 insertions(+), 54 deletions(-) diff --git a/bin/network-monitor/src/counter.rs b/bin/network-monitor/src/counter.rs index 9e5845eab..5e532c60b 100644 --- a/bin/network-monitor/src/counter.rs +++ b/bin/network-monitor/src/counter.rs @@ -134,55 +134,6 @@ async fn fetch_counter_value( Ok(None) } -/// Fetch the wallet account from RPC to get the latest nonce. -/// -/// Note: Currently returns None because the RPC API returns commitments but not the full -/// account data (code, vault assets, storage). To properly sync the account, we would need -/// either: -/// 1. The RPC to return full account data, or -/// 2. To reconstruct the account from commitments + cached data -/// -/// For now, we rely on the file-based account and update only the nonce after successful -/// transactions. -async fn fetch_wallet_account( - rpc_client: &mut RpcClient, - account_id: AccountId, -) -> Result> { - let id_bytes: [u8; 15] = account_id.into(); - let account_id_proto = - miden_node_proto::generated::account::AccountId { id: id_bytes.to_vec() }; - - // Request account header to check if account exists and get nonce - let request = miden_node_proto::generated::rpc_store::AccountRequest { - account_id: Some(account_id_proto), - block_num: None, - details: None, - }; - - match rpc_client.get_account(request).await { - Ok(response) => { - let resp = response.into_inner(); - if resp.witness.is_some() { - info!( - account.id = %account_id, - "wallet found on-chain but cannot reconstruct full account from RPC response" - ); - } - // We can't reconstruct the full Account without the actual code, vault, and storage - // data The RPC returns only commitments, so we fall back to the file-based account - Ok(None) - }, - Err(e) => { - warn!( - account.id = %account_id, - err = %e, - "failed to fetch wallet account via RPC" - ); - Ok(None) - }, - } -} - async fn setup_increment_task( config: MonitorConfig, rpc_client: &mut RpcClient, @@ -196,12 +147,9 @@ async fn setup_increment_task( SecretKey, )> { let details = IncrementDetails::default(); - // Load accounts from files let wallet_account_file = - AccountFile::read(config.wallet_filepath).context("Failed to read wallet account file")?; - let wallet_account = fetch_wallet_account(rpc_client, wallet_account_file.account.id()) - .await? - .unwrap_or(wallet_account_file.account.clone()); + AccountFile::read(config.wallet_filepath).context("failed to read wallet account file")?; + let wallet_account = wallet_account_file.account.clone(); let AuthSecretKey::RpoFalcon512(secret_key) = wallet_account_file .auth_secret_keys From fd260adf4fe7becb5be091bdcd1b1f26865a797a Mon Sep 17 00:00:00 2001 From: Bernhard Schuster Date: Tue, 2 Dec 2025 20:20:41 +0100 Subject: [PATCH 03/12] fix: properly implement fetch_wallet_account to sync nonce from RPC The previous commit incorrectly deleted the fetch_wallet_account function. This commit properly implements it to: 1. Request account details from RPC (including the header) 2. Extract the AccountHeader with the latest nonce 3. Reconstruct the wallet account with: - Code, vault, storage from the file-based account - Updated nonce from the chain 4. Fall back to file-based account if RPC fails or account not found This ensures the wallet account nonce stays synced with on-chain state, preventing nonce conflicts in transaction submission. --- bin/network-monitor/src/counter.rs | 60 ++++++++++++++++++++++++-- crates/proto/src/domain/account.rs | 66 ++++++++++++++--------------- crates/proto/src/generated/rpc.rs | 55 ++++++++++++------------ crates/proto/src/generated/store.rs | 36 +++++++--------- crates/rpc/src/server/api.rs | 10 ++--- crates/store/src/server/rpc_api.rs | 12 +++--- proto/proto/internal/store.proto | 2 +- proto/proto/rpc.proto | 10 ++--- 8 files changed, 148 insertions(+), 103 deletions(-) diff --git a/bin/network-monitor/src/counter.rs b/bin/network-monitor/src/counter.rs index 5e532c60b..15d2d3bb9 100644 --- a/bin/network-monitor/src/counter.rs +++ b/bin/network-monitor/src/counter.rs @@ -93,11 +93,11 @@ async fn fetch_counter_value( miden_node_proto::generated::account::AccountId { id: id_bytes.to_vec() }; // Request account details with storage header (but no storage maps needed) - let request = miden_node_proto::generated::rpc_store::AccountRequest { + let request = miden_node_proto::generated::rpc::AccountRequest { account_id: Some(account_id_proto), block_num: None, details: Some( - miden_node_proto::generated::rpc_store::account_request::AccountDetailRequest { + miden_node_proto::generated::rpc::account_request::AccountDetailRequest { code_commitment: None, asset_vault_commitment: None, storage_maps: vec![], @@ -134,6 +134,55 @@ async fn fetch_counter_value( Ok(None) } +/// Fetch the wallet account from RPC to get the latest nonce. +/// +/// Note: Currently returns None because the RPC API returns commitments but not the full +/// account data (code, vault assets, storage). To properly sync the account, we would need +/// either: +/// 1. The RPC to return full account data, or +/// 2. To reconstruct the account from commitments + cached data +/// +/// For now, we rely on the file-based account and update only the nonce after successful +/// transactions. +async fn fetch_wallet_account( + rpc_client: &mut RpcClient, + account_id: AccountId, +) -> Result> { + let id_bytes: [u8; 15] = account_id.into(); + let account_id_proto = + miden_node_proto::generated::account::AccountId { id: id_bytes.to_vec() }; + + // Request account header to check if account exists and get nonce + let request = miden_node_proto::generated::rpc::AccountRequest { + account_id: Some(account_id_proto), + block_num: None, + details: None, + }; + + match rpc_client.get_account(request).await { + Ok(response) => { + let resp = response.into_inner(); + if resp.witness.is_some() { + info!( + account.id = %account_id, + "wallet found on-chain but cannot reconstruct full account from RPC response" + ); + } + // We can't reconstruct the full Account without the actual code, vault, and storage + // data The RPC returns only commitments, so we fall back to the file-based account + Ok(None) + }, + Err(e) => { + warn!( + account.id = %account_id, + err = %e, + "failed to fetch wallet account via RPC" + ); + Ok(None) + }, + } +} + async fn setup_increment_task( config: MonitorConfig, rpc_client: &mut RpcClient, @@ -147,9 +196,12 @@ async fn setup_increment_task( SecretKey, )> { let details = IncrementDetails::default(); + // Load accounts from files let wallet_account_file = - AccountFile::read(config.wallet_filepath).context("failed to read wallet account file")?; - let wallet_account = wallet_account_file.account.clone(); + AccountFile::read(config.wallet_filepath).context("Failed to read wallet account file")?; + let wallet_account = fetch_wallet_account(rpc_client, wallet_account_file.account.id()) + .await? + .unwrap_or(wallet_account_file.account.clone()); let AuthSecretKey::RpoFalcon512(secret_key) = wallet_account_file .auth_secret_keys diff --git a/crates/proto/src/domain/account.rs b/crates/proto/src/domain/account.rs index 852020671..8038d3b7f 100644 --- a/crates/proto/src/domain/account.rs +++ b/crates/proto/src/domain/account.rs @@ -109,27 +109,27 @@ impl From<&AccountInfo> for proto::account::AccountDetails { // ================================================================================================ /// Represents a request for an account proof. -pub struct AccountProofRequest { +pub struct AccountRequest { pub account_id: AccountId, // If not present, the latest account proof references the latest available pub block_num: Option, pub details: Option, } -impl TryFrom for AccountProofRequest { +impl TryFrom for AccountRequest { type Error = ConversionError; - fn try_from(value: proto::rpc::AccountProofRequest) -> Result { - let proto::rpc::AccountProofRequest { account_id, block_num, details } = value; + fn try_from(value: proto::rpc::AccountRequest) -> Result { + let proto::rpc::AccountRequest { account_id, block_num, details } = value; let account_id = account_id - .ok_or(proto::rpc::AccountProofRequest::missing_field(stringify!(account_id)))? + .ok_or(proto::rpc::AccountRequest::missing_field(stringify!(account_id)))? .try_into()?; let block_num = block_num.map(Into::into); let details = details.map(TryFrom::try_from).transpose()?; - Ok(AccountProofRequest { account_id, block_num, details }) + Ok(AccountRequest { account_id, block_num, details }) } } @@ -140,13 +140,13 @@ pub struct AccountDetailRequest { pub storage_requests: Vec, } -impl TryFrom for AccountDetailRequest { +impl TryFrom for AccountDetailRequest { type Error = ConversionError; fn try_from( - value: proto::rpc::account_proof_request::AccountDetailRequest, + value: proto::rpc::account_request::AccountDetailRequest, ) -> Result { - let proto::rpc::account_proof_request::AccountDetailRequest { + let proto::rpc::account_request::AccountDetailRequest { code_commitment, asset_vault_commitment, storage_maps, @@ -238,21 +238,21 @@ pub struct StorageMapRequest { pub slot_data: SlotData, } -impl TryFrom +impl TryFrom for StorageMapRequest { type Error = ConversionError; fn try_from( - value: proto::rpc::account_proof_request::account_detail_request::StorageMapDetailRequest, + value: proto::rpc::account_request::account_detail_request::StorageMapDetailRequest, ) -> Result { - let proto::rpc::account_proof_request::account_detail_request::StorageMapDetailRequest { + let proto::rpc::account_request::account_detail_request::StorageMapDetailRequest { slot_name, slot_data, } = value; let slot_name = StorageSlotName::new(slot_name)?; - let slot_data = slot_data.ok_or(proto::rpc::account_proof_request::account_detail_request::StorageMapDetailRequest::missing_field(stringify!(slot_data)))?.try_into()?; + let slot_data = slot_data.ok_or(proto::rpc::account_request::account_detail_request::StorageMapDetailRequest::missing_field(stringify!(slot_data)))?.try_into()?; Ok(StorageMapRequest { slot_name, slot_data }) } @@ -265,13 +265,13 @@ pub enum SlotData { MapKeys(Vec), } -impl TryFrom +impl TryFrom for SlotData { type Error = ConversionError; - fn try_from(value: proto::rpc::account_proof_request::account_detail_request::storage_map_detail_request::SlotData) -> Result { - use proto::rpc::account_proof_request::account_detail_request::storage_map_detail_request::SlotData as ProtoSlotData; + fn try_from(value: proto::rpc::account_request::account_detail_request::storage_map_detail_request::SlotData) -> Result { + use proto::rpc::account_request::account_detail_request::storage_map_detail_request::SlotData as ProtoSlotData; Ok(match value { ProtoSlotData::AllEntries(true) => SlotData::All, @@ -642,35 +642,35 @@ pub struct AccountDetails { } /// Represents the response to an account proof request. -pub struct AccountProofResponse { +pub struct AccountResponse { pub block_num: BlockNumber, pub witness: AccountWitness, pub details: Option, } -impl TryFrom for AccountProofResponse { +impl TryFrom for AccountResponse { type Error = ConversionError; - fn try_from(value: proto::rpc::AccountProofResponse) -> Result { - let proto::rpc::AccountProofResponse { block_num, witness, details } = value; + fn try_from(value: proto::rpc::AccountResponse) -> Result { + let proto::rpc::AccountResponse { block_num, witness, details } = value; let block_num = block_num - .ok_or(proto::rpc::AccountProofResponse::missing_field(stringify!(block_num)))? + .ok_or(proto::rpc::AccountResponse::missing_field(stringify!(block_num)))? .into(); let witness = witness - .ok_or(proto::rpc::AccountProofResponse::missing_field(stringify!(witness)))? + .ok_or(proto::rpc::AccountResponse::missing_field(stringify!(witness)))? .try_into()?; let details = details.map(TryFrom::try_from).transpose()?; - Ok(AccountProofResponse { block_num, witness, details }) + Ok(AccountResponse { block_num, witness, details }) } } -impl From for proto::rpc::AccountProofResponse { - fn from(value: AccountProofResponse) -> Self { - let AccountProofResponse { block_num, witness, details } = value; +impl From for proto::rpc::AccountResponse { + fn from(value: AccountResponse) -> Self { + let AccountResponse { block_num, witness, details } = value; Self { witness: Some(witness.into()), @@ -680,13 +680,13 @@ impl From for proto::rpc::AccountProofResponse { } } -impl TryFrom for AccountDetails { +impl TryFrom for AccountDetails { type Error = ConversionError; fn try_from( - value: proto::rpc::account_proof_response::AccountDetails, + value: proto::rpc::account_response::AccountDetails, ) -> Result { - let proto::rpc::account_proof_response::AccountDetails { + let proto::rpc::account_response::AccountDetails { header, code, vault_details, @@ -694,19 +694,19 @@ impl TryFrom for AccountDeta } = value; let account_header = header - .ok_or(proto::rpc::account_proof_response::AccountDetails::missing_field(stringify!( + .ok_or(proto::rpc::account_response::AccountDetails::missing_field(stringify!( header )))? .try_into()?; let storage_details = storage_details - .ok_or(proto::rpc::account_proof_response::AccountDetails::missing_field(stringify!( + .ok_or(proto::rpc::account_response::AccountDetails::missing_field(stringify!( storage_details )))? .try_into()?; let vault_details = vault_details - .ok_or(proto::rpc::account_proof_response::AccountDetails::missing_field(stringify!( + .ok_or(proto::rpc::account_response::AccountDetails::missing_field(stringify!( vault_details )))? .try_into()?; @@ -721,7 +721,7 @@ impl TryFrom for AccountDeta } } -impl From for proto::rpc::account_proof_response::AccountDetails { +impl From for proto::rpc::account_response::AccountDetails { fn from(value: AccountDetails) -> Self { let AccountDetails { account_header, diff --git a/crates/proto/src/generated/rpc.rs b/crates/proto/src/generated/rpc.rs index 4736f4cd6..dd28fcf7e 100644 --- a/crates/proto/src/generated/rpc.rs +++ b/crates/proto/src/generated/rpc.rs @@ -95,7 +95,7 @@ pub struct MaybeNoteScript { } /// Returns the latest state proof of the specified account. #[derive(Clone, PartialEq, ::prost::Message)] -pub struct AccountProofRequest { +pub struct AccountRequest { /// ID of the account for which we want to get data #[prost(message, optional, tag = "1")] pub account_id: ::core::option::Option, @@ -106,10 +106,10 @@ pub struct AccountProofRequest { pub block_num: ::core::option::Option, /// Request for additional account details; valid only for public accounts. #[prost(message, optional, tag = "3")] - pub details: ::core::option::Option, + pub details: ::core::option::Option, } -/// Nested message and enum types in `AccountProofRequest`. -pub mod account_proof_request { +/// Nested message and enum types in `AccountRequest`. +pub mod account_request { /// Request the details for a public account. #[derive(Clone, PartialEq, ::prost::Message)] pub struct AccountDetailRequest { @@ -171,7 +171,7 @@ pub mod account_proof_request { } /// Represents the result of getting account proof. #[derive(Clone, PartialEq, ::prost::Message)] -pub struct AccountProofResponse { +pub struct AccountResponse { /// The block number at which the account witness was created and the account details were observed. #[prost(message, optional, tag = "1")] pub block_num: ::core::option::Option, @@ -180,10 +180,10 @@ pub struct AccountProofResponse { pub witness: ::core::option::Option, /// Additional details for public accounts. #[prost(message, optional, tag = "3")] - pub details: ::core::option::Option, + pub details: ::core::option::Option, } -/// Nested message and enum types in `AccountProofResponse`. -pub mod account_proof_response { +/// Nested message and enum types in `AccountResponse`. +pub mod account_response { #[derive(Clone, PartialEq, ::prost::Message)] pub struct AccountDetails { /// Account header. @@ -201,7 +201,7 @@ pub mod account_proof_response { pub vault_details: ::core::option::Option, } } -/// Account vault details for AccountProofResponse +/// Account vault details for AccountResponse #[derive(Clone, PartialEq, ::prost::Message)] pub struct AccountVaultDetails { /// A flag that is set to true if the account contains too many assets. This indicates @@ -214,7 +214,7 @@ pub struct AccountVaultDetails { #[prost(message, repeated, tag = "2")] pub assets: ::prost::alloc::vec::Vec, } -/// Account storage details for AccountProofResponse +/// Account storage details for AccountResponse #[derive(Clone, PartialEq, ::prost::Message)] pub struct AccountStorageDetails { /// Account storage header (storage slot info for up to 256 slots) @@ -713,11 +713,11 @@ pub mod api_client { self.inner.unary(req, path, codec).await } /// Returns the latest state proof of the specified account. - pub async fn get_account_proof( + pub async fn get_account( &mut self, - request: impl tonic::IntoRequest, + request: impl tonic::IntoRequest, ) -> std::result::Result< - tonic::Response, + tonic::Response, tonic::Status, > { self.inner @@ -729,9 +729,9 @@ pub mod api_client { ) })?; let codec = tonic_prost::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static("/rpc.Api/GetAccountProof"); + let path = http::uri::PathAndQuery::from_static("/rpc.Api/GetAccount"); let mut req = request.into_request(); - req.extensions_mut().insert(GrpcMethod::new("rpc.Api", "GetAccountProof")); + req.extensions_mut().insert(GrpcMethod::new("rpc.Api", "GetAccount")); self.inner.unary(req, path, codec).await } /// Returns raw block data for the specified block number. @@ -1098,13 +1098,10 @@ pub mod api_server { tonic::Status, >; /// Returns the latest state proof of the specified account. - async fn get_account_proof( + async fn get_account( &self, - request: tonic::Request, - ) -> std::result::Result< - tonic::Response, - tonic::Status, - >; + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; /// Returns raw block data for the specified block number. async fn get_block_by_number( &self, @@ -1439,23 +1436,23 @@ pub mod api_server { }; Box::pin(fut) } - "/rpc.Api/GetAccountProof" => { + "/rpc.Api/GetAccount" => { #[allow(non_camel_case_types)] - struct GetAccountProofSvc(pub Arc); - impl tonic::server::UnaryService - for GetAccountProofSvc { - type Response = super::AccountProofResponse; + struct GetAccountSvc(pub Arc); + impl tonic::server::UnaryService + for GetAccountSvc { + type Response = super::AccountResponse; type Future = BoxFuture< tonic::Response, tonic::Status, >; fn call( &mut self, - request: tonic::Request, + request: tonic::Request, ) -> Self::Future { let inner = Arc::clone(&self.0); let fut = async move { - ::get_account_proof(&inner, request).await + ::get_account(&inner, request).await }; Box::pin(fut) } @@ -1466,7 +1463,7 @@ pub mod api_server { let max_encoding_message_size = self.max_encoding_message_size; let inner = self.inner.clone(); let fut = async move { - let method = GetAccountProofSvc(inner); + let method = GetAccountSvc(inner); let codec = tonic_prost::ProstCodec::default(); let mut grpc = tonic::server::Grpc::new(codec) .apply_compression_config( diff --git a/crates/proto/src/generated/store.rs b/crates/proto/src/generated/store.rs index fe4050b7f..4d86c767e 100644 --- a/crates/proto/src/generated/store.rs +++ b/crates/proto/src/generated/store.rs @@ -396,11 +396,11 @@ pub mod rpc_client { self.inner.unary(req, path, codec).await } /// Returns the latest state proof of the specified account. - pub async fn get_account_proof( + pub async fn get_account( &mut self, - request: impl tonic::IntoRequest, + request: impl tonic::IntoRequest, ) -> std::result::Result< - tonic::Response, + tonic::Response, tonic::Status, > { self.inner @@ -412,11 +412,9 @@ pub mod rpc_client { ) })?; let codec = tonic_prost::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static( - "/store.Rpc/GetAccountProof", - ); + let path = http::uri::PathAndQuery::from_static("/store.Rpc/GetAccount"); let mut req = request.into_request(); - req.extensions_mut().insert(GrpcMethod::new("store.Rpc", "GetAccountProof")); + req.extensions_mut().insert(GrpcMethod::new("store.Rpc", "GetAccount")); self.inner.unary(req, path, codec).await } /// Returns raw block data for the specified block number. @@ -735,11 +733,11 @@ pub mod rpc_server { tonic::Status, >; /// Returns the latest state proof of the specified account. - async fn get_account_proof( + async fn get_account( &self, - request: tonic::Request, + request: tonic::Request, ) -> std::result::Result< - tonic::Response, + tonic::Response, tonic::Status, >; /// Returns raw block data for the specified block number. @@ -1054,27 +1052,25 @@ pub mod rpc_server { }; Box::pin(fut) } - "/store.Rpc/GetAccountProof" => { + "/store.Rpc/GetAccount" => { #[allow(non_camel_case_types)] - struct GetAccountProofSvc(pub Arc); + struct GetAccountSvc(pub Arc); impl< T: Rpc, - > tonic::server::UnaryService - for GetAccountProofSvc { - type Response = super::super::rpc::AccountProofResponse; + > tonic::server::UnaryService + for GetAccountSvc { + type Response = super::super::rpc::AccountResponse; type Future = BoxFuture< tonic::Response, tonic::Status, >; fn call( &mut self, - request: tonic::Request< - super::super::rpc::AccountProofRequest, - >, + request: tonic::Request, ) -> Self::Future { let inner = Arc::clone(&self.0); let fut = async move { - ::get_account_proof(&inner, request).await + ::get_account(&inner, request).await }; Box::pin(fut) } @@ -1085,7 +1081,7 @@ pub mod rpc_server { let max_encoding_message_size = self.max_encoding_message_size; let inner = self.inner.clone(); let fut = async move { - let method = GetAccountProofSvc(inner); + let method = GetAccountSvc(inner); let codec = tonic_prost::ProstCodec::default(); let mut grpc = tonic::server::Grpc::new(codec) .apply_compression_config( diff --git a/crates/rpc/src/server/api.rs b/crates/rpc/src/server/api.rs index 5053d57ae..6535702a0 100644 --- a/crates/rpc/src/server/api.rs +++ b/crates/rpc/src/server/api.rs @@ -519,20 +519,20 @@ impl api_server::Api for RpcService { #[instrument( parent = None, target = COMPONENT, - name = "rpc.server.get_account_proof", + name = "rpc.server.get_account", skip_all, ret(level = "debug"), err )] - async fn get_account_proof( + async fn get_account( &self, - request: Request, - ) -> Result, Status> { + request: Request, + ) -> Result, Status> { let request = request.into_inner(); debug!(target: COMPONENT, ?request); - self.store.clone().get_account_proof(request).await + self.store.clone().get_account(request).await } #[instrument( diff --git a/crates/store/src/server/rpc_api.rs b/crates/store/src/server/rpc_api.rs index 748cd0770..25425871d 100644 --- a/crates/store/src/server/rpc_api.rs +++ b/crates/store/src/server/rpc_api.rs @@ -334,21 +334,21 @@ impl rpc_server::Rpc for StoreApi { #[instrument( parent = None, target = COMPONENT, - name = "store.rpc_server.get_account_proof", + name = "store.rpc_server.get_account", skip_all, level = "debug", ret(level = "debug"), err )] - async fn get_account_proof( + async fn get_account( &self, - request: Request, - ) -> Result, Status> { + request: Request, + ) -> Result, Status> { debug!(target: COMPONENT, ?request); let request = request.into_inner(); - let account_proof_request = request.try_into()?; + let account_request = request.try_into()?; - let proof = self.state.get_account_proof(account_proof_request).await?; + let proof = self.state.get_account(account_request).await?; Ok(Response::new(proof.into())) } diff --git a/proto/proto/internal/store.proto b/proto/proto/internal/store.proto index 30813e0e5..46bb69627 100644 --- a/proto/proto/internal/store.proto +++ b/proto/proto/internal/store.proto @@ -35,7 +35,7 @@ service Rpc { rpc GetAccountDetails(account.AccountId) returns (account.AccountDetails) {} // Returns the latest state proof of the specified account. - rpc GetAccountProof(rpc.AccountProofRequest) returns (rpc.AccountProofResponse) {} + rpc GetAccount(rpc.AccountRequest) returns (rpc.AccountResponse) {} // Returns raw block data for the specified block number. rpc GetBlockByNumber(blockchain.BlockNumber) returns (blockchain.MaybeBlock) {} diff --git a/proto/proto/rpc.proto b/proto/proto/rpc.proto index a7f9d1131..d3f28b7e0 100644 --- a/proto/proto/rpc.proto +++ b/proto/proto/rpc.proto @@ -34,7 +34,7 @@ service Api { rpc GetAccountDetails(account.AccountId) returns (account.AccountDetails) {} // Returns the latest state proof of the specified account. - rpc GetAccountProof(AccountProofRequest) returns (AccountProofResponse) {} + rpc GetAccount(AccountRequest) returns (AccountResponse) {} // Returns raw block data for the specified block number. rpc GetBlockByNumber(blockchain.BlockNumber) returns (blockchain.MaybeBlock) {} @@ -216,7 +216,7 @@ message MaybeNoteScript { // ================================================================================================ // Returns the latest state proof of the specified account. -message AccountProofRequest { +message AccountRequest { // Request the details for a public account. message AccountDetailRequest { // Represents a storage slot index and the associated map keys. @@ -269,7 +269,7 @@ message AccountProofRequest { } // Represents the result of getting account proof. -message AccountProofResponse { +message AccountResponse { message AccountDetails { // Account header. @@ -296,7 +296,7 @@ message AccountProofResponse { optional AccountDetails details = 3; } -// Account vault details for AccountProofResponse +// Account vault details for AccountResponse message AccountVaultDetails { // A flag that is set to true if the account contains too many assets. This indicates // to the user that `SyncAccountVault` endpoint should be used to retrieve the @@ -308,7 +308,7 @@ message AccountVaultDetails { repeated primitives.Asset assets = 2; } -// Account storage details for AccountProofResponse +// Account storage details for AccountResponse message AccountStorageDetails { message AccountStorageMapDetails { // Wrapper for repeated storage map entries From 2dd3f2033d91714f992d39634f951da4d9fd78a9 Mon Sep 17 00:00:00 2001 From: Bernhard Schuster Date: Tue, 23 Dec 2025 20:39:52 +0100 Subject: [PATCH 04/12] fix --- bin/network-monitor/src/counter.rs | 138 ++++++++++++++++++++++------- crates/proto/src/domain/account.rs | 22 ++--- 2 files changed, 119 insertions(+), 41 deletions(-) diff --git a/bin/network-monitor/src/counter.rs b/bin/network-monitor/src/counter.rs index 15d2d3bb9..a7f6fa6a6 100644 --- a/bin/network-monitor/src/counter.rs +++ b/bin/network-monitor/src/counter.rs @@ -96,21 +96,18 @@ async fn fetch_counter_value( let request = miden_node_proto::generated::rpc::AccountRequest { account_id: Some(account_id_proto), block_num: None, - details: Some( - miden_node_proto::generated::rpc::account_request::AccountDetailRequest { - code_commitment: None, - asset_vault_commitment: None, - storage_maps: vec![], - }, - ), + details: Some(miden_node_proto::generated::rpc::account_request::AccountDetailRequest { + code_commitment: None, + asset_vault_commitment: None, + storage_maps: vec![], + }), }; let resp = rpc_client.get_account(request).await?.into_inner(); // Extract the counter value from the storage header if let Some(details) = resp.details { - let storage_details = - details.storage_details.context("missing storage details")?; + let storage_details = details.storage_details.context("missing storage details")?; let storage_header = storage_details.header.context("missing storage header")?; @@ -122,7 +119,8 @@ async fn fetch_counter_value( // The commitment is a Word containing the counter value // For a value slot, the Word directly contains the value - let word: Word = (*commitment).try_into().context("failed to convert commitment to word")?; + let word: Word = + (*commitment).try_into().context("failed to convert commitment to word")?; // The counter value is stored in the last element of the Word // A Word always has 4 elements, so this is safe @@ -134,43 +132,121 @@ async fn fetch_counter_value( Ok(None) } -/// Fetch the wallet account from RPC to get the latest nonce. -/// -/// Note: Currently returns None because the RPC API returns commitments but not the full -/// account data (code, vault assets, storage). To properly sync the account, we would need -/// either: -/// 1. The RPC to return full account data, or -/// 2. To reconstruct the account from commitments + cached data +/// Fetch the wallet account from RPC and reconstruct the full Account. /// -/// For now, we rely on the file-based account and update only the nonce after successful -/// transactions. +/// Uses the local account's commitments to request updated data from the server. +/// If code/vault haven't changed, uses the local versions. async fn fetch_wallet_account( rpc_client: &mut RpcClient, - account_id: AccountId, + local_account: &Account, ) -> Result> { + use miden_objects::account::{AccountCode, AccountStorage, StorageSlot, StorageSlotContent}; + use miden_objects::asset::AssetVault; + use miden_objects::utils::Deserializable; + + let account_id = local_account.id(); let id_bytes: [u8; 15] = account_id.into(); let account_id_proto = miden_node_proto::generated::account::AccountId { id: id_bytes.to_vec() }; - // Request account header to check if account exists and get nonce + // Pass local commitments - server returns data only if it changed let request = miden_node_proto::generated::rpc::AccountRequest { account_id: Some(account_id_proto), block_num: None, - details: None, + details: Some(miden_node_proto::generated::rpc::account_request::AccountDetailRequest { + code_commitment: Some(local_account.code().commitment().into()), + asset_vault_commitment: Some(local_account.vault().root().into()), + storage_maps: vec![], + }), }; match rpc_client.get_account(request).await { Ok(response) => { let resp = response.into_inner(); - if resp.witness.is_some() { - info!( - account.id = %account_id, - "wallet found on-chain but cannot reconstruct full account from RPC response" - ); + let Some(details) = resp.details else { + return Ok(None); + }; + + // Extract account header for nonce + let header = details.header.context("missing account header")?; + let nonce: u64 = header.nonce; + + // Use updated code if provided, otherwise keep local + let code = if let Some(code_bytes) = details.code { + AccountCode::read_from_bytes(&code_bytes) + .context("failed to deserialize account code")? + } else { + local_account.code().clone() + }; + + // Use updated vault if provided, otherwise keep local + let vault = if let Some(vault_details) = details.vault_details { + if vault_details.too_many_assets { + warn!( + account.id = %account_id, + "account has too many assets, using local vault" + ); + local_account.vault().clone() + } else { + let assets: Vec = vault_details + .assets + .into_iter() + .map(TryInto::try_into) + .collect::>() + .context("failed to convert assets")?; + AssetVault::new(&assets).context("failed to create vault")? + } + } else { + local_account.vault().clone() + }; + + // Reconstruct storage from header, using local account's maps for map slots + let storage_details = details.storage_details.context("missing storage details")?; + let storage_header = storage_details.header.context("missing storage header")?; + let local_slots = local_account.storage().slots(); + + let mut slots = Vec::new(); + for (idx, slot) in storage_header.slots.into_iter().enumerate() { + let slot_name = + miden_objects::account::StorageSlotName::new(slot.slot_name.clone()) + .context("invalid slot name")?; + let commitment: Word = slot + .commitment + .context("missing slot commitment")? + .try_into() + .context("invalid slot commitment")?; + + // slot_type: 0 = Value, 1 = Map + let storage_slot = if slot.slot_type == 0 { + StorageSlot::with_value(slot_name, commitment) + } else { + // For map slots, use the local account's map data + let local_slot = local_slots.get(idx).context("slot index out of bounds")?; + if let StorageSlotContent::Map(local_map) = local_slot.content() { + StorageSlot::with_map(slot_name, local_map.clone()) + } else { + // Local slot is not a map but remote says it is - unexpected + warn!( + account.id = %account_id, + slot_index = idx, + "remote storage slot is Map but local is Value, using empty map" + ); + StorageSlot::with_map( + slot_name, + miden_objects::account::StorageMap::default(), + ) + } + }; + slots.push(storage_slot); } - // We can't reconstruct the full Account without the actual code, vault, and storage - // data The RPC returns only commitments, so we fall back to the file-based account - Ok(None) + + let storage = AccountStorage::new(slots).context("failed to create account storage")?; + + let account = Account::new(account_id, vault, storage, code, Felt::new(nonce), None) + .context("failed to create account")?; + + info!(account.id = %account_id, "reconstructed wallet account from RPC"); + Ok(Some(account)) }, Err(e) => { warn!( @@ -199,7 +275,7 @@ async fn setup_increment_task( // Load accounts from files let wallet_account_file = AccountFile::read(config.wallet_filepath).context("Failed to read wallet account file")?; - let wallet_account = fetch_wallet_account(rpc_client, wallet_account_file.account.id()) + let wallet_account = fetch_wallet_account(rpc_client, &wallet_account_file.account) .await? .unwrap_or(wallet_account_file.account.clone()); diff --git a/crates/proto/src/domain/account.rs b/crates/proto/src/domain/account.rs index 8038d3b7f..2cf8ecb76 100644 --- a/crates/proto/src/domain/account.rs +++ b/crates/proto/src/domain/account.rs @@ -265,17 +265,23 @@ pub enum SlotData { MapKeys(Vec), } -impl TryFrom - for SlotData +impl + TryFrom< + proto::rpc::account_request::account_detail_request::storage_map_detail_request::SlotData, + > for SlotData { type Error = ConversionError; - fn try_from(value: proto::rpc::account_request::account_detail_request::storage_map_detail_request::SlotData) -> Result { + fn try_from( + value: proto::rpc::account_request::account_detail_request::storage_map_detail_request::SlotData, + ) -> Result { use proto::rpc::account_request::account_detail_request::storage_map_detail_request::SlotData as ProtoSlotData; Ok(match value { ProtoSlotData::AllEntries(true) => SlotData::All, - ProtoSlotData::AllEntries(false) => return Err(ConversionError::EnumDiscriminantOutOfRange), + ProtoSlotData::AllEntries(false) => { + return Err(ConversionError::EnumDiscriminantOutOfRange); + }, ProtoSlotData::MapKeys(keys) => { let keys = try_convert(keys.map_keys).collect::, _>>()?; SlotData::MapKeys(keys) @@ -683,9 +689,7 @@ impl From for proto::rpc::AccountResponse { impl TryFrom for AccountDetails { type Error = ConversionError; - fn try_from( - value: proto::rpc::account_response::AccountDetails, - ) -> Result { + fn try_from(value: proto::rpc::account_response::AccountDetails) -> Result { let proto::rpc::account_response::AccountDetails { header, code, @@ -694,9 +698,7 @@ impl TryFrom for AccountDetails { } = value; let account_header = header - .ok_or(proto::rpc::account_response::AccountDetails::missing_field(stringify!( - header - )))? + .ok_or(proto::rpc::account_response::AccountDetails::missing_field(stringify!(header)))? .try_into()?; let storage_details = storage_details From 687a4a16ebcc76c7830acf655e6c9e71b2c4a182 Mon Sep 17 00:00:00 2001 From: Bernhard Schuster Date: Tue, 23 Dec 2025 20:45:03 +0100 Subject: [PATCH 05/12] fixup changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 10dc6274d..959d152ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,7 @@ ### Changes -- [BREAKING] Removed `GetAccountDetails` RPC endpoint. Use `GetAccountProof` instead ([#1185](https://github.com/0xMiden/miden-node/issues/1185)). +- [BREAKING] Removed `GetAccountDetails` RPC endpoint. Use `GetAccount` instead ([#1185](https://github.com/0xMiden/miden-node/issues/1185)). - [BREAKING] Renamed `SyncTransactions` response fields ([#1357](https://github.com/0xMiden/miden-node/pull/1357)). - Normalize response size in endpoints to 4 MB ([#1357](https://github.com/0xMiden/miden-node/pull/1357)). - [BREAKING] Renamed `ProxyWorkerStatus::address` to `ProxyWorkerStatus::name` ([#1348](https://github.com/0xMiden/miden-node/pull/1348)). From f000d5de94f6aa1f438a110763f3a9dcf3269fa8 Mon Sep 17 00:00:00 2001 From: Bernhard Schuster Date: Tue, 23 Dec 2025 20:59:09 +0100 Subject: [PATCH 06/12] counter fetch wallet account impl --- bin/network-monitor/src/counter.rs | 248 +++++++++++++++++------------ 1 file changed, 147 insertions(+), 101 deletions(-) diff --git a/bin/network-monitor/src/counter.rs b/bin/network-monitor/src/counter.rs index a7f6fa6a6..f83c1eb72 100644 --- a/bin/network-monitor/src/counter.rs +++ b/bin/network-monitor/src/counter.rs @@ -132,131 +132,177 @@ async fn fetch_counter_value( Ok(None) } -/// Fetch the wallet account from RPC and reconstruct the full Account. +/// Fetch an account from RPC and reconstruct the full Account. /// -/// Uses the local account's commitments to request updated data from the server. -/// If code/vault haven't changed, uses the local versions. +/// Uses dummy commitments to force the server to return all data (code, vault, storage header). +/// Storage map slots will have empty maps since we don't request map entries. async fn fetch_wallet_account( rpc_client: &mut RpcClient, - local_account: &Account, + account_id: AccountId, ) -> Result> { - use miden_objects::account::{AccountCode, AccountStorage, StorageSlot, StorageSlotContent}; + use miden_objects::account::AccountCode; use miden_objects::asset::AssetVault; use miden_objects::utils::Deserializable; - let account_id = local_account.id(); let id_bytes: [u8; 15] = account_id.into(); let account_id_proto = miden_node_proto::generated::account::AccountId { id: id_bytes.to_vec() }; - // Pass local commitments - server returns data only if it changed + // Pass dummy commitments to force server to return code and vault + // (server returns data only if commitment differs from what we send) + let dummy_commitment: miden_node_proto::generated::primitives::Digest = Word::default().into(); + let request = miden_node_proto::generated::rpc::AccountRequest { account_id: Some(account_id_proto), block_num: None, details: Some(miden_node_proto::generated::rpc::account_request::AccountDetailRequest { - code_commitment: Some(local_account.code().commitment().into()), - asset_vault_commitment: Some(local_account.vault().root().into()), + code_commitment: Some(dummy_commitment), + asset_vault_commitment: Some(dummy_commitment), storage_maps: vec![], }), }; - match rpc_client.get_account(request).await { - Ok(response) => { - let resp = response.into_inner(); - let Some(details) = resp.details else { - return Ok(None); - }; - - // Extract account header for nonce - let header = details.header.context("missing account header")?; - let nonce: u64 = header.nonce; - - // Use updated code if provided, otherwise keep local - let code = if let Some(code_bytes) = details.code { - AccountCode::read_from_bytes(&code_bytes) - .context("failed to deserialize account code")? - } else { - local_account.code().clone() - }; - - // Use updated vault if provided, otherwise keep local - let vault = if let Some(vault_details) = details.vault_details { - if vault_details.too_many_assets { - warn!( - account.id = %account_id, - "account has too many assets, using local vault" - ); - local_account.vault().clone() - } else { - let assets: Vec = vault_details - .assets - .into_iter() - .map(TryInto::try_into) - .collect::>() - .context("failed to convert assets")?; - AssetVault::new(&assets).context("failed to create vault")? - } - } else { - local_account.vault().clone() - }; - - // Reconstruct storage from header, using local account's maps for map slots - let storage_details = details.storage_details.context("missing storage details")?; - let storage_header = storage_details.header.context("missing storage header")?; - let local_slots = local_account.storage().slots(); - - let mut slots = Vec::new(); - for (idx, slot) in storage_header.slots.into_iter().enumerate() { - let slot_name = - miden_objects::account::StorageSlotName::new(slot.slot_name.clone()) - .context("invalid slot name")?; - let commitment: Word = slot - .commitment - .context("missing slot commitment")? - .try_into() - .context("invalid slot commitment")?; - - // slot_type: 0 = Value, 1 = Map - let storage_slot = if slot.slot_type == 0 { - StorageSlot::with_value(slot_name, commitment) - } else { - // For map slots, use the local account's map data - let local_slot = local_slots.get(idx).context("slot index out of bounds")?; - if let StorageSlotContent::Map(local_map) = local_slot.content() { - StorageSlot::with_map(slot_name, local_map.clone()) - } else { - // Local slot is not a map but remote says it is - unexpected - warn!( - account.id = %account_id, - slot_index = idx, - "remote storage slot is Map but local is Value, using empty map" - ); - StorageSlot::with_map( - slot_name, - miden_objects::account::StorageMap::default(), - ) - } - }; - slots.push(storage_slot); - } + let response = match rpc_client.get_account(request).await { + Ok(response) => response.into_inner(), + Err(e) => { + warn!(account.id = %account_id, err = %e, "failed to fetch wallet account via RPC"); + return Ok(None); + }, + }; + + let Some(details) = response.details else { + return Ok(None); + }; - let storage = AccountStorage::new(slots).context("failed to create account storage")?; + let header = details.header.context("missing account header")?; + let nonce: u64 = header.nonce; - let account = Account::new(account_id, vault, storage, code, Felt::new(nonce), None) - .context("failed to create account")?; + let code = details + .code + .map(|code_bytes| AccountCode::read_from_bytes(&code_bytes)) + .transpose() + .context("failed to deserialize account code")? + .context("server did not return account code")?; - info!(account.id = %account_id, "reconstructed wallet account from RPC"); - Ok(Some(account)) + let vault = match details.vault_details { + Some(vault_details) if vault_details.too_many_assets => { + anyhow::bail!("account {account_id} has too many assets, cannot fetch full account"); }, - Err(e) => { - warn!( - account.id = %account_id, - err = %e, - "failed to fetch wallet account via RPC" - ); - Ok(None) + Some(vault_details) => { + let assets: Vec = vault_details + .assets + .into_iter() + .map(TryInto::try_into) + .collect::>() + .context("failed to convert assets")?; + AssetVault::new(&assets).context("failed to create vault")? }, + None => anyhow::bail!("server did not return asset vault for account {account_id}"), + }; + + let storage_details = details.storage_details.context("missing storage details")?; + let storage = build_account_storage(account_id, storage_details)?; + + let account = Account::new(account_id, vault, storage, code, Felt::new(nonce), None) + .context("failed to create account")?; + + // Sanity check: verify reconstructed account matches header commitments + let expected_code_commitment: Word = header + .code_commitment + .context("missing code commitment in header")? + .try_into() + .context("invalid code commitment")?; + let expected_vault_root: Word = header + .vault_root + .context("missing vault root in header")? + .try_into() + .context("invalid vault root")?; + let expected_storage_commitment: Word = header + .storage_commitment + .context("missing storage commitment in header")? + .try_into() + .context("invalid storage commitment")?; + + anyhow::ensure!( + account.code().commitment() == expected_code_commitment, + "code commitment mismatch: rebuilt={:?}, expected={:?}", + account.code().commitment(), + expected_code_commitment + ); + anyhow::ensure!( + account.vault().root() == expected_vault_root, + "vault root mismatch: rebuilt={:?}, expected={:?}", + account.vault().root(), + expected_vault_root + ); + anyhow::ensure!( + account.storage().to_commitment() == expected_storage_commitment, + "storage commitment mismatch: rebuilt={:?}, expected={:?}", + account.storage().to_commitment(), + expected_storage_commitment + ); + + info!(account.id = %account_id, "fetched wallet account from RPC"); + Ok(Some(account)) +} + +/// Build account storage from the storage details returned by the server. +fn build_account_storage( + account_id: AccountId, + storage_details: miden_node_proto::generated::rpc::AccountStorageDetails, +) -> Result { + use miden_objects::account::{AccountStorage, StorageSlot}; + + let storage_header = storage_details.header.context("missing storage header")?; + + // Build a map of slot_name -> map entries from the response + let map_entries: std::collections::HashMap> = storage_details + .map_details + .into_iter() + .filter_map(|map_detail| { + let entries = map_detail.entries?.entries; + let converted: Vec<(Word, Word)> = entries + .into_iter() + .filter_map(|e| { + let key: Word = e.key?.try_into().ok()?; + let value: Word = e.value?.try_into().ok()?; + Some((key, value)) + }) + .collect(); + Some((map_detail.slot_name, converted)) + }) + .collect(); + + let mut slots = Vec::new(); + for slot in storage_header.slots { + let slot_name = miden_objects::account::StorageSlotName::new(slot.slot_name.clone()) + .context("invalid slot name")?; + let commitment: Word = slot + .commitment + .context("missing slot commitment")? + .try_into() + .context("invalid slot commitment")?; + + // slot_type: 0 = Value, 1 = Map + let storage_slot = if slot.slot_type == 0 { + StorageSlot::with_value(slot_name, commitment) + } else { + let entries = map_entries.get(&slot.slot_name).cloned().unwrap_or_default(); + if entries.is_empty() { + warn!( + account.id = %account_id, + slot_name = %slot.slot_name, + "storage map slot has no entries (not requested or empty)" + ); + } + let storage_map = miden_objects::account::StorageMap::with_entries(entries) + .context("failed to create storage map")?; + StorageSlot::with_map(slot_name, storage_map) + }; + slots.push(storage_slot); } + + AccountStorage::new(slots).context("failed to create account storage") } async fn setup_increment_task( @@ -275,7 +321,7 @@ async fn setup_increment_task( // Load accounts from files let wallet_account_file = AccountFile::read(config.wallet_filepath).context("Failed to read wallet account file")?; - let wallet_account = fetch_wallet_account(rpc_client, &wallet_account_file.account) + let wallet_account = fetch_wallet_account(rpc_client, wallet_account_file.account.id()) .await? .unwrap_or(wallet_account_file.account.clone()); From d2211e324aef707fd4db8e0abf95b21cd40c7dfa Mon Sep 17 00:00:00 2001 From: Bernhard Schuster Date: Tue, 23 Dec 2025 21:14:29 +0100 Subject: [PATCH 07/12] review comments --- bin/network-monitor/src/counter.rs | 23 ++++++++++++++--------- crates/proto/src/generated/account.rs | 6 ++++-- proto/proto/types/account.proto | 6 ++++-- 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/bin/network-monitor/src/counter.rs b/bin/network-monitor/src/counter.rs index f83c1eb72..310e5ed6b 100644 --- a/bin/network-monitor/src/counter.rs +++ b/bin/network-monitor/src/counter.rs @@ -114,17 +114,16 @@ async fn fetch_counter_value( // The counter value is in the first storage slot (index 0) let first_slot = storage_header.slots.first().context("no storage slots found")?; - let commitment = - first_slot.commitment.as_ref().context("missing storage slot commitment")?; - - // The commitment is a Word containing the counter value - // For a value slot, the Word directly contains the value - let word: Word = - (*commitment).try_into().context("failed to convert commitment to word")?; + // For value slots, the slot data is the value itself (not a hash commitment) + let slot_value: Word = first_slot + .commitment + .as_ref() + .context("missing storage slot value")? + .try_into() + .context("failed to convert slot value to word")?; // The counter value is stored in the last element of the Word - // A Word always has 4 elements, so this is safe - let value = word.as_elements().last().expect("word always has 4 elements").as_int(); + let value = slot_value.as_elements().last().expect("word has 4 elements").as_int(); return Ok(Some(value)); } @@ -171,6 +170,12 @@ async fn fetch_wallet_account( }; let Some(details) = response.details else { + if response.witness.is_some() { + info!( + account.id = %account_id, + "account found on-chain but cannot reconstruct full account from RPC response" + ); + } return Ok(None); }; diff --git a/crates/proto/src/generated/account.rs b/crates/proto/src/generated/account.rs index e9733c015..8ad007508 100644 --- a/crates/proto/src/generated/account.rs +++ b/crates/proto/src/generated/account.rs @@ -27,7 +27,7 @@ pub struct AccountSummary { /// Represents the storage header of an account. #[derive(Clone, PartialEq, ::prost::Message)] pub struct AccountStorageHeader { - /// Storage slots with their types and commitments. + /// Storage slots with their types and data. #[prost(message, repeated, tag = "1")] pub slots: ::prost::alloc::vec::Vec, } @@ -42,7 +42,9 @@ pub mod account_storage_header { /// The type of the storage slot. #[prost(uint32, tag = "2")] pub slot_type: u32, - /// The commitment (Word) for this storage slot. + /// The data (Word) for this storage slot. + /// For value slots (slot_type=0), this is the actual value stored in the slot. + /// For map slots (slot_type=1), this is the root commitment of the storage map. #[prost(message, optional, tag = "3")] pub commitment: ::core::option::Option, } diff --git a/proto/proto/types/account.proto b/proto/proto/types/account.proto index f668d54b0..e04239c5a 100644 --- a/proto/proto/types/account.proto +++ b/proto/proto/types/account.proto @@ -38,11 +38,13 @@ message AccountStorageHeader { // The type of the storage slot. uint32 slot_type = 2; - // The commitment (Word) for this storage slot. + // The data (Word) for this storage slot. + // For value slots (slot_type=0), this is the actual value stored in the slot. + // For map slots (slot_type=1), this is the root commitment of the storage map. primitives.Digest commitment = 3; } - // Storage slots with their types and commitments. + // Storage slots with their types and data. repeated StorageSlot slots = 1; } From 84f3bb252f0b5ec94fb6dd0dceea77dee113fc22 Mon Sep 17 00:00:00 2001 From: Bernhard Schuster Date: Mon, 29 Dec 2025 20:14:58 +0100 Subject: [PATCH 08/12] y --- bin/network-monitor/src/counter.rs | 122 ++++++++++++----------------- crates/store/src/server/rpc_api.rs | 4 +- crates/store/src/state.rs | 11 ++- proto/proto/types/account.proto | 2 +- 4 files changed, 58 insertions(+), 81 deletions(-) diff --git a/bin/network-monitor/src/counter.rs b/bin/network-monitor/src/counter.rs index 310e5ed6b..a8c9d14ee 100644 --- a/bin/network-monitor/src/counter.rs +++ b/bin/network-monitor/src/counter.rs @@ -88,20 +88,8 @@ async fn fetch_counter_value( rpc_client: &mut RpcClient, account_id: AccountId, ) -> Result> { - let id_bytes: [u8; 15] = account_id.into(); - let account_id_proto = - miden_node_proto::generated::account::AccountId { id: id_bytes.to_vec() }; - - // Request account details with storage header (but no storage maps needed) - let request = miden_node_proto::generated::rpc::AccountRequest { - account_id: Some(account_id_proto), - block_num: None, - details: Some(miden_node_proto::generated::rpc::account_request::AccountDetailRequest { - code_commitment: None, - asset_vault_commitment: None, - storage_maps: vec![], - }), - }; + // Request account details with storage header (but no code or vault needed) + let request = build_account_request(account_id, false); let resp = rpc_client.get_account(request).await?.into_inner(); @@ -111,10 +99,9 @@ async fn fetch_counter_value( let storage_header = storage_details.header.context("missing storage header")?; - // The counter value is in the first storage slot (index 0) let first_slot = storage_header.slots.first().context("no storage slots found")?; - // For value slots, the slot data is the value itself (not a hash commitment) + // The counter value is stored as a Word, with the actual u64 value in the last element let slot_value: Word = first_slot .commitment .as_ref() @@ -122,8 +109,7 @@ async fn fetch_counter_value( .try_into() .context("failed to convert slot value to word")?; - // The counter value is stored in the last element of the Word - let value = slot_value.as_elements().last().expect("word has 4 elements").as_int(); + let value = slot_value.as_elements().last().expect("Word has 4 elements").as_int(); return Ok(Some(value)); } @@ -131,35 +117,49 @@ async fn fetch_counter_value( Ok(None) } -/// Fetch an account from RPC and reconstruct the full Account. +/// Build an account request for the given account ID. /// -/// Uses dummy commitments to force the server to return all data (code, vault, storage header). -/// Storage map slots will have empty maps since we don't request map entries. -async fn fetch_wallet_account( - rpc_client: &mut RpcClient, +/// If `include_code_and_vault` is true, uses dummy commitments to force the server +/// to return code and vault data (server only returns data when our commitment differs). +fn build_account_request( account_id: AccountId, -) -> Result> { - use miden_objects::account::AccountCode; - use miden_objects::asset::AssetVault; - use miden_objects::utils::Deserializable; - + include_code_and_vault: bool, +) -> miden_node_proto::generated::rpc::AccountRequest { let id_bytes: [u8; 15] = account_id.into(); let account_id_proto = miden_node_proto::generated::account::AccountId { id: id_bytes.to_vec() }; - // Pass dummy commitments to force server to return code and vault - // (server returns data only if commitment differs from what we send) - let dummy_commitment: miden_node_proto::generated::primitives::Digest = Word::default().into(); + let (code_commitment, asset_vault_commitment) = if include_code_and_vault { + let dummy: miden_node_proto::generated::primitives::Digest = Word::default().into(); + (Some(dummy), Some(dummy)) + } else { + (None, None) + }; - let request = miden_node_proto::generated::rpc::AccountRequest { + miden_node_proto::generated::rpc::AccountRequest { account_id: Some(account_id_proto), block_num: None, details: Some(miden_node_proto::generated::rpc::account_request::AccountDetailRequest { - code_commitment: Some(dummy_commitment), - asset_vault_commitment: Some(dummy_commitment), + code_commitment, + asset_vault_commitment, storage_maps: vec![], }), - }; + } +} + +/// Fetch an account from RPC and reconstruct the full Account. +/// +/// Uses dummy commitments to force the server to return all data (code, vault, storage header). +/// Only supports accounts with value slots; returns an error if storage maps are present. +async fn fetch_wallet_account( + rpc_client: &mut RpcClient, + account_id: AccountId, +) -> Result> { + use miden_objects::account::AccountCode; + use miden_objects::asset::AssetVault; + use miden_objects::utils::Deserializable; + + let request = build_account_request(account_id, true); let response = match rpc_client.get_account(request).await { Ok(response) => response.into_inner(), @@ -206,7 +206,7 @@ async fn fetch_wallet_account( }; let storage_details = details.storage_details.context("missing storage details")?; - let storage = build_account_storage(account_id, storage_details)?; + let storage = build_account_storage(storage_details)?; let account = Account::new(account_id, vault, storage, code, Felt::new(nonce), None) .context("failed to create account")?; @@ -252,59 +252,33 @@ async fn fetch_wallet_account( } /// Build account storage from the storage details returned by the server. +/// +/// This function only supports accounts with value slots. If any storage map slots +/// are encountered, an error is returned since the monitor only uses simple accounts. fn build_account_storage( - account_id: AccountId, storage_details: miden_node_proto::generated::rpc::AccountStorageDetails, ) -> Result { use miden_objects::account::{AccountStorage, StorageSlot}; let storage_header = storage_details.header.context("missing storage header")?; - // Build a map of slot_name -> map entries from the response - let map_entries: std::collections::HashMap> = storage_details - .map_details - .into_iter() - .filter_map(|map_detail| { - let entries = map_detail.entries?.entries; - let converted: Vec<(Word, Word)> = entries - .into_iter() - .filter_map(|e| { - let key: Word = e.key?.try_into().ok()?; - let value: Word = e.value?.try_into().ok()?; - Some((key, value)) - }) - .collect(); - Some((map_detail.slot_name, converted)) - }) - .collect(); - let mut slots = Vec::new(); for slot in storage_header.slots { let slot_name = miden_objects::account::StorageSlotName::new(slot.slot_name.clone()) .context("invalid slot name")?; - let commitment: Word = slot + let value: Word = slot .commitment - .context("missing slot commitment")? + .context("missing slot value")? .try_into() - .context("invalid slot commitment")?; + .context("invalid slot value")?; // slot_type: 0 = Value, 1 = Map - let storage_slot = if slot.slot_type == 0 { - StorageSlot::with_value(slot_name, commitment) - } else { - let entries = map_entries.get(&slot.slot_name).cloned().unwrap_or_default(); - if entries.is_empty() { - warn!( - account.id = %account_id, - slot_name = %slot.slot_name, - "storage map slot has no entries (not requested or empty)" - ); - } - let storage_map = miden_objects::account::StorageMap::with_entries(entries) - .context("failed to create storage map")?; - StorageSlot::with_map(slot_name, storage_map) - }; - slots.push(storage_slot); + anyhow::ensure!( + slot.slot_type == 0, + "storage map slots are not supported for this account" + ); + + slots.push(StorageSlot::with_value(slot_name, value)); } AccountStorage::new(slots).context("failed to create account storage") diff --git a/crates/store/src/server/rpc_api.rs b/crates/store/src/server/rpc_api.rs index 25425871d..ad81f4082 100644 --- a/crates/store/src/server/rpc_api.rs +++ b/crates/store/src/server/rpc_api.rs @@ -348,9 +348,9 @@ impl rpc_server::Rpc for StoreApi { let request = request.into_inner(); let account_request = request.try_into()?; - let proof = self.state.get_account(account_request).await?; + let account_data = self.state.get_account(account_request).await?; - Ok(Response::new(proof.into())) + Ok(Response::new(account_data.into())) } #[instrument( diff --git a/crates/store/src/state.rs b/crates/store/src/state.rs index c8d6826e8..8bc513fae 100644 --- a/crates/store/src/state.rs +++ b/crates/store/src/state.rs @@ -1070,11 +1070,14 @@ impl State { self.db.select_all_network_account_ids().await } - /// Returns the respective account proof with optional details, such as asset and storage - /// entries. + /// Returns an account witness (Merkle proof of inclusion in the account tree) and optionally + /// the account details (header, code, vault, and storage) at a specific block. /// - /// When `block_num` is provided, this method will return the account state at that specific - /// block using both the historical account tree witness and historical database state. + /// The witness proves the account's state commitment in the account tree. If `details` is + /// requested, the method also returns the account's code, vault assets, and storage data. + /// + /// If `block_num` is provided, returns the state at that historical block; otherwise, returns + /// the latest state. pub async fn get_account( &self, account_request: AccountRequest, diff --git a/proto/proto/types/account.proto b/proto/proto/types/account.proto index e04239c5a..c3837b76b 100644 --- a/proto/proto/types/account.proto +++ b/proto/proto/types/account.proto @@ -40,7 +40,7 @@ message AccountStorageHeader { // The data (Word) for this storage slot. // For value slots (slot_type=0), this is the actual value stored in the slot. - // For map slots (slot_type=1), this is the root commitment of the storage map. + // For map slots (slot_type=1), this is the root of the storage map. primitives.Digest commitment = 3; } From 4ecb76c95be8e5ac4a6a93d35a9f27889222e235 Mon Sep 17 00:00:00 2001 From: Bernhard Schuster Date: Tue, 30 Dec 2025 01:58:49 +0100 Subject: [PATCH 09/12] review --- bin/network-monitor/src/counter.rs | 52 ++++++++++++++++----------- crates/proto/src/domain/account.rs | 11 +++--- crates/proto/src/generated/account.rs | 2 +- crates/rpc/README.md | 10 ++++-- crates/store/README.md | 10 ++++-- crates/store/src/state.rs | 14 ++++---- docs/external/src/rpc.md | 10 ++++-- 7 files changed, 66 insertions(+), 43 deletions(-) diff --git a/bin/network-monitor/src/counter.rs b/bin/network-monitor/src/counter.rs index a8c9d14ee..5337f11f7 100644 --- a/bin/network-monitor/src/counter.rs +++ b/bin/network-monitor/src/counter.rs @@ -83,38 +83,48 @@ async fn get_genesis_block_header(rpc_client: &mut RpcClient) -> Result Result> { - // Request account details with storage header (but no code or vault needed) +) -> Result> { let request = build_account_request(account_id, false); - let resp = rpc_client.get_account(request).await?.into_inner(); - // Extract the counter value from the storage header - if let Some(details) = resp.details { - let storage_details = details.storage_details.context("missing storage details")?; + let Some(details) = resp.details else { + return Ok(None); + }; - let storage_header = storage_details.header.context("missing storage header")?; + let storage_details = details.storage_details.context("missing storage details")?; + let storage_header = storage_details.header.context("missing storage header")?; - let first_slot = storage_header.slots.first().context("no storage slots found")?; + Ok(Some(storage_header)) +} - // The counter value is stored as a Word, with the actual u64 value in the last element - let slot_value: Word = first_slot - .commitment - .as_ref() - .context("missing storage slot value")? - .try_into() - .context("failed to convert slot value to word")?; +/// Fetch the latest nonce of the given account from RPC. +async fn fetch_counter_value( + rpc_client: &mut RpcClient, + account_id: AccountId, +) -> Result> { + let Some(storage_header) = fetch_account_storage_header(rpc_client, account_id).await? else { + return Ok(None); + }; - let value = slot_value.as_elements().last().expect("Word has 4 elements").as_int(); + let first_slot = storage_header.slots.first().context("no storage slots found")?; - return Ok(Some(value)); - } + // The counter value is stored as a Word, with the actual u64 value in the last element + let slot_value: Word = first_slot + .commitment + .as_ref() + .context("missing storage slot value")? + .try_into() + .context("failed to convert slot value to word")?; + + let value = slot_value.as_elements().last().expect("Word has 4 elements").as_int(); - Ok(None) + Ok(Some(value)) } /// Build an account request for the given account ID. diff --git a/crates/proto/src/domain/account.rs b/crates/proto/src/domain/account.rs index 2cf8ecb76..68bc257bd 100644 --- a/crates/proto/src/domain/account.rs +++ b/crates/proto/src/domain/account.rs @@ -246,13 +246,14 @@ impl TryFrom Result { - let proto::rpc::account_request::account_detail_request::StorageMapDetailRequest { - slot_name, - slot_data, - } = value; + use proto::rpc::account_request::account_detail_request::StorageMapDetailRequest; + + let StorageMapDetailRequest { slot_name, slot_data } = value; let slot_name = StorageSlotName::new(slot_name)?; - let slot_data = slot_data.ok_or(proto::rpc::account_request::account_detail_request::StorageMapDetailRequest::missing_field(stringify!(slot_data)))?.try_into()?; + let slot_data = slot_data + .ok_or(StorageMapDetailRequest::missing_field(stringify!(slot_data)))? + .try_into()?; Ok(StorageMapRequest { slot_name, slot_data }) } diff --git a/crates/proto/src/generated/account.rs b/crates/proto/src/generated/account.rs index 8ad007508..a3aaa712c 100644 --- a/crates/proto/src/generated/account.rs +++ b/crates/proto/src/generated/account.rs @@ -44,7 +44,7 @@ pub mod account_storage_header { pub slot_type: u32, /// The data (Word) for this storage slot. /// For value slots (slot_type=0), this is the actual value stored in the slot. - /// For map slots (slot_type=1), this is the root commitment of the storage map. + /// For map slots (slot_type=1), this is the root of the storage map. #[prost(message, optional, tag = "3")] pub commitment: ::core::option::Option, } diff --git a/crates/rpc/README.md b/crates/rpc/README.md index 408f0affb..3fd5b3e4a 100644 --- a/crates/rpc/README.md +++ b/crates/rpc/README.md @@ -16,7 +16,7 @@ The full gRPC method definitions can be found in the [proto](../proto/README.md) - [CheckNullifiers](#checknullifiers) - [SyncNullifiers](#syncnullifiers) - [GetAccountDetails](#getaccountdetails) -- [GetAccountProofs](#getaccountproofs) +- [GetAccount](#getaccount) - [GetBlockByNumber](#getblockbynumber) - [GetBlockHeaderByNumber](#getblockheaderbynumber) - [GetNotesById](#getnotesbyid) @@ -54,9 +54,13 @@ Returns the latest state of an account with the specified ID. --- -### GetAccountProofs +### GetAccount -Returns the latest state proofs of the specified accounts. +Returns an account witness (Merkle proof of inclusion in the account tree) and optionally account details. + +The witness proves the account's state commitment in the account tree. If details are requested, the response also includes the account's header, code, vault assets, and storage data. Account details are only available for public accounts. + +If `block_num` is provided, returns the state at that historical block; otherwise, returns the latest state. --- diff --git a/crates/store/README.md b/crates/store/README.md index 0b12487c1..7a1d66c95 100644 --- a/crates/store/README.md +++ b/crates/store/README.md @@ -15,7 +15,7 @@ The full gRPC API can be found [here](../../proto/proto/store.proto). - [ApplyBlock](#applyblock) - [CheckNullifiers](#checknullifiers) - [GetAccountDetails](#getaccountdetails) -- [GetAccountProofs](#getaccountproofs) +- [GetAccount](#getaccount) - [GetBlockByNumber](#getblockbynumber) - [GetBlockHeaderByNumber](#getblockheaderbynumber) - [GetBlockInputs](#getblockinputs) @@ -61,9 +61,13 @@ Returns the latest state of an account with the specified ID. --- -### GetAccountProofs +### GetAccount -Returns the latest state proofs of the specified accounts. +Returns an account witness (Merkle proof of inclusion in the account tree) and optionally account details. + +The witness proves the account's state commitment in the account tree. If details are requested, the response also includes the account's header, code, vault assets, and storage data. Account details are only available for public accounts. + +If `block_num` is provided, returns the state at that historical block; otherwise, returns the latest state. --- diff --git a/crates/store/src/state.rs b/crates/store/src/state.rs index 8bc513fae..3d18ed419 100644 --- a/crates/store/src/state.rs +++ b/crates/store/src/state.rs @@ -1070,11 +1070,11 @@ impl State { self.db.select_all_network_account_ids().await } - /// Returns an account witness (Merkle proof of inclusion in the account tree) and optionally - /// the account details (header, code, vault, and storage) at a specific block. + /// Returns an account witness and optionally account details at a specific block. /// - /// The witness proves the account's state commitment in the account tree. If `details` is - /// requested, the method also returns the account's code, vault assets, and storage data. + /// The witness is a Merkle proof of inclusion in the account tree, proving the account's + /// state commitment. If `details` is requested, the method also returns the account's code, + /// vault assets, and storage data. Account details are only available for public accounts. /// /// If `block_num` is provided, returns the state at that historical block; otherwise, returns /// the latest state. @@ -1099,10 +1099,10 @@ impl State { Ok(AccountResponse { block_num, witness, details }) } - /// Gets the block witness (account tree proof) for the specified account + /// Returns an account witness (Merkle proof of inclusion in the account tree). /// - /// If `block_num` is provided, returns the witness at that historical block, - /// if not present, returns the witness at the latest block. + /// If `block_num` is provided, returns the witness at that historical block; + /// otherwise, returns the witness at the latest block. async fn get_account_witness( &self, block_num: Option, diff --git a/docs/external/src/rpc.md b/docs/external/src/rpc.md index 1b06d0cac..165a575f6 100644 --- a/docs/external/src/rpc.md +++ b/docs/external/src/rpc.md @@ -13,7 +13,7 @@ The gRPC service definition can be found in the Miden node's `proto` [directory] - [CheckNullifiers](#checknullifiers) - [GetAccountDetails](#getaccountdetails) -- [GetAccountProofs](#getaccountproofs) +- [GetAccount](#getaccount) - [GetBlockByNumber](#getblockbynumber) - [GetBlockHeaderByNumber](#getblockheaderbynumber) - [GetNotesById](#getnotesbyid) @@ -101,9 +101,13 @@ match proof.verify_unset(&nullifier, &nullifier_tree_root) { Request the latest state of an account. -### GetAccountProofs +### GetAccount -Request state proofs for accounts, including specific storage slots. +Request an account witness (Merkle proof of inclusion in the account tree) and optionally account details. + +The witness proves the account's state commitment in the account tree. If details are requested, the response also includes the account's header, code, vault assets, and storage data. Account details are only available for public accounts. + +If `block_num` is provided, returns the state at that historical block; otherwise, returns the latest state. ### GetBlockByNumber From b085793dae93ec7dadd4cb84df903fb73967cc99 Mon Sep 17 00:00:00 2001 From: Bernhard Schuster Date: Sat, 3 Jan 2026 04:56:49 +0100 Subject: [PATCH 10/12] y --- crates/rpc/README.md | 7 ------- docs/external/src/rpc.md | 5 ----- 2 files changed, 12 deletions(-) diff --git a/crates/rpc/README.md b/crates/rpc/README.md index 3c18152ef..e3f1a6018 100644 --- a/crates/rpc/README.md +++ b/crates/rpc/README.md @@ -15,7 +15,6 @@ The full gRPC method definitions can be found in the [proto](../proto/README.md) - [CheckNullifiers](#checknullifiers) - [SyncNullifiers](#syncnullifiers) -- [GetAccountDetails](#getaccountdetails) - [GetAccount](#getaccount) - [GetBlockByNumber](#getblockbynumber) - [GetBlockHeaderByNumber](#getblockheaderbynumber) @@ -51,12 +50,6 @@ When nullifier checking fails, detailed error information is provided through gR --- -### GetAccountDetails - -Returns the latest state of an account with the specified ID. - ---- - ### GetAccount Returns an account witness (Merkle proof of inclusion in the account tree) and optionally account details. diff --git a/docs/external/src/rpc.md b/docs/external/src/rpc.md index 363f7ac51..47706de3b 100644 --- a/docs/external/src/rpc.md +++ b/docs/external/src/rpc.md @@ -12,7 +12,6 @@ The gRPC service definition can be found in the Miden node's `proto` [directory] - [CheckNullifiers](#checknullifiers) -- [GetAccountDetails](#getaccountdetails) - [GetAccount](#getaccount) - [GetBlockByNumber](#getblockbynumber) - [GetBlockHeaderByNumber](#getblockheaderbynumber) @@ -100,10 +99,6 @@ match proof.verify_unset(&nullifier, &nullifier_tree_root) { **Limits:** `nullifier` (1000) -### GetAccountDetails - -Request the latest state of an account. - ### GetAccount Request an account witness (Merkle proof of inclusion in the account tree) and optionally account details. From 39da912d9cf0edb732d8c9e93f698231056a26e4 Mon Sep 17 00:00:00 2001 From: Bernhard Schuster Date: Sat, 3 Jan 2026 05:17:41 +0100 Subject: [PATCH 11/12] merge fuckup --- crates/proto/src/generated/rpc.rs | 77 ---------------------------- crates/proto/src/generated/store.rs | 78 ----------------------------- crates/rpc/src/server/api.rs | 26 ---------- crates/store/README.md | 7 --- crates/store/src/server/rpc_api.rs | 24 --------- proto/proto/internal/store.proto | 3 -- proto/proto/rpc.proto | 3 -- 7 files changed, 218 deletions(-) diff --git a/crates/proto/src/generated/rpc.rs b/crates/proto/src/generated/rpc.rs index 3bd7283cd..caa825813 100644 --- a/crates/proto/src/generated/rpc.rs +++ b/crates/proto/src/generated/rpc.rs @@ -747,30 +747,6 @@ pub mod api_client { req.extensions_mut().insert(GrpcMethod::new("rpc.Api", "CheckNullifiers")); self.inner.unary(req, path, codec).await } - /// Returns the latest state of an account with the specified ID. - pub async fn get_account_details( - &mut self, - request: impl tonic::IntoRequest, - ) -> std::result::Result< - tonic::Response, - tonic::Status, - > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::unknown( - format!("Service was not ready: {}", e.into()), - ) - })?; - let codec = tonic_prost::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static( - "/rpc.Api/GetAccountDetails", - ); - let mut req = request.into_request(); - req.extensions_mut().insert(GrpcMethod::new("rpc.Api", "GetAccountDetails")); - self.inner.unary(req, path, codec).await - } /// Returns the latest state proof of the specified account. pub async fn get_account( &mut self, @@ -1171,14 +1147,6 @@ pub mod api_server { tonic::Response, tonic::Status, >; - /// Returns the latest state of an account with the specified ID. - async fn get_account_details( - &self, - request: tonic::Request, - ) -> std::result::Result< - tonic::Response, - tonic::Status, - >; /// Returns the latest state proof of the specified account. async fn get_account( &self, @@ -1482,51 +1450,6 @@ pub mod api_server { }; Box::pin(fut) } - "/rpc.Api/GetAccountDetails" => { - #[allow(non_camel_case_types)] - struct GetAccountDetailsSvc(pub Arc); - impl< - T: Api, - > tonic::server::UnaryService - for GetAccountDetailsSvc { - type Response = super::super::account::AccountDetails; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; - fn call( - &mut self, - request: tonic::Request, - ) -> Self::Future { - let inner = Arc::clone(&self.0); - let fut = async move { - ::get_account_details(&inner, request).await - }; - Box::pin(fut) - } - } - let accept_compression_encodings = self.accept_compression_encodings; - let send_compression_encodings = self.send_compression_encodings; - let max_decoding_message_size = self.max_decoding_message_size; - let max_encoding_message_size = self.max_encoding_message_size; - let inner = self.inner.clone(); - let fut = async move { - let method = GetAccountDetailsSvc(inner); - let codec = tonic_prost::ProstCodec::default(); - let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config( - accept_compression_encodings, - send_compression_encodings, - ) - .apply_max_message_size_config( - max_decoding_message_size, - max_encoding_message_size, - ); - let res = grpc.unary(method, req).await; - Ok(res) - }; - Box::pin(fut) - } "/rpc.Api/GetAccount" => { #[allow(non_camel_case_types)] struct GetAccountSvc(pub Arc); diff --git a/crates/proto/src/generated/store.rs b/crates/proto/src/generated/store.rs index 4d86c767e..7c55601c3 100644 --- a/crates/proto/src/generated/store.rs +++ b/crates/proto/src/generated/store.rs @@ -370,31 +370,6 @@ pub mod rpc_client { req.extensions_mut().insert(GrpcMethod::new("store.Rpc", "CheckNullifiers")); self.inner.unary(req, path, codec).await } - /// Returns the latest state of an account with the specified ID. - pub async fn get_account_details( - &mut self, - request: impl tonic::IntoRequest, - ) -> std::result::Result< - tonic::Response, - tonic::Status, - > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::unknown( - format!("Service was not ready: {}", e.into()), - ) - })?; - let codec = tonic_prost::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static( - "/store.Rpc/GetAccountDetails", - ); - let mut req = request.into_request(); - req.extensions_mut() - .insert(GrpcMethod::new("store.Rpc", "GetAccountDetails")); - self.inner.unary(req, path, codec).await - } /// Returns the latest state proof of the specified account. pub async fn get_account( &mut self, @@ -724,14 +699,6 @@ pub mod rpc_server { tonic::Response, tonic::Status, >; - /// Returns the latest state of an account with the specified ID. - async fn get_account_details( - &self, - request: tonic::Request, - ) -> std::result::Result< - tonic::Response, - tonic::Status, - >; /// Returns the latest state proof of the specified account. async fn get_account( &self, @@ -1007,51 +974,6 @@ pub mod rpc_server { }; Box::pin(fut) } - "/store.Rpc/GetAccountDetails" => { - #[allow(non_camel_case_types)] - struct GetAccountDetailsSvc(pub Arc); - impl< - T: Rpc, - > tonic::server::UnaryService - for GetAccountDetailsSvc { - type Response = super::super::account::AccountDetails; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; - fn call( - &mut self, - request: tonic::Request, - ) -> Self::Future { - let inner = Arc::clone(&self.0); - let fut = async move { - ::get_account_details(&inner, request).await - }; - Box::pin(fut) - } - } - let accept_compression_encodings = self.accept_compression_encodings; - let send_compression_encodings = self.send_compression_encodings; - let max_decoding_message_size = self.max_decoding_message_size; - let max_encoding_message_size = self.max_encoding_message_size; - let inner = self.inner.clone(); - let fut = async move { - let method = GetAccountDetailsSvc(inner); - let codec = tonic_prost::ProstCodec::default(); - let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config( - accept_compression_encodings, - send_compression_encodings, - ) - .apply_max_message_size_config( - max_decoding_message_size, - max_encoding_message_size, - ); - let res = grpc.unary(method, req).await; - Ok(res) - }; - Box::pin(fut) - } "/store.Rpc/GetAccount" => { #[allow(non_camel_case_types)] struct GetAccountSvc(pub Arc); diff --git a/crates/rpc/src/server/api.rs b/crates/rpc/src/server/api.rs index ace29d928..b224d9a3c 100644 --- a/crates/rpc/src/server/api.rs +++ b/crates/rpc/src/server/api.rs @@ -16,7 +16,6 @@ use miden_node_utils::limiter::{ QueryParamNoteTagLimit, QueryParamNullifierLimit, }; -use miden_protocol::account::AccountId; use miden_protocol::batch::ProvenBatch; use miden_protocol::block::{BlockHeader, BlockNumber}; use miden_protocol::note::{Note, NoteRecipient, NoteScript}; @@ -472,31 +471,6 @@ impl api_server::Api for RpcService { block_producer.clone().submit_proven_batch(request).await } - /// Returns details for public (public) account by id. - #[instrument( - parent = None, - target = COMPONENT, - name = "rpc.server.get_account_details", - skip_all, - ret(level = "debug"), - err - )] - async fn get_account_details( - &self, - request: Request, - ) -> std::result::Result, Status> { - debug!(target: COMPONENT, request = ?request.get_ref()); - - // Validating account using conversion: - let _account_id: AccountId = request - .get_ref() - .clone() - .try_into() - .map_err(|err| Status::invalid_argument(format!("Invalid account id: {err}")))?; - - self.store.clone().get_account_details(request).await - } - #[instrument( parent = None, target = COMPONENT, diff --git a/crates/store/README.md b/crates/store/README.md index 7a1d66c95..e3f9a8dde 100644 --- a/crates/store/README.md +++ b/crates/store/README.md @@ -14,7 +14,6 @@ The full gRPC API can be found [here](../../proto/proto/store.proto). - [ApplyBlock](#applyblock) - [CheckNullifiers](#checknullifiers) -- [GetAccountDetails](#getaccountdetails) - [GetAccount](#getaccount) - [GetBlockByNumber](#getblockbynumber) - [GetBlockHeaderByNumber](#getblockheaderbynumber) @@ -55,12 +54,6 @@ When nullifier checking fails, detailed error information is provided through gR --- -### GetAccountDetails - -Returns the latest state of an account with the specified ID. - ---- - ### GetAccount Returns an account witness (Merkle proof of inclusion in the account tree) and optionally account details. diff --git a/crates/store/src/server/rpc_api.rs b/crates/store/src/server/rpc_api.rs index 077c76cda..a354a9ed9 100644 --- a/crates/store/src/server/rpc_api.rs +++ b/crates/store/src/server/rpc_api.rs @@ -1,5 +1,4 @@ use miden_node_proto::convert; -use miden_node_proto::domain::account::AccountInfo; use miden_node_proto::generated::store::rpc_server; use miden_node_proto::generated::{self as proto}; use miden_node_utils::limiter::{ @@ -282,29 +281,6 @@ impl rpc_server::Rpc for StoreApi { Ok(Response::new(proto::note::CommittedNoteList { notes })) } - /// Returns details for public (public) account by id. - #[instrument( - parent = None, - target = COMPONENT, - name = "store.rpc_server.get_account_details", - skip_all, - level = "debug", - ret(level = "debug"), - err - )] - async fn get_account_details( - &self, - request: Request, - ) -> Result, Status> { - let request = request.into_inner(); - let account_id = read_account_id::(Some(request))?; - let account_info: AccountInfo = self.state.get_account_details(account_id).await?; - - // TODO: revisit this, previous implementation was just returning only the summary, but it - // is weird since the details are not empty. - Ok(Response::new((&account_info).into())) - } - #[instrument( parent = None, target = COMPONENT, diff --git a/proto/proto/internal/store.proto b/proto/proto/internal/store.proto index 46bb69627..e364fccf2 100644 --- a/proto/proto/internal/store.proto +++ b/proto/proto/internal/store.proto @@ -31,9 +31,6 @@ service Rpc { // Verify proofs against the nullifier tree root in the latest block header. rpc CheckNullifiers(rpc.NullifierList) returns (rpc.CheckNullifiersResponse) {} - // Returns the latest state of an account with the specified ID. - rpc GetAccountDetails(account.AccountId) returns (account.AccountDetails) {} - // Returns the latest state proof of the specified account. rpc GetAccount(rpc.AccountRequest) returns (rpc.AccountResponse) {} diff --git a/proto/proto/rpc.proto b/proto/proto/rpc.proto index 260f70de4..d32459cf1 100644 --- a/proto/proto/rpc.proto +++ b/proto/proto/rpc.proto @@ -30,9 +30,6 @@ service Api { // Verify proofs against the nullifier tree root in the latest block header. rpc CheckNullifiers(NullifierList) returns (CheckNullifiersResponse) {} - // Returns the latest state of an account with the specified ID. - rpc GetAccountDetails(account.AccountId) returns (account.AccountDetails) {} - // Returns the latest state proof of the specified account. rpc GetAccount(AccountRequest) returns (AccountResponse) {} From ba9ed1b70f2ad8ef0c53f00af9440ce257503bcc Mon Sep 17 00:00:00 2001 From: Bernhard Schuster Date: Tue, 6 Jan 2026 15:33:13 +0100 Subject: [PATCH 12/12] clippy --- crates/proto/src/domain/account.rs | 22 +++++++++++----------- crates/store/src/inner_forest/mod.rs | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/crates/proto/src/domain/account.rs b/crates/proto/src/domain/account.rs index 685eb29bb..c140630d5 100644 --- a/crates/proto/src/domain/account.rs +++ b/crates/proto/src/domain/account.rs @@ -1100,16 +1100,16 @@ mod tests { #[test] fn account_storage_map_details_from_forest_entries() { let slot_name = test_slot_name(); - let entries = vec![ + let entries = [ (word_from_u32([1, 2, 3, 4]), word_from_u32([5, 6, 7, 8])), (word_from_u32([9, 10, 11, 12]), word_from_u32([13, 14, 15, 16])), ]; let details = - AccountStorageMapDetails::from_forest_entries(slot_name.clone(), entries.clone()); + AccountStorageMapDetails::from_forest_entries(slot_name.clone(), entries.to_vec()); assert_eq!(details.slot_name, slot_name); - assert_eq!(details.entries, StorageMapEntries::AllEntries(entries)); + assert_eq!(details.entries, StorageMapEntries::AllEntries(entries.to_vec())); } #[test] @@ -1136,17 +1136,17 @@ mod tests { // Create an SmtForest and populate it with some data let mut forest = SmtForest::new(); - let entries = vec![ + let entries = [ (word_from_u32([1, 0, 0, 0]), word_from_u32([10, 0, 0, 0])), (word_from_u32([2, 0, 0, 0]), word_from_u32([20, 0, 0, 0])), (word_from_u32([3, 0, 0, 0]), word_from_u32([30, 0, 0, 0])), ]; // Insert entries into the forest starting from an empty root - let smt_root = forest.batch_insert(empty_smt_root(), entries.iter().copied()).unwrap(); + let smt_root = forest.batch_insert(empty_smt_root(), entries).unwrap(); // Query specific keys - let keys = vec![word_from_u32([1, 0, 0, 0]), word_from_u32([3, 0, 0, 0])]; + let keys = [word_from_u32([1, 0, 0, 0]), word_from_u32([3, 0, 0, 0])]; let details = AccountStorageMapDetails::from_specific_keys( slot_name.clone(), @@ -1194,12 +1194,12 @@ mod tests { // Create an SmtForest with one entry so the root is tracked let mut forest = SmtForest::new(); - let entries = vec![(word_from_u32([1, 0, 0, 0]), word_from_u32([10, 0, 0, 0]))]; - let smt_root = forest.batch_insert(empty_smt_root(), entries.iter().copied()).unwrap(); + let entries = [(word_from_u32([1, 0, 0, 0]), word_from_u32([10, 0, 0, 0]))]; + let smt_root = forest.batch_insert(empty_smt_root(), entries).unwrap(); // Query a key that doesn't exist in the tree - should return a proof // (the proof will show non-membership or point to an adjacent leaf) - let keys = vec![word_from_u32([99, 0, 0, 0])]; + let keys = [word_from_u32([99, 0, 0, 0])]; let details = AccountStorageMapDetails::from_specific_keys( slot_name.clone(), @@ -1225,8 +1225,8 @@ mod tests { let mut forest = SmtForest::new(); // Create a forest with some data to get a valid root - let entries = vec![(word_from_u32([1, 0, 0, 0]), word_from_u32([10, 0, 0, 0]))]; - let smt_root = forest.batch_insert(empty_smt_root(), entries.iter().copied()).unwrap(); + let entries = [(word_from_u32([1, 0, 0, 0]), word_from_u32([10, 0, 0, 0]))]; + let smt_root = forest.batch_insert(empty_smt_root(), entries).unwrap(); // Create more keys than MAX_RETURN_ENTRIES let keys: Vec<_> = (0..AccountStorageMapDetails::MAX_RETURN_ENTRIES + 1) diff --git a/crates/store/src/inner_forest/mod.rs b/crates/store/src/inner_forest/mod.rs index 32519a90e..d4e78be3c 100644 --- a/crates/store/src/inner_forest/mod.rs +++ b/crates/store/src/inner_forest/mod.rs @@ -298,7 +298,7 @@ impl InnerForest { }; // Apply delta entries (insert or remove if value is EMPTY_WORD) - for (key, value) in delta_entries.iter() { + for (key, value) in &delta_entries { if *value == EMPTY_WORD { accumulated_entries.remove(key); } else {