Skip to content
Open
Show file tree
Hide file tree
Changes from all 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 @@ -50,6 +50,7 @@
- [BREAKING] Removed `--wallet-filepath` / `--counter-filepath` flags and the `MIDEN_MONITOR_WALLET_FILEPATH` / `MIDEN_MONITOR_COUNTER_FILEPATH` env vars from the network monitor. The monitor now keeps wallet and counter accounts fully in memory and regenerates them on every startup; the dashboard's counter value resets to zero on restart.
- Added `--counter-pending-unhealthy-threshold` (env `MIDEN_MONITOR_COUNTER_PENDING_UNHEALTHY_THRESHOLD`, default `5`) to the network monitor: the Network Transactions card now flips unhealthy when the gap between expected and observed counter values stays above the threshold for three consecutive polls.
- Allowed network transaction submission conditionally via the gRPC `SubmitProvenTx` and `SubmitProvenTxBatch` endpoints: the NTX builder can now send a key in the `x-miden-network-tx-auth` header that enables submitting network transactions ([#2131](https://github.com/0xMiden/node/issues/2131)).
- [BREAKING] `GetAccount` can now return all storage map entries with a single request ([#2121](https://github.com/0xMiden/node/issues/2121)).
- Added a `miden-ntx-builder bootstrap` command that initializes the ntx-builder database with the genesis block fetched from the node RPC. The `start` command now requires a bootstrapped database instead of fetching the genesis block from the committed-block subscription on first run ([#2149](https://github.com/0xMiden/node/pull/2149)).

## v0.14.11 (TBD)
Expand Down
2 changes: 1 addition & 1 deletion bin/network-monitor/src/counter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -836,7 +836,7 @@ fn build_account_request(
details: Some(miden_node_proto::generated::rpc::account_request::AccountDetailRequest {
code_commitment,
asset_vault_commitment,
storage_maps: vec![],
storage_request: None,
}),
}
}
Expand Down
22 changes: 12 additions & 10 deletions bin/stress-test/src/store/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,11 +128,7 @@ async fn get_account(

let start = Instant::now();
let request = AccountRequest::try_from(request).expect("request should be valid");
let response: proto::rpc::AccountResponse = state
.get_account(request.account_id, request.block_num, request.details)
.await
.unwrap()
.into();
let response: proto::rpc::AccountResponse = state.get_account(request).await.unwrap().into();
let duration = start.elapsed();

let details = response.details;
Expand Down Expand Up @@ -170,19 +166,25 @@ fn get_account_request(
storage_map_slot: String,
) -> proto::rpc::AccountRequest {
use proto::rpc::account_request::AccountDetailRequest;
use proto::rpc::account_request::account_detail_request::StorageMapDetailRequest;
use proto::rpc::account_request::account_detail_request::storage_map_detail_request::SlotData;
use proto::rpc::account_request::account_detail_request::{
StorageMapDetailRequest,
StorageMapDetailRequests,
StorageRequest,
};

proto::rpc::AccountRequest {
account_id: Some(proto::account::AccountId { id: account_id.to_bytes() }),
block_num: None,
details: Some(AccountDetailRequest {
code_commitment: None,
asset_vault_commitment: Some(proto::primitives::Digest::from(Word::empty())),
storage_maps: vec![StorageMapDetailRequest {
slot_name: storage_map_slot,
slot_data: Some(SlotData::AllEntries(true)),
}],
storage_request: Some(StorageRequest::StorageMaps(StorageMapDetailRequests {
storage_maps: vec![StorageMapDetailRequest {
slot_name: storage_map_slot,
slot_data: Some(SlotData::AllEntries(true)),
}],
})),
}),
}
}
Expand Down
34 changes: 29 additions & 5 deletions crates/proto/src/domain/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,10 +163,18 @@ impl TryFrom<proto::rpc::AccountRequest> for AccountRequest {
}

/// Represents a request for account details alongside specific storage data.
#[derive(Debug)]
pub struct AccountDetailRequest {
pub code_commitment: Option<Word>,
pub asset_vault_commitment: Option<Word>,
pub storage_requests: Vec<StorageMapRequest>,
pub storage_request: AccountStorageRequest,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AccountStorageRequest {
None,
AllStorageMaps,
Explicit(Vec<StorageMapRequest>),
}

impl TryFrom<proto::rpc::account_request::AccountDetailRequest> for AccountDetailRequest {
Expand All @@ -175,10 +183,12 @@ impl TryFrom<proto::rpc::account_request::AccountDetailRequest> for AccountDetai
fn try_from(
value: proto::rpc::account_request::AccountDetailRequest,
) -> Result<Self, Self::Error> {
use proto::rpc::account_request::account_detail_request::StorageRequest as ProtoStorageRequest;

let proto::rpc::account_request::AccountDetailRequest {
code_commitment,
asset_vault_commitment,
storage_maps,
storage_request,
} = value;

let code_commitment =
Expand All @@ -187,13 +197,27 @@ impl TryFrom<proto::rpc::account_request::AccountDetailRequest> for AccountDetai
.map(TryFrom::try_from)
.transpose()
.context("asset_vault_commitment")?;
let storage_requests =
try_convert(storage_maps).collect::<Result<_, _>>().context("storage_maps")?;

let storage_request = match storage_request {
None => AccountStorageRequest::None,
Some(ProtoStorageRequest::AllStorageMaps(true)) => {
AccountStorageRequest::AllStorageMaps
},
Some(ProtoStorageRequest::AllStorageMaps(false)) => {
return Err(ConversionError::message("all_storage_maps must be true when set"));
},
Some(ProtoStorageRequest::StorageMaps(requests)) => {
let requests = try_convert(requests.storage_maps)
.collect::<Result<_, _>>()
.context("storage_maps")?;
AccountStorageRequest::Explicit(requests)
},
};

Ok(AccountDetailRequest {
code_commitment,
asset_vault_commitment,
storage_requests,
storage_request,
})
}
}
Expand Down
71 changes: 71 additions & 0 deletions crates/proto/src/domain/account/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,74 @@ fn account_storage_map_details_from_forest_entries_limit_exceeded() {
assert_eq!(details.slot_name, slot_name);
assert_eq!(details.entries, StorageMapEntries::LimitExceeded);
}

#[test]
fn account_detail_request_converts_all_storage_maps() {
use crate::generated::rpc::account_request::account_detail_request::StorageRequest;

let request = crate::generated::rpc::account_request::AccountDetailRequest {
code_commitment: None,
asset_vault_commitment: None,
storage_request: Some(StorageRequest::AllStorageMaps(true)),
};

let request = AccountDetailRequest::try_from(request).unwrap();

assert_eq!(request.storage_request, AccountStorageRequest::AllStorageMaps);
}

#[test]
fn account_detail_request_rejects_false_all_storage_maps() {
use crate::generated::rpc::account_request::account_detail_request::StorageRequest;

let request = crate::generated::rpc::account_request::AccountDetailRequest {
code_commitment: None,
asset_vault_commitment: None,
storage_request: Some(StorageRequest::AllStorageMaps(false)),
};

let err = AccountDetailRequest::try_from(request).unwrap_err();

assert!(err.to_string().contains("all_storage_maps"));
}

#[test]
fn account_detail_request_converts_explicit_storage_maps() {
use crate::generated::rpc::account_request::account_detail_request::{
StorageMapDetailRequest,
StorageMapDetailRequests,
StorageRequest,
storage_map_detail_request,
};

let request = crate::generated::rpc::account_request::AccountDetailRequest {
code_commitment: None,
asset_vault_commitment: None,
storage_request: Some(StorageRequest::StorageMaps(StorageMapDetailRequests {
storage_maps: vec![StorageMapDetailRequest {
slot_name: "miden::test::storage::slot".to_string(),
slot_data: Some(storage_map_detail_request::SlotData::AllEntries(true)),
}],
})),
};

let request = AccountDetailRequest::try_from(request).unwrap();

assert!(matches!(
request.storage_request,
AccountStorageRequest::Explicit(ref requests) if requests.len() == 1
));
}

#[test]
fn account_detail_request_allows_no_storage_slot_data() {
let request = crate::generated::rpc::account_request::AccountDetailRequest {
code_commitment: None,
asset_vault_commitment: None,
storage_request: None,
};

let request = AccountDetailRequest::try_from(request).unwrap();

assert_eq!(request.storage_request, AccountStorageRequest::None);
}
2 changes: 2 additions & 0 deletions crates/rpc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ Returns an account witness (Merkle proof of inclusion in the account tree) and o

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.

Storage map details can be requested either for explicitly selected maps or for all storage map slots. Full-map responses are bounded by the response payload budget; maps that do not fit are returned with `too_many_entries` so clients can follow up with `SyncAccountStorageMaps`.

If `block_num` is provided, returns the state at that historical block; otherwise, returns the latest state.

---
Expand Down
27 changes: 13 additions & 14 deletions crates/rpc/src/server/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use miden_node_proto::decode::{
read_block_range,
read_root,
};
use miden_node_proto::domain::account::{AccountRequest, SlotData};
use miden_node_proto::domain::account::{AccountRequest, AccountStorageRequest, SlotData};
use miden_node_proto::domain::block::InvalidBlockRange;
use miden_node_proto::generated::rpc::MempoolStats as ProtoMempoolStats;
use miden_node_proto::generated::rpc::api_server::{self, Api};
Expand Down Expand Up @@ -709,22 +709,21 @@ impl api_server::Api for RpcService {
// Validate total storage map key limit before forwarding to store
if let Some(details) = &request.details {
let _span = info_span!(target: COMPONENT, "validate_storage_map_keys").entered();
let total_keys: usize = details
.storage_requests
.iter()
.filter_map(|d| match &d.slot_data {
SlotData::All => None,
SlotData::MapKeys(items) => Some(items.len()),
})
.sum();
let total_keys: usize = match &details.storage_request {
AccountStorageRequest::None | AccountStorageRequest::AllStorageMaps => 0,
AccountStorageRequest::Explicit(requests) => requests
.iter()
.filter_map(|request| match &request.slot_data {
SlotData::All => None,
SlotData::MapKeys(items) => Some(items.len()),
})
.sum(),
};
check::<QueryParamStorageMapKeyTotalLimit>(total_keys)?;
}

let account_data = self
.store
.get_account(request.account_id, request.block_num, request.details)
.await
.map_err(get_account_error_to_status)?;
let account_data =
self.store.get_account(request).await.map_err(get_account_error_to_status)?;
Ok(Response::new(account_data.into()))
}

Expand Down
2 changes: 2 additions & 0 deletions crates/store/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ Returns an account witness (Merkle proof of inclusion in the account tree) and o

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.

Storage map details can be requested either for explicitly selected maps or for all storage map slots. Full-map responses are bounded by the response payload budget; maps that do not fit are returned with `too_many_entries` so clients can follow up with `SyncAccountStorageMaps`.

If `block_num` is provided, returns the state at that historical block; otherwise, returns the latest state.

---
Expand Down
Loading
Loading