Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
119 changes: 44 additions & 75 deletions crates/proto/src/domain/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,9 +200,9 @@ impl TryFrom<proto::rpc_store::account_storage_details::AccountStorageMapDetails

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
// Extract map_entries from the MapEntries message
let map_entries = if let Some(entries) = entries {
entries
.entries
.into_iter()
.map(|entry| {
Expand All @@ -220,8 +220,9 @@ impl TryFrom<proto::rpc_store::account_storage_details::AccountStorageMapDetails
.try_into()?;
Ok((key, value))
})
.collect::<Result<Vec<_>, ConversionError>>()?,
None => Vec::new(),
.collect::<Result<Vec<_>, ConversionError>>()?
} else {
Vec::new()
};

Ok(Self {
Expand Down Expand Up @@ -368,12 +369,14 @@ impl AccountVaultDetails {
}
}
}

pub fn empty() -> Self {
Self {
too_many_assets: false,
assets: Vec::new(),
}
}

fn too_many() -> Self {
Self {
too_many_assets: true,
Expand Down Expand Up @@ -424,31 +427,40 @@ impl AccountStorageMapDetails {
const MAX_RETURN_ENTRIES: usize = 1000;

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().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)
}
},
}
}

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 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)
}

pub fn too_many_entries(slot_index: u8) -> Self {
Self {
slot_index,
Expand Down Expand Up @@ -611,74 +623,31 @@ impl From<AccountDetails> for proto::rpc_store::account_proof_response::AccountD
}
}

// ACCOUNT PROOF
impl From<AccountStorageMapDetails>
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,
} = 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(),
};
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,
entries: Some(entries),
}
}
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct StorageMapEntry {
pub key: Word,
pub value: Word,
}

impl
TryFrom<proto::rpc_store::account_storage_details::account_storage_map_details::map_entries::StorageMapEntry>
for StorageMapEntry
{
type Error = ConversionError;

fn try_from(
value: proto::rpc_store::account_storage_details::account_storage_map_details::map_entries::StorageMapEntry,
) -> Result<Self, Self::Error> {
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<StorageMapEntry>
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)),
entries,
}
}
}
Expand Down
59 changes: 31 additions & 28 deletions crates/proto/src/generated/rpc_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<super::super::primitives::Digest>,
/// 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
Expand Down Expand Up @@ -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<u8>>,
/// Account asset vault data; empty if vault commitments matched
/// Account asset vault data; empty if vault commitments matched or the requester
/// omitted it in the request.
#[prost(message, optional, tag = "4")]
pub vault_details: ::core::option::Option<super::AccountVaultDetails>,
}
Expand Down Expand Up @@ -231,12 +234,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")]
Expand All @@ -248,7 +251,7 @@ pub struct SyncStateRequest {
/// it won't be included in the response.
#[prost(message, repeated, tag = "2")]
pub account_ids: ::prost::alloc::vec::Vec<super::account::AccountId>,
/// 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<u32>,
}
Expand Down Expand Up @@ -277,7 +280,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.
Expand Down Expand Up @@ -317,14 +320,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<BlockRange>,
/// 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<u32>,
}
Expand All @@ -349,7 +352,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 {
Expand Down Expand Up @@ -410,11 +413,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 {
Expand All @@ -431,7 +434,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.
Expand Down Expand Up @@ -779,9 +782,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.
Expand Down Expand Up @@ -809,20 +812,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,
Expand Down Expand Up @@ -1004,9 +1007,9 @@ pub mod rpc_server {
tonic::Response<super::SyncNullifiersResponse>,
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.
Expand All @@ -1020,20 +1023,20 @@ pub mod rpc_server {
tonic::Response<super::SyncNotesResponse>,
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,
Expand Down
5 changes: 5 additions & 0 deletions crates/store/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down
Loading