From e6661813b5049f98b56b641879b73cdc5e18018f Mon Sep 17 00:00:00 2001 From: Bernhard Schuster Date: Wed, 8 Oct 2025 00:55:46 +0200 Subject: [PATCH 1/6] better --- crates/proto/src/domain/account.rs | 252 ++++++++++++++---------- crates/proto/src/generated/rpc_store.rs | 109 ++++++---- crates/store/src/state.rs | 5 + proto/proto/store/rpc.proto | 68 ++++--- 4 files changed, 274 insertions(+), 160 deletions(-) diff --git a/crates/proto/src/domain/account.rs b/crates/proto/src/domain/account.rs index 825fd759f..f530c7ac1 100644 --- a/crates/proto/src/domain/account.rs +++ b/crates/proto/src/domain/account.rs @@ -3,15 +3,12 @@ use std::fmt::{Debug, Display, Formatter}; use miden_node_utils::formatting::format_opt; use miden_objects::Word; use miden_objects::account::{ - Account, - AccountHeader, - AccountId, - AccountStorageHeader, - StorageMap, + Account, AccountHeader, AccountId, AccountStorageHeader, StorageMap, StorageMapWitness, StorageSlotType, }; use miden_objects::asset::{Asset, AssetVault}; use miden_objects::block::{AccountWitness, BlockNumber}; +use miden_objects::crypto::merkle::SmtProof; use miden_objects::crypto::merkle::SparseMerklePath; use miden_objects::note::{NoteExecutionMode, NoteTag}; use miden_objects::utils::{Deserializable, DeserializationError, Serializable}; @@ -192,42 +189,79 @@ impl TryFrom Result { + use proto::rpc_store::account_storage_details::account_storage_map_details::MapEntries; + let proto::rpc_store::account_storage_details::AccountStorageMapDetails { slot_index, too_many_entries, - entries, + map_entries, } = value; let slot_index = slot_index.try_into().map_err(ConversionError::TryFromIntError)?; - // Extract map_entries from the entries field - let map_entries = match entries { - Some(entries) => entries - .entries - .into_iter() - .map(|entry| { - let key = entry - .key - .ok_or(proto::rpc_store::account_storage_details::account_storage_map_details::map_entries::StorageMapEntry::missing_field( - stringify!(key), - ))? - .try_into()?; - let value = entry - .value - .ok_or(proto::rpc_store::account_storage_details::account_storage_map_details::map_entries::StorageMapEntry::missing_field( - stringify!(value), - ))? - .try_into()?; - Ok((key, value)) - }) - .collect::, ConversionError>>()?, - None => Vec::new(), + // Extract map_entries from the oneof field + let (all_map_entries, map_entries_with_proofs) = match map_entries { + Some(MapEntries::All(all_entries)) => { + let entries = all_entries + .entries + .into_iter() + .map(|entry| { + let key = entry + .key + .ok_or(proto::rpc_store::account_storage_details::account_storage_map_details::all_map_entries::StorageMapEntry::missing_field( + stringify!(key), + ))? + .try_into()?; + let value = entry + .value + .ok_or(proto::rpc_store::account_storage_details::account_storage_map_details::all_map_entries::StorageMapEntry::missing_field( + stringify!(value), + ))? + .try_into()?; + Ok((key, value)) + }) + .collect::, ConversionError>>()?; + (entries, Vec::new()) + }, + Some(MapEntries::WithProofs(entries_with_proofs)) => { + let entries = entries_with_proofs + .entries + .into_iter() + .map(|entry| { + let key = entry + .key + .ok_or(proto::rpc_store::account_storage_details::account_storage_map_details::map_entries_with_proofs::StorageMapEntryWithProof::missing_field( + stringify!(key), + ))? + .try_into()?; + let value = entry + .value + .ok_or(proto::rpc_store::account_storage_details::account_storage_map_details::map_entries_with_proofs::StorageMapEntryWithProof::missing_field( + stringify!(value), + ))? + .try_into()?; + let smt_opening = entry + .proof + .ok_or(proto::rpc_store::account_storage_details::account_storage_map_details::map_entries_with_proofs::StorageMapEntryWithProof::missing_field( + stringify!(proof), + ))?; + // Convert SmtOpening to SmtProof, then wrap in StorageMapWitness + let smt_proof = SmtProof::try_from(smt_opening)?; + // Create StorageMapWitness with the key-value pair + let proof = StorageMapWitness::new_unchecked(smt_proof, [(key, value)]); + Ok((key, value, proof)) + }) + .collect::, ConversionError>>()?; + (Vec::new(), entries) + }, + None => (Vec::new(), Vec::new()), }; Ok(Self { slot_index, too_many_entries, - map_entries, + all_map_entries, + map_entries_with_proofs, }) } } @@ -368,12 +402,14 @@ impl AccountVaultDetails { } } } + pub fn empty() -> Self { Self { too_many_assets: false, assets: Vec::new(), } } + fn too_many() -> Self { Self { too_many_assets: true, @@ -417,43 +453,70 @@ impl From for proto::rpc_store::AccountVaultDetails { pub struct AccountStorageMapDetails { pub slot_index: u8, pub too_many_entries: bool, - pub map_entries: Vec<(Word, Word)>, + pub all_map_entries: Vec<(Word, Word)>, + pub map_entries_with_proofs: Vec<(Word, Word, StorageMapWitness)>, } impl AccountStorageMapDetails { const MAX_RETURN_ENTRIES: usize = 1000; + const RETURN_ALL_THRESHOLD: usize = 256; pub fn new(slot_index: u8, slot_data: SlotData, storage_map: &StorageMap) -> Self { - let exceeds = if let SlotData::MapKeys(keys) = &slot_data { - keys.len() > Self::MAX_RETURN_ENTRIES - } else { - storage_map.entries().nth(Self::MAX_RETURN_ENTRIES).is_some() - }; - if exceeds { - Self::too_many_entries(slot_index) - } else { - Self::from_slot_data(slot_index, slot_data, storage_map) + match slot_data { + SlotData::All => { + // For "All" requests, check if total map size exceeds limit + // Fix off-by-one error: use skip() instead of nth() to check for entries >= limit + if storage_map.entries().skip(Self::MAX_RETURN_ENTRIES).next().is_some() { + Self::too_many_entries(slot_index) + } else { + Self::from_all_entries(slot_index, storage_map) + } + }, + SlotData::MapKeys(keys) => { + if keys.len() < Self::RETURN_ALL_THRESHOLD { + return Self::from_all_entries(slot_index, storage_map); + } + // For specific key requests, only check if the number of requested keys exceeds + // limit This allows retrieving specific keys even if the total map + // is large + if keys.len() > Self::MAX_RETURN_ENTRIES { + Self::too_many_entries(slot_index) + } else { + Self::from_specific_keys(slot_index, &keys, storage_map) + } + }, } } - pub fn from_slot_data(slot_index: u8, slot_data: SlotData, storage_map: &StorageMap) -> Self { - let map_entries = match slot_data { - SlotData::All => Vec::from_iter(storage_map.entries().map(|(k, v)| (*k, *v))), - SlotData::MapKeys(keys) => { - Vec::from_iter(keys.iter().map(|key| (*key, storage_map.get(key)))) - }, - }; + fn from_all_entries(slot_index: u8, storage_map: &StorageMap) -> Self { + let all_map_entries = Vec::from_iter(storage_map.entries().map(|(k, v)| (*k, *v))); Self { slot_index, too_many_entries: false, - map_entries, + all_map_entries, + map_entries_with_proofs: Vec::new(), } } + + fn from_specific_keys(slot_index: u8, keys: &[Word], storage_map: &StorageMap) -> Self { + // TODO respond with a compressed version + let map_entries_with_proofs = Vec::from_iter( + keys.iter().map(|key| (*key, storage_map.get(key), storage_map.open(key))), + ); + Self { + slot_index, + too_many_entries: false, + all_map_entries: Vec::new(), + map_entries_with_proofs, + } + } + pub fn too_many_entries(slot_index: u8) -> Self { Self { slot_index, too_many_entries: true, - map_entries: Vec::new(), + all_map_entries: Vec::new(), + map_entries_with_proofs: Vec::new(), } } } @@ -616,69 +679,56 @@ impl From for proto::rpc_store::account_storage_details::AccountStorageMapDetails { fn from(value: AccountStorageMapDetails) -> Self { + use proto::rpc_store::account_storage_details::account_storage_map_details; + let AccountStorageMapDetails { slot_index, too_many_entries, - map_entries, + all_map_entries, + map_entries_with_proofs, } = value; - // Create the entries wrapped in MapEntries message - let entries = proto::rpc_store::account_storage_details::account_storage_map_details::MapEntries { - entries: map_entries - .into_iter() - .map(|(key, value)| { - proto::rpc_store::account_storage_details::account_storage_map_details::map_entries::StorageMapEntry { - key: Some(key.into()), - value: Some(value.into()), - } - }) - .collect(), + // Determine which variant to use based on available data + let map_entries = if !all_map_entries.is_empty() { + // Create AllMapEntries variant + let all = account_storage_map_details::AllMapEntries { + entries: all_map_entries + .into_iter() + .map(|(key, value)| { + account_storage_map_details::all_map_entries::StorageMapEntry { + key: Some(key.into()), + value: Some(value.into()), + } + }) + .collect(), + }; + Some(account_storage_map_details::MapEntries::All(all)) + } else if !map_entries_with_proofs.is_empty() { + // Create EntriesWithProofs variant + let entries_with_proofs = account_storage_map_details::MapEntriesWithProofs { + entries: map_entries_with_proofs + .into_iter() + .map(|(key, value, proof)| { + // Convert StorageMapWitness to SmtProof, then to SmtOpening + let smt_proof: SmtProof = proof.into(); + let smt_opening = proto::primitives::SmtOpening::from(smt_proof); + account_storage_map_details::map_entries_with_proofs::StorageMapEntryWithProof { + key: Some(key.into()), + value: Some(value.into()), + proof: Some(smt_opening), + } + }) + .collect(), + }; + Some(account_storage_map_details::MapEntries::WithProofs(entries_with_proofs)) + } else { + None }; Self { slot_index: u32::from(slot_index), too_many_entries, - entries: Some(entries), - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct StorageMapEntry { - pub key: Word, - pub value: Word, -} - -impl - TryFrom - for StorageMapEntry -{ - type Error = ConversionError; - - fn try_from( - value: proto::rpc_store::account_storage_details::account_storage_map_details::map_entries::StorageMapEntry, - ) -> Result { - let proto::rpc_store::account_storage_details::account_storage_map_details::map_entries::StorageMapEntry { - key, - value - } = value; - - Ok(Self { - key: key.ok_or(proto::rpc_store::account_storage_details::account_storage_map_details::map_entries::StorageMapEntry::missing_field(stringify!(key)))?.try_into()?, - value: value.ok_or(proto::rpc_store::account_storage_details::account_storage_map_details::map_entries::StorageMapEntry::missing_field(stringify!(value)))?.try_into()?, - }) - } -} - -impl From - for proto::rpc_store::account_storage_details::account_storage_map_details::map_entries::StorageMapEntry -{ - fn from(value: StorageMapEntry) -> Self { - let StorageMapEntry { key, value } = value; - - Self { - key: Some(proto::primitives::Digest::from(key)), - value: Some(proto::primitives::Digest::from(value)), + map_entries, } } } diff --git a/crates/proto/src/generated/rpc_store.rs b/crates/proto/src/generated/rpc_store.rs index da0af6342..aac7d1291 100644 --- a/crates/proto/src/generated/rpc_store.rs +++ b/crates/proto/src/generated/rpc_store.rs @@ -30,11 +30,13 @@ pub mod account_proof_request { /// Request the details for a public account. #[derive(Clone, PartialEq, ::prost::Message)] pub struct AccountDetailRequest { - /// Last known code commitment to the client. The response will include account code - /// only if its commitment is different from this value or if the value is not present. + /// Last known code commitment to the requester. The response will include account code + /// only if its commitment is different from this value. + /// + /// If the field is ommiteed, the response will not include the account code. #[prost(message, optional, tag = "1")] pub code_commitment: ::core::option::Option, - /// Last known asset vault commitment to the client. The response will include asset vault data + /// Last known asset vault commitment to the requester. The response will include asset vault data /// only if its commitment is different from this value. If the value is not present in the /// request, the response will not contain one either. /// If the number of to-be-returned asset entries exceed a threshold, they have to be requested @@ -107,7 +109,8 @@ pub mod account_proof_response { /// Account code; empty if code commitments matched or none was requested #[prost(bytes = "vec", optional, tag = "3")] pub code: ::core::option::Option<::prost::alloc::vec::Vec>, - /// Account asset vault data; empty if vault commitments matched + /// Account asset vault data; empty if vault commitments matched or the requester + /// ommitted it in the request. #[prost(message, optional, tag = "4")] pub vault_details: ::core::option::Option, } @@ -149,20 +152,46 @@ pub mod account_storage_details { /// endpoint should be used to get all storage map data. #[prost(bool, tag = "2")] pub too_many_entries: bool, - /// By default we provide all storage entries. - #[prost(message, optional, tag = "3")] - pub entries: ::core::option::Option, + #[prost(oneof = "account_storage_map_details::MapEntries", tags = "3, 4")] + pub map_entries: ::core::option::Option, } /// Nested message and enum types in `AccountStorageMapDetails`. pub mod account_storage_map_details { + /// Wrapper for repeated storage map entries including their proofs + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct MapEntriesWithProofs { + #[prost(message, repeated, tag = "1")] + pub entries: ::prost::alloc::vec::Vec< + map_entries_with_proofs::StorageMapEntryWithProof, + >, + } + /// Nested message and enum types in `MapEntriesWithProofs`. + pub mod map_entries_with_proofs { + /// Definition of individual storage entries including a proof + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct StorageMapEntryWithProof { + #[prost(message, optional, tag = "1")] + pub key: ::core::option::Option< + super::super::super::super::primitives::Digest, + >, + #[prost(message, optional, tag = "2")] + pub value: ::core::option::Option< + super::super::super::super::primitives::Digest, + >, + #[prost(message, optional, tag = "3")] + pub proof: ::core::option::Option< + super::super::super::super::primitives::SmtOpening, + >, + } + } /// Wrapper for repeated storage map entries #[derive(Clone, PartialEq, ::prost::Message)] - pub struct MapEntries { + pub struct AllMapEntries { #[prost(message, repeated, tag = "1")] - pub entries: ::prost::alloc::vec::Vec, + pub entries: ::prost::alloc::vec::Vec, } - /// Nested message and enum types in `MapEntries`. - pub mod map_entries { + /// Nested message and enum types in `AllMapEntries`. + pub mod all_map_entries { /// Definition of individual storage entries. #[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct StorageMapEntry { @@ -176,6 +205,16 @@ pub mod account_storage_details { >, } } + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum MapEntries { + /// By default we provide all storage entries. + #[prost(message, tag = "3")] + All(AllMapEntries), + /// If only a subset is requested, we respond with proofs. + /// TODO compress, see <> + #[prost(message, tag = "4")] + WithProofs(MapEntriesWithProofs), + } } } /// List of nullifiers to return proofs for. @@ -231,12 +270,12 @@ pub mod sync_nullifiers_response { } /// State synchronization request. /// -/// Specifies state updates the client is interested in. The server will return the first block which +/// Specifies state updates the requester is interested in. The server will return the first block which /// contains a note matching `note_tags` or the chain tip. And the corresponding updates to /// `account_ids` for that block range. #[derive(Clone, PartialEq, ::prost::Message)] pub struct SyncStateRequest { - /// Last block known by the client. The response will contain data starting from the next block, + /// Last block known by the requester. The response will contain data starting from the next block, /// until the first block which contains a note of matching the requested tag, or the chain tip /// if there are no notes. #[prost(fixed32, tag = "1")] @@ -248,7 +287,7 @@ pub struct SyncStateRequest { /// it won't be included in the response. #[prost(message, repeated, tag = "2")] pub account_ids: ::prost::alloc::vec::Vec, - /// Specifies the tags which the client is interested in. + /// Specifies the tags which the requester is interested in. #[prost(fixed32, repeated, tag = "3")] pub note_tags: ::prost::alloc::vec::Vec, } @@ -277,7 +316,7 @@ pub struct SyncStateResponse { } /// Account vault synchronization request. /// -/// Allows clients to sync asset values for specific public accounts within a block range. +/// Allows requesters to sync asset values for specific public accounts within a block range. #[derive(Clone, PartialEq, ::prost::Message)] pub struct SyncAccountVaultRequest { /// Block range from which to start synchronizing. @@ -317,14 +356,14 @@ pub struct AccountVaultUpdate { } /// Note synchronization request. /// -/// Specifies note tags that client is interested in. The server will return the first block which +/// Specifies note tags that requester is interested in. The server will return the first block which /// contains a note matching `note_tags` or the chain tip. #[derive(Clone, PartialEq, ::prost::Message)] pub struct SyncNotesRequest { /// Block range from which to start synchronizing. #[prost(message, optional, tag = "1")] pub block_range: ::core::option::Option, - /// Specifies the tags which the client is interested in. + /// Specifies the tags which the requester is interested in. #[prost(fixed32, repeated, tag = "2")] pub note_tags: ::prost::alloc::vec::Vec, } @@ -349,7 +388,7 @@ pub struct SyncNotesResponse { } /// Storage map synchronization request. /// -/// Allows clients to sync storage map values for specific public accounts within a block range, +/// Allows requesters to sync storage map values for specific public accounts within a block range, /// with support for cursor-based pagination to handle large storage maps. #[derive(Clone, PartialEq, ::prost::Message)] pub struct SyncStorageMapsRequest { @@ -410,11 +449,11 @@ pub struct BlockRange { } /// Represents pagination information for chunked responses. /// -/// Pagination is done using block numbers as the axis, allowing clients to request +/// Pagination is done using block numbers as the axis, allowing requesters to request /// data in chunks by specifying block ranges and continuing from where the previous /// response left off. /// -/// To request the next chunk, the client should use `block_num + 1` from the previous response +/// To request the next chunk, the requester should use `block_num + 1` from the previous response /// as the `block_from` for the next request. #[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct PaginationInfo { @@ -431,7 +470,7 @@ pub struct PaginationInfo { } /// Transactions synchronization request. /// -/// Allows clients to sync transactions for specific accounts within a block range. +/// Allows requesters to sync transactions for specific accounts within a block range. #[derive(Clone, PartialEq, ::prost::Message)] pub struct SyncTransactionsRequest { /// Block range from which to start synchronizing. @@ -779,9 +818,9 @@ pub mod rpc_client { .insert(GrpcMethod::new("rpc_store.Rpc", "SyncNullifiers")); self.inner.unary(req, path, codec).await } - /// Returns info which can be used by the client to sync up to the tip of chain for the notes they are interested in. + /// Returns info which can be used by the requester to sync up to the tip of chain for the notes they are interested in. /// - /// Client specifies the `note_tags` they are interested in, and the block height from which to search for new for + /// requester specifies the `note_tags` they are interested in, and the block height from which to search for new for /// matching notes for. The request will then return the next block containing any note matching the provided tags. /// /// The response includes each note's metadata and inclusion proof. @@ -809,20 +848,20 @@ pub mod rpc_client { req.extensions_mut().insert(GrpcMethod::new("rpc_store.Rpc", "SyncNotes")); self.inner.unary(req, path, codec).await } - /// Returns info which can be used by the client to sync up to the latest state of the chain - /// for the objects (accounts, notes, nullifiers) the client is interested in. + /// Returns info which can be used by the requester to sync up to the latest state of the chain + /// for the objects (accounts, notes, nullifiers) the requester is interested in. /// /// This request returns the next block containing requested data. It also returns `chain_tip` - /// which is the latest block number in the chain. Client is expected to repeat these requests + /// which is the latest block number in the chain. requester is expected to repeat these requests /// in a loop until `response.block_header.block_num == response.chain_tip`, at which point - /// the client is fully synchronized with the chain. + /// the requester is fully synchronized with the chain. /// /// Each request also returns info about new notes, nullifiers etc. created. It also returns /// Chain MMR delta that can be used to update the state of Chain MMR. This includes both chain /// MMR peaks and chain MMR nodes. /// /// For preserving some degree of privacy, note tags and nullifiers filters contain only high - /// part of hashes. Thus, returned data contains excessive notes and nullifiers, client can make + /// part of hashes. Thus, returned data contains excessive notes and nullifiers, requester can make /// additional filtering of that data on its side. pub async fn sync_state( &mut self, @@ -1004,9 +1043,9 @@ pub mod rpc_server { tonic::Response, tonic::Status, >; - /// Returns info which can be used by the client to sync up to the tip of chain for the notes they are interested in. + /// Returns info which can be used by the requester to sync up to the tip of chain for the notes they are interested in. /// - /// Client specifies the `note_tags` they are interested in, and the block height from which to search for new for + /// requester specifies the `note_tags` they are interested in, and the block height from which to search for new for /// matching notes for. The request will then return the next block containing any note matching the provided tags. /// /// The response includes each note's metadata and inclusion proof. @@ -1020,20 +1059,20 @@ pub mod rpc_server { tonic::Response, tonic::Status, >; - /// Returns info which can be used by the client to sync up to the latest state of the chain - /// for the objects (accounts, notes, nullifiers) the client is interested in. + /// Returns info which can be used by the requester to sync up to the latest state of the chain + /// for the objects (accounts, notes, nullifiers) the requester is interested in. /// /// This request returns the next block containing requested data. It also returns `chain_tip` - /// which is the latest block number in the chain. Client is expected to repeat these requests + /// which is the latest block number in the chain. requester is expected to repeat these requests /// in a loop until `response.block_header.block_num == response.chain_tip`, at which point - /// the client is fully synchronized with the chain. + /// the requester is fully synchronized with the chain. /// /// Each request also returns info about new notes, nullifiers etc. created. It also returns /// Chain MMR delta that can be used to update the state of Chain MMR. This includes both chain /// MMR peaks and chain MMR nodes. /// /// For preserving some degree of privacy, note tags and nullifiers filters contain only high - /// part of hashes. Thus, returned data contains excessive notes and nullifiers, client can make + /// part of hashes. Thus, returned data contains excessive notes and nullifiers, requester can make /// additional filtering of that data on its side. async fn sync_state( &self, diff --git a/crates/store/src/state.rs b/crates/store/src/state.rs index effccc31d..69546170f 100644 --- a/crates/store/src/state.rs +++ b/crates/store/src/state.rs @@ -890,6 +890,9 @@ impl State { /// Returns the respective account proof with optional details, such as asset and storage /// entries. + /// + /// Note: The `block_num` parameter in the request is currently ignored and will always + /// return the current state. Historical block support will be implemented in a future update. #[allow(clippy::too_many_lines)] pub async fn get_account_proof( &self, @@ -901,6 +904,8 @@ impl State { let inner_state = self.inner.read().await; let account_id = account_request.account_id; + // TODO: Implement historical block support using account_request.block_num + // For now, we always return the current state let account_details = if let Some(AccountDetailRequest { code_commitment, asset_vault_commitment, diff --git a/proto/proto/store/rpc.proto b/proto/proto/store/rpc.proto index 1da00771c..2d1586caa 100644 --- a/proto/proto/store/rpc.proto +++ b/proto/proto/store/rpc.proto @@ -47,9 +47,9 @@ service Rpc { // Note that only 16-bit prefixes are supported at this time. rpc SyncNullifiers(SyncNullifiersRequest) returns (SyncNullifiersResponse) {} - // Returns info which can be used by the client to sync up to the tip of chain for the notes they are interested in. + // Returns info which can be used by the requester to sync up to the tip of chain for the notes they are interested in. // - // Client specifies the `note_tags` they are interested in, and the block height from which to search for new for + // requester specifies the `note_tags` they are interested in, and the block height from which to search for new for // matching notes for. The request will then return the next block containing any note matching the provided tags. // // The response includes each note's metadata and inclusion proof. @@ -58,20 +58,20 @@ service Rpc { // tip of the chain. rpc SyncNotes(SyncNotesRequest) returns (SyncNotesResponse) {} - // Returns info which can be used by the client to sync up to the latest state of the chain - // for the objects (accounts, notes, nullifiers) the client is interested in. + // Returns info which can be used by the requester to sync up to the latest state of the chain + // for the objects (accounts, notes, nullifiers) the requester is interested in. // // This request returns the next block containing requested data. It also returns `chain_tip` - // which is the latest block number in the chain. Client is expected to repeat these requests + // which is the latest block number in the chain. requester is expected to repeat these requests // in a loop until `response.block_header.block_num == response.chain_tip`, at which point - // the client is fully synchronized with the chain. + // the requester is fully synchronized with the chain. // // Each request also returns info about new notes, nullifiers etc. created. It also returns // Chain MMR delta that can be used to update the state of Chain MMR. This includes both chain // MMR peaks and chain MMR nodes. // // For preserving some degree of privacy, note tags and nullifiers filters contain only high - // part of hashes. Thus, returned data contains excessive notes and nullifiers, client can make + // part of hashes. Thus, returned data contains excessive notes and nullifiers, requester can make // additional filtering of that data on its side. rpc SyncState(SyncStateRequest) returns (SyncStateResponse) {} @@ -127,11 +127,13 @@ message AccountProofRequest { } } - // Last known code commitment to the client. The response will include account code - // only if its commitment is different from this value or if the value is not present. + // Last known code commitment to the requester. The response will include account code + // only if its commitment is different from this value. + // + // If the field is ommiteed, the response will not include the account code. optional primitives.Digest code_commitment = 1; - // Last known asset vault commitment to the client. The response will include asset vault data + // Last known asset vault commitment to the requester. The response will include asset vault data // only if its commitment is different from this value. If the value is not present in the // request, the response will not contain one either. // If the number of to-be-returned asset entries exceed a threshold, they have to be requested @@ -165,7 +167,8 @@ message AccountProofResponse { // Account code; empty if code commitments matched or none was requested optional bytes code = 3; - // Account asset vault data; empty if vault commitments matched + // Account asset vault data; empty if vault commitments matched or the requester + // ommitted it in the request. optional AccountVaultDetails vault_details = 4; } @@ -191,8 +194,19 @@ message AccountVaultDetails { // Account storage details for AccountProofResponse message AccountStorageDetails { message AccountStorageMapDetails { + // Wrapper for repeated storage map entries including their proofs + message MapEntriesWithProofs { + // Definition of individual storage entries including a proof + message StorageMapEntryWithProof { + primitives.Digest key = 1; + primitives.Digest value = 2; + primitives.SmtOpening proof = 3; + } + + repeated StorageMapEntryWithProof entries = 1; + } // Wrapper for repeated storage map entries - message MapEntries { + message AllMapEntries { // Definition of individual storage entries. message StorageMapEntry { primitives.Digest key = 1; @@ -209,8 +223,14 @@ message AccountStorageDetails { // endpoint should be used to get all storage map data. bool too_many_entries = 2; - // By default we provide all storage entries. - MapEntries entries = 3; + oneof map_entries { + // By default we provide all storage entries. + AllMapEntries all = 3; + + // If only a subset is requested, we respond with proofs. + // TODO compress, see + MapEntriesWithProofs with_proofs = 4; + } } // Account storage header (storage slot info for up to 256 slots) @@ -275,11 +295,11 @@ message SyncNullifiersResponse { // State synchronization request. // -// Specifies state updates the client is interested in. The server will return the first block which +// Specifies state updates the requester is interested in. The server will return the first block which // contains a note matching `note_tags` or the chain tip. And the corresponding updates to // `account_ids` for that block range. message SyncStateRequest { - // Last block known by the client. The response will contain data starting from the next block, + // Last block known by the requester. The response will contain data starting from the next block, // until the first block which contains a note of matching the requested tag, or the chain tip // if there are no notes. fixed32 block_num = 1; @@ -291,7 +311,7 @@ message SyncStateRequest { // it won't be included in the response. repeated account.AccountId account_ids = 2; - // Specifies the tags which the client is interested in. + // Specifies the tags which the requester is interested in. repeated fixed32 note_tags = 3; } @@ -322,7 +342,7 @@ message SyncStateResponse { // Account vault synchronization request. // -// Allows clients to sync asset values for specific public accounts within a block range. +// Allows requesters to sync asset values for specific public accounts within a block range. message SyncAccountVaultRequest { // Block range from which to start synchronizing. // @@ -362,13 +382,13 @@ message AccountVaultUpdate { // Note synchronization request. // -// Specifies note tags that client is interested in. The server will return the first block which +// Specifies note tags that requester is interested in. The server will return the first block which // contains a note matching `note_tags` or the chain tip. message SyncNotesRequest { // Block range from which to start synchronizing. BlockRange block_range = 1; - // Specifies the tags which the client is interested in. + // Specifies the tags which the requester is interested in. repeated fixed32 note_tags = 2; } @@ -395,7 +415,7 @@ message SyncNotesResponse { // Storage map synchronization request. // -// Allows clients to sync storage map values for specific public accounts within a block range, +// Allows requesters to sync storage map values for specific public accounts within a block range, // with support for cursor-based pagination to handle large storage maps. message SyncStorageMapsRequest { // Block range from which to start synchronizing. @@ -460,11 +480,11 @@ message BlockRange { // Represents pagination information for chunked responses. // -// Pagination is done using block numbers as the axis, allowing clients to request +// Pagination is done using block numbers as the axis, allowing requesters to request // data in chunks by specifying block ranges and continuing from where the previous // response left off. // -// To request the next chunk, the client should use `block_num + 1` from the previous response +// To request the next chunk, the requester should use `block_num + 1` from the previous response // as the `block_from` for the next request. message PaginationInfo { // Current chain tip @@ -483,7 +503,7 @@ message PaginationInfo { // Transactions synchronization request. // -// Allows clients to sync transactions for specific accounts within a block range. +// Allows requesters to sync transactions for specific accounts within a block range. message SyncTransactionsRequest { // Block range from which to start synchronizing. BlockRange block_range = 1; From c3a1f80d8ebf383b10dc81a144ed99c8072b947b Mon Sep 17 00:00:00 2001 From: Bernhard Schuster Date: Wed, 8 Oct 2025 09:02:22 +0200 Subject: [PATCH 2/6] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f636deb4..602fd9bd5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - Implemented storage map `DataStore` function ([#1226](https://github.com/0xMiden/miden-node/pull/1226)). - [BREAKING] Renamed `RemoteProverProxy` to `RemoteProverClient` ([#1236](https://github.com/0xMiden/miden-node/pull/1236)). - Added pagination to `SyncNotes` endpoint ([#1257](https://github.com/0xMiden/miden-node/pull/1257)). +- [BREAKING] Response type nuances of `GetAccountProof` in the public store API (#[1277](https://github.com/0xMiden/miden-node/pull/1277)). ## v0.11.2 (2025-09-10) From 223f978eb6e60566290d51af6f19a5b4e03c5928 Mon Sep 17 00:00:00 2001 From: Bernhard Schuster Date: Wed, 8 Oct 2025 09:05:08 +0200 Subject: [PATCH 3/6] lints and friends --- crates/proto/src/domain/account.rs | 10 +++++++--- proto/proto/store/rpc.proto | 4 ++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/crates/proto/src/domain/account.rs b/crates/proto/src/domain/account.rs index f530c7ac1..ed14f6fe7 100644 --- a/crates/proto/src/domain/account.rs +++ b/crates/proto/src/domain/account.rs @@ -3,13 +3,17 @@ use std::fmt::{Debug, Display, Formatter}; use miden_node_utils::formatting::format_opt; use miden_objects::Word; use miden_objects::account::{ - Account, AccountHeader, AccountId, AccountStorageHeader, StorageMap, StorageMapWitness, + Account, + AccountHeader, + AccountId, + AccountStorageHeader, + StorageMap, + StorageMapWitness, StorageSlotType, }; use miden_objects::asset::{Asset, AssetVault}; use miden_objects::block::{AccountWitness, BlockNumber}; -use miden_objects::crypto::merkle::SmtProof; -use miden_objects::crypto::merkle::SparseMerklePath; +use miden_objects::crypto::merkle::{SmtProof, SparseMerklePath}; use miden_objects::note::{NoteExecutionMode, NoteTag}; use miden_objects::utils::{Deserializable, DeserializationError, Serializable}; use thiserror::Error; diff --git a/proto/proto/store/rpc.proto b/proto/proto/store/rpc.proto index 2d1586caa..b1b69c38f 100644 --- a/proto/proto/store/rpc.proto +++ b/proto/proto/store/rpc.proto @@ -36,7 +36,7 @@ service Rpc { // and current chain length to authenticate the block's inclusion. rpc GetBlockHeaderByNumber(shared.BlockHeaderByNumberRequest) returns (shared.BlockHeaderByNumberResponse) {} - // Returns a list of committed notes matching the provided note IDs. + // Returns a list of comitted notes matching the provided note IDs. rpc GetNotesById(note.NoteIdList) returns (note.CommittedNoteList) {} // Returns the script for a note by its root. @@ -168,7 +168,7 @@ message AccountProofResponse { optional bytes code = 3; // Account asset vault data; empty if vault commitments matched or the requester - // ommitted it in the request. + // omitted it in the request. optional AccountVaultDetails vault_details = 4; } From 0abc5690d0e5eed872bd6bb44a370b7c22261141 Mon Sep 17 00:00:00 2001 From: Bernhard Schuster Date: Wed, 8 Oct 2025 10:27:16 +0200 Subject: [PATCH 4/6] Simplify storage map details response for now --- crates/proto/src/domain/account.rs | 173 ++++++------------------ crates/proto/src/generated/rpc_store.rs | 52 ++----- proto/proto/store/rpc.proto | 25 +--- 3 files changed, 56 insertions(+), 194 deletions(-) diff --git a/crates/proto/src/domain/account.rs b/crates/proto/src/domain/account.rs index ed14f6fe7..4377d5b11 100644 --- a/crates/proto/src/domain/account.rs +++ b/crates/proto/src/domain/account.rs @@ -8,12 +8,11 @@ use miden_objects::account::{ AccountId, AccountStorageHeader, StorageMap, - StorageMapWitness, StorageSlotType, }; use miden_objects::asset::{Asset, AssetVault}; use miden_objects::block::{AccountWitness, BlockNumber}; -use miden_objects::crypto::merkle::{SmtProof, SparseMerklePath}; +use miden_objects::crypto::merkle::SparseMerklePath; use miden_objects::note::{NoteExecutionMode, NoteTag}; use miden_objects::utils::{Deserializable, DeserializationError, Serializable}; use thiserror::Error; @@ -193,79 +192,43 @@ impl TryFrom Result { - use proto::rpc_store::account_storage_details::account_storage_map_details::MapEntries; - let proto::rpc_store::account_storage_details::AccountStorageMapDetails { slot_index, too_many_entries, - map_entries, + entries, } = value; let slot_index = slot_index.try_into().map_err(ConversionError::TryFromIntError)?; - // Extract map_entries from the oneof field - let (all_map_entries, map_entries_with_proofs) = match map_entries { - Some(MapEntries::All(all_entries)) => { - let entries = all_entries - .entries - .into_iter() - .map(|entry| { - let key = entry - .key - .ok_or(proto::rpc_store::account_storage_details::account_storage_map_details::all_map_entries::StorageMapEntry::missing_field( - stringify!(key), - ))? - .try_into()?; - let value = entry - .value - .ok_or(proto::rpc_store::account_storage_details::account_storage_map_details::all_map_entries::StorageMapEntry::missing_field( - stringify!(value), - ))? - .try_into()?; - Ok((key, value)) - }) - .collect::, ConversionError>>()?; - (entries, Vec::new()) - }, - Some(MapEntries::WithProofs(entries_with_proofs)) => { - let entries = entries_with_proofs - .entries - .into_iter() - .map(|entry| { - let key = entry - .key - .ok_or(proto::rpc_store::account_storage_details::account_storage_map_details::map_entries_with_proofs::StorageMapEntryWithProof::missing_field( - stringify!(key), - ))? - .try_into()?; - let value = entry - .value - .ok_or(proto::rpc_store::account_storage_details::account_storage_map_details::map_entries_with_proofs::StorageMapEntryWithProof::missing_field( - stringify!(value), - ))? - .try_into()?; - let smt_opening = entry - .proof - .ok_or(proto::rpc_store::account_storage_details::account_storage_map_details::map_entries_with_proofs::StorageMapEntryWithProof::missing_field( - stringify!(proof), - ))?; - // Convert SmtOpening to SmtProof, then wrap in StorageMapWitness - let smt_proof = SmtProof::try_from(smt_opening)?; - // Create StorageMapWitness with the key-value pair - let proof = StorageMapWitness::new_unchecked(smt_proof, [(key, value)]); - Ok((key, value, proof)) - }) - .collect::, ConversionError>>()?; - (Vec::new(), entries) - }, - None => (Vec::new(), Vec::new()), + // Extract map_entries from the MapEntries message + let map_entries = if let Some(entries) = entries { + entries + .entries + .into_iter() + .map(|entry| { + let key = entry + .key + .ok_or(proto::rpc_store::account_storage_details::account_storage_map_details::map_entries::StorageMapEntry::missing_field( + stringify!(key), + ))? + .try_into()?; + let value = entry + .value + .ok_or(proto::rpc_store::account_storage_details::account_storage_map_details::map_entries::StorageMapEntry::missing_field( + stringify!(value), + ))? + .try_into()?; + Ok((key, value)) + }) + .collect::, ConversionError>>()? + } else { + Vec::new() }; Ok(Self { slot_index, too_many_entries, - all_map_entries, - map_entries_with_proofs, + map_entries, }) } } @@ -457,13 +420,11 @@ impl From for proto::rpc_store::AccountVaultDetails { pub struct AccountStorageMapDetails { pub slot_index: u8, pub too_many_entries: bool, - pub all_map_entries: Vec<(Word, Word)>, - pub map_entries_with_proofs: Vec<(Word, Word, StorageMapWitness)>, + pub map_entries: Vec<(Word, Word)>, } impl AccountStorageMapDetails { const MAX_RETURN_ENTRIES: usize = 1000; - const RETURN_ALL_THRESHOLD: usize = 256; pub fn new(slot_index: u8, slot_data: SlotData, storage_map: &StorageMap) -> Self { match slot_data { @@ -477,50 +438,34 @@ impl AccountStorageMapDetails { } }, SlotData::MapKeys(keys) => { - if keys.len() < Self::RETURN_ALL_THRESHOLD { - return Self::from_all_entries(slot_index, storage_map); - } - // For specific key requests, only check if the number of requested keys exceeds - // limit This allows retrieving specific keys even if the total map - // is large if keys.len() > Self::MAX_RETURN_ENTRIES { Self::too_many_entries(slot_index) } else { - Self::from_specific_keys(slot_index, &keys, storage_map) + Self::from_specific_keys(slot_index, &keys[..], storage_map) } }, } } fn from_all_entries(slot_index: u8, storage_map: &StorageMap) -> Self { - let all_map_entries = Vec::from_iter(storage_map.entries().map(|(k, v)| (*k, *v))); + let map_entries = Vec::from_iter(storage_map.entries().map(|(k, v)| (*k, *v))); Self { slot_index, too_many_entries: false, - all_map_entries, - map_entries_with_proofs: Vec::new(), + map_entries, } } - fn from_specific_keys(slot_index: u8, keys: &[Word], storage_map: &StorageMap) -> Self { - // TODO respond with a compressed version - let map_entries_with_proofs = Vec::from_iter( - keys.iter().map(|key| (*key, storage_map.get(key), storage_map.open(key))), - ); - Self { - slot_index, - too_many_entries: false, - all_map_entries: Vec::new(), - map_entries_with_proofs, - } + fn from_specific_keys(slot_index: u8, _keys: &[Word], storage_map: &StorageMap) -> Self { + // TODO For now, we return all entries instead of specific keys with proofs + Self::from_all_entries(slot_index, storage_map) } pub fn too_many_entries(slot_index: u8) -> Self { Self { slot_index, too_many_entries: true, - all_map_entries: Vec::new(), - map_entries_with_proofs: Vec::new(), + map_entries: Vec::new(), } } } @@ -678,7 +623,6 @@ impl From for proto::rpc_store::account_proof_response::AccountD } } -// ACCOUNT PROOF impl From for proto::rpc_store::account_storage_details::AccountStorageMapDetails { @@ -688,51 +632,22 @@ impl From let AccountStorageMapDetails { slot_index, too_many_entries, - all_map_entries, - map_entries_with_proofs, + map_entries, } = value; - // Determine which variant to use based on available data - let map_entries = if !all_map_entries.is_empty() { - // Create AllMapEntries variant - let all = account_storage_map_details::AllMapEntries { - entries: all_map_entries - .into_iter() - .map(|(key, value)| { - account_storage_map_details::all_map_entries::StorageMapEntry { - key: Some(key.into()), - value: Some(value.into()), - } - }) - .collect(), - }; - Some(account_storage_map_details::MapEntries::All(all)) - } else if !map_entries_with_proofs.is_empty() { - // Create EntriesWithProofs variant - let entries_with_proofs = account_storage_map_details::MapEntriesWithProofs { - entries: map_entries_with_proofs - .into_iter() - .map(|(key, value, proof)| { - // Convert StorageMapWitness to SmtProof, then to SmtOpening - let smt_proof: SmtProof = proof.into(); - let smt_opening = proto::primitives::SmtOpening::from(smt_proof); - account_storage_map_details::map_entries_with_proofs::StorageMapEntryWithProof { - key: Some(key.into()), - value: Some(value.into()), - proof: Some(smt_opening), - } - }) - .collect(), - }; - Some(account_storage_map_details::MapEntries::WithProofs(entries_with_proofs)) - } else { - None - }; + let entries = Some(account_storage_map_details::MapEntries { + entries: Vec::from_iter(map_entries.into_iter().map(|(key, value)| { + account_storage_map_details::map_entries::StorageMapEntry { + key: Some(key.into()), + value: Some(value.into()), + } + })), + }); Self { slot_index: u32::from(slot_index), too_many_entries, - map_entries, + entries, } } } diff --git a/crates/proto/src/generated/rpc_store.rs b/crates/proto/src/generated/rpc_store.rs index aac7d1291..5f6f05816 100644 --- a/crates/proto/src/generated/rpc_store.rs +++ b/crates/proto/src/generated/rpc_store.rs @@ -110,7 +110,7 @@ pub mod account_proof_response { #[prost(bytes = "vec", optional, tag = "3")] pub code: ::core::option::Option<::prost::alloc::vec::Vec>, /// Account asset vault data; empty if vault commitments matched or the requester - /// ommitted it in the request. + /// omitted it in the request. #[prost(message, optional, tag = "4")] pub vault_details: ::core::option::Option, } @@ -152,46 +152,20 @@ pub mod account_storage_details { /// endpoint should be used to get all storage map data. #[prost(bool, tag = "2")] pub too_many_entries: bool, - #[prost(oneof = "account_storage_map_details::MapEntries", tags = "3, 4")] - pub map_entries: ::core::option::Option, + /// By default we provide all storage entries. + #[prost(message, optional, tag = "3")] + pub entries: ::core::option::Option, } /// Nested message and enum types in `AccountStorageMapDetails`. pub mod account_storage_map_details { - /// Wrapper for repeated storage map entries including their proofs - #[derive(Clone, PartialEq, ::prost::Message)] - pub struct MapEntriesWithProofs { - #[prost(message, repeated, tag = "1")] - pub entries: ::prost::alloc::vec::Vec< - map_entries_with_proofs::StorageMapEntryWithProof, - >, - } - /// Nested message and enum types in `MapEntriesWithProofs`. - pub mod map_entries_with_proofs { - /// Definition of individual storage entries including a proof - #[derive(Clone, PartialEq, ::prost::Message)] - pub struct StorageMapEntryWithProof { - #[prost(message, optional, tag = "1")] - pub key: ::core::option::Option< - super::super::super::super::primitives::Digest, - >, - #[prost(message, optional, tag = "2")] - pub value: ::core::option::Option< - super::super::super::super::primitives::Digest, - >, - #[prost(message, optional, tag = "3")] - pub proof: ::core::option::Option< - super::super::super::super::primitives::SmtOpening, - >, - } - } /// Wrapper for repeated storage map entries #[derive(Clone, PartialEq, ::prost::Message)] - pub struct AllMapEntries { + pub struct MapEntries { #[prost(message, repeated, tag = "1")] - pub entries: ::prost::alloc::vec::Vec, + pub entries: ::prost::alloc::vec::Vec, } - /// Nested message and enum types in `AllMapEntries`. - pub mod all_map_entries { + /// Nested message and enum types in `MapEntries`. + pub mod map_entries { /// Definition of individual storage entries. #[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct StorageMapEntry { @@ -205,16 +179,6 @@ pub mod account_storage_details { >, } } - #[derive(Clone, PartialEq, ::prost::Oneof)] - pub enum MapEntries { - /// By default we provide all storage entries. - #[prost(message, tag = "3")] - All(AllMapEntries), - /// If only a subset is requested, we respond with proofs. - /// TODO compress, see <> - #[prost(message, tag = "4")] - WithProofs(MapEntriesWithProofs), - } } } /// List of nullifiers to return proofs for. diff --git a/proto/proto/store/rpc.proto b/proto/proto/store/rpc.proto index b1b69c38f..1bbc37f67 100644 --- a/proto/proto/store/rpc.proto +++ b/proto/proto/store/rpc.proto @@ -36,7 +36,7 @@ service Rpc { // and current chain length to authenticate the block's inclusion. rpc GetBlockHeaderByNumber(shared.BlockHeaderByNumberRequest) returns (shared.BlockHeaderByNumberResponse) {} - // Returns a list of comitted notes matching the provided note IDs. + // Returns a list of committed notes matching the provided note IDs. rpc GetNotesById(note.NoteIdList) returns (note.CommittedNoteList) {} // Returns the script for a note by its root. @@ -194,19 +194,8 @@ message AccountVaultDetails { // Account storage details for AccountProofResponse message AccountStorageDetails { message AccountStorageMapDetails { - // Wrapper for repeated storage map entries including their proofs - message MapEntriesWithProofs { - // Definition of individual storage entries including a proof - message StorageMapEntryWithProof { - primitives.Digest key = 1; - primitives.Digest value = 2; - primitives.SmtOpening proof = 3; - } - - repeated StorageMapEntryWithProof entries = 1; - } // Wrapper for repeated storage map entries - message AllMapEntries { + message MapEntries { // Definition of individual storage entries. message StorageMapEntry { primitives.Digest key = 1; @@ -223,14 +212,8 @@ message AccountStorageDetails { // endpoint should be used to get all storage map data. bool too_many_entries = 2; - oneof map_entries { - // By default we provide all storage entries. - AllMapEntries all = 3; - - // If only a subset is requested, we respond with proofs. - // TODO compress, see - MapEntriesWithProofs with_proofs = 4; - } + // By default we provide all storage entries. + MapEntries entries = 3; } // Account storage header (storage slot info for up to 256 slots) From 5f84dd7faf61e4047d7740a0a114feaa99302323 Mon Sep 17 00:00:00 2001 From: Bernhard Schuster Date: Wed, 8 Oct 2025 11:02:21 +0200 Subject: [PATCH 5/6] clippy --- crates/proto/src/domain/account.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/proto/src/domain/account.rs b/crates/proto/src/domain/account.rs index 4377d5b11..b78b91f2a 100644 --- a/crates/proto/src/domain/account.rs +++ b/crates/proto/src/domain/account.rs @@ -431,7 +431,7 @@ impl AccountStorageMapDetails { SlotData::All => { // For "All" requests, check if total map size exceeds limit // Fix off-by-one error: use skip() instead of nth() to check for entries >= limit - if storage_map.entries().skip(Self::MAX_RETURN_ENTRIES).next().is_some() { + if storage_map.entries().nth(Self::MAX_RETURN_ENTRIES).is_some() { Self::too_many_entries(slot_index) } else { Self::from_all_entries(slot_index, storage_map) From 7e7a128482de652557551bd5a07d8144dcefe7f4 Mon Sep 17 00:00:00 2001 From: Bernhard Schuster Date: Wed, 8 Oct 2025 22:39:20 +0200 Subject: [PATCH 6/6] feedback --- crates/proto/src/domain/account.rs | 42 +++++++++++++----------------- 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/crates/proto/src/domain/account.rs b/crates/proto/src/domain/account.rs index b78b91f2a..494f16671 100644 --- a/crates/proto/src/domain/account.rs +++ b/crates/proto/src/domain/account.rs @@ -428,37 +428,31 @@ impl AccountStorageMapDetails { pub fn new(slot_index: u8, slot_data: SlotData, storage_map: &StorageMap) -> Self { match slot_data { - SlotData::All => { - // For "All" requests, check if total map size exceeds limit - // Fix off-by-one error: use skip() instead of nth() to check for entries >= limit - if storage_map.entries().nth(Self::MAX_RETURN_ENTRIES).is_some() { - Self::too_many_entries(slot_index) - } else { - Self::from_all_entries(slot_index, storage_map) - } - }, - SlotData::MapKeys(keys) => { - if keys.len() > Self::MAX_RETURN_ENTRIES { - Self::too_many_entries(slot_index) - } else { - Self::from_specific_keys(slot_index, &keys[..], storage_map) - } - }, + SlotData::All => Self::from_all_entries(slot_index, storage_map), + SlotData::MapKeys(keys) => Self::from_specific_keys(slot_index, &keys[..], storage_map), } } fn from_all_entries(slot_index: u8, storage_map: &StorageMap) -> Self { - let map_entries = Vec::from_iter(storage_map.entries().map(|(k, v)| (*k, *v))); - Self { - slot_index, - too_many_entries: false, - map_entries, + if storage_map.num_entries() > Self::MAX_RETURN_ENTRIES { + Self::too_many_entries(slot_index) + } else { + let map_entries = Vec::from_iter(storage_map.entries().map(|(k, v)| (*k, *v))); + Self { + slot_index, + too_many_entries: false, + map_entries, + } } } - fn from_specific_keys(slot_index: u8, _keys: &[Word], storage_map: &StorageMap) -> Self { - // TODO For now, we return all entries instead of specific keys with proofs - Self::from_all_entries(slot_index, storage_map) + fn from_specific_keys(slot_index: u8, keys: &[Word], storage_map: &StorageMap) -> Self { + if keys.len() > Self::MAX_RETURN_ENTRIES { + Self::too_many_entries(slot_index) + } else { + // TODO For now, we return all entries instead of specific keys with proofs + Self::from_all_entries(slot_index, storage_map) + } } pub fn too_many_entries(slot_index: u8) -> Self {