diff --git a/CHANGELOG.md b/CHANGELOG.md index fb1f8476d..4d4798cd1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ ### Changes +- [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)). diff --git a/bin/network-monitor/src/counter.rs b/bin/network-monitor/src/counter.rs index 4c89c74f8..3f02a6a84 100644 --- a/bin/network-monitor/src/counter.rs +++ b/bin/network-monitor/src/counter.rs @@ -83,51 +83,216 @@ async fn get_genesis_block_header(rpc_client: &mut RpcClient) -> Result Result> { + let request = build_account_request(account_id, false); + let resp = rpc_client.get_account(request).await?.into_inner(); + + let Some(details) = resp.details else { + return Ok(None); + }; + + let storage_details = details.storage_details.context("missing storage details")?; + let storage_header = storage_details.header.context("missing storage header")?; + + Ok(Some(storage_header)) +} + /// Fetch the latest nonce of the given account from RPC. async fn fetch_counter_value( 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?.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 Some(storage_header) = fetch_account_storage_header(rpc_client, account_id).await? else { + return Ok(None); + }; + + let first_slot = storage_header.slots.first().context("no storage slots found")?; + + // 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 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 value = slot_value.as_elements().last().expect("Word has 4 elements").as_int(); - Ok(Some(value)) + Ok(Some(value)) +} + +/// Build an account request for the given account ID. +/// +/// 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, + 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() }; + + 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 { - Ok(None) + (None, None) + }; + + 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, + 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> { - 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; + use miden_protocol::account::AccountCode; + use miden_protocol::asset::AssetVault; - // If the RPC call fails, return None - if resp.is_err() { - return Ok(None); - } + let request = build_account_request(account_id, true); - let Some(account_details) = resp.expect("Previously checked for error").into_inner().details - else { + 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 { + 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); }; - let account = Account::read_from_bytes(&account_details) - .map_err(|e| anyhow::anyhow!("failed to deserialize account details: {e}"))?; + let header = details.header.context("missing account header")?; + let nonce: u64 = header.nonce; + + 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")?; + + 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"); + }, + 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(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. +/// +/// 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( + storage_details: miden_node_proto::generated::rpc::AccountStorageDetails, +) -> Result { + use miden_protocol::account::{AccountStorage, StorageSlot}; + + let storage_header = storage_details.header.context("missing storage header")?; + + let mut slots = Vec::new(); + for slot in storage_header.slots { + let slot_name = miden_protocol::account::StorageSlotName::new(slot.slot_name.clone()) + .context("invalid slot name")?; + let value: Word = slot + .commitment + .context("missing slot value")? + .try_into() + .context("invalid slot value")?; + + // slot_type: 0 = Value, 1 = Map + 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") +} + async fn setup_increment_task( config: MonitorConfig, rpc_client: &mut RpcClient, diff --git a/crates/proto/src/domain/account.rs b/crates/proto/src/domain/account.rs index ffbdb0b8f..c140630d5 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, @@ -267,21 +267,22 @@ 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 { - 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_proof_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 }) } @@ -294,17 +295,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_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, - 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) @@ -650,35 +657,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()), @@ -688,13 +695,11 @@ 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, - ) -> Result { - let proto::rpc::account_proof_response::AccountDetails { + fn try_from(value: proto::rpc::account_response::AccountDetails) -> Result { + let proto::rpc::account_response::AccountDetails { header, code, vault_details, @@ -702,19 +707,17 @@ impl TryFrom for AccountDeta } = value; let account_header = header - .ok_or(proto::rpc::account_proof_response::AccountDetails::missing_field(stringify!( - header - )))? + .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()?; @@ -729,7 +732,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, @@ -1097,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] @@ -1133,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(), @@ -1191,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(), @@ -1222,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/proto/src/generated/account.rs b/crates/proto/src/generated/account.rs index f93017b30..6ff613562 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 of the storage map. #[prost(message, optional, tag = "3")] pub commitment: ::core::option::Option, } diff --git a/crates/proto/src/generated/rpc.rs b/crates/proto/src/generated/rpc.rs index 755009e2c..caa825813 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) @@ -747,36 +747,12 @@ 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_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 @@ -788,9 +764,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. @@ -1171,22 +1147,11 @@ 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_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, @@ -1485,68 +1450,23 @@ 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/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) } @@ -1557,7 +1477,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..7c55601c3 100644 --- a/crates/proto/src/generated/store.rs +++ b/crates/proto/src/generated/store.rs @@ -370,37 +370,12 @@ 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_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 +387,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. @@ -726,20 +699,12 @@ 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_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. @@ -1009,72 +974,25 @@ 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/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 +1003,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/README.md b/crates/rpc/README.md index da30b7144..e3f1a6018 100644 --- a/crates/rpc/README.md +++ b/crates/rpc/README.md @@ -15,8 +15,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) - [GetLimits](#getlimits) @@ -51,15 +50,13 @@ When nullifier checking fails, detailed error information is provided through gR --- -### GetAccountDetails +### GetAccount -Returns the latest state of an account with the specified ID. +Returns an account witness (Merkle proof of inclusion in the account tree) and optionally account details. ---- - -### GetAccountProofs +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. -Returns the latest state proofs of the specified accounts. +If `block_num` is provided, returns the state at that historical block; otherwise, returns the latest state. --- diff --git a/crates/rpc/src/server/api.rs b/crates/rpc/src/server/api.rs index 7dc17e196..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, @@ -519,20 +493,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/README.md b/crates/store/README.md index 0b12487c1..e3f9a8dde 100644 --- a/crates/store/README.md +++ b/crates/store/README.md @@ -14,8 +14,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) @@ -55,15 +54,13 @@ When nullifier checking fails, detailed error information is provided through gR --- -### GetAccountDetails +### GetAccount -Returns the latest state of an account with the specified ID. +Returns an account witness (Merkle proof of inclusion in the account tree) and optionally account details. ---- - -### GetAccountProofs +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. -Returns the latest state proofs of the specified accounts. +If `block_num` is provided, returns the state at that historical block; otherwise, returns the latest state. --- 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 { diff --git a/crates/store/src/server/rpc_api.rs b/crates/store/src/server/rpc_api.rs index 5ac014868..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, @@ -334,23 +310,23 @@ 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 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 eed83e196..d7e8081a2 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, @@ -940,16 +940,19 @@ 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 and optionally account details 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. - pub async fn get_account_proof( + /// 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. + 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.has_public_state() { return Err(DatabaseError::AccountNotPublic(account_id)); @@ -963,13 +966,13 @@ 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 + /// 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 7aeb9a81b..47706de3b 100644 --- a/docs/external/src/rpc.md +++ b/docs/external/src/rpc.md @@ -12,8 +12,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) - [GetLimits](#getlimits) @@ -100,13 +99,13 @@ match proof.verify_unset(&nullifier, &nullifier_tree_root) { **Limits:** `nullifier` (1000) -### GetAccountDetails +### GetAccount -Request the latest state of an account. +Request an account witness (Merkle proof of inclusion in the account tree) and optionally account details. -### GetAccountProofs +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. -Request state proofs for accounts, including specific storage slots. +If `block_num` is provided, returns the state at that historical block; otherwise, returns the latest state. ### GetBlockByNumber diff --git a/proto/proto/internal/store.proto b/proto/proto/internal/store.proto index 30813e0e5..e364fccf2 100644 --- a/proto/proto/internal/store.proto +++ b/proto/proto/internal/store.proto @@ -31,11 +31,8 @@ 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 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 2918af848..d32459cf1 100644 --- a/proto/proto/rpc.proto +++ b/proto/proto/rpc.proto @@ -30,11 +30,8 @@ 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 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) {} @@ -223,7 +220,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. @@ -276,7 +273,7 @@ message AccountProofRequest { } // Represents the result of getting account proof. -message AccountProofResponse { +message AccountResponse { message AccountDetails { // Account header. @@ -303,7 +300,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 @@ -315,7 +312,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 including their proofs. diff --git a/proto/proto/types/account.proto b/proto/proto/types/account.proto index 15ae475b3..e61db64ae 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 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; }