Skip to main content

miden_client/rpc/domain/
storage_map.rs

1use alloc::collections::BTreeMap;
2use alloc::string::ToString;
3use alloc::vec::Vec;
4
5use miden_protocol::Word;
6use miden_protocol::account::{StorageMapKey, StorageMapPatchEntries, StorageSlotName};
7use miden_protocol::block::BlockNumber;
8
9use crate::rpc::domain::MissingFieldHelper;
10use crate::rpc::{RpcError, generated as proto};
11
12// STORAGE MAP INFO
13// ================================================================================================
14
15/// The merged result of syncing an account's storage maps over a block range.
16///
17/// The node reports per-block map entry updates that may repeat a `(slot, key)` across blocks;
18/// these are merged per slot into the absolute changed entries (latest block wins per key). Also
19/// provides the current chain tip observed while processing the request.
20pub struct StorageMapInfo {
21    /// Current chain tip.
22    pub chain_tip: BlockNumber,
23    /// The block number of the last check included in this response.
24    pub block_number: BlockNumber,
25    /// The absolute changed entries per storage map slot, merged from the per-block updates.
26    pub map_entries: BTreeMap<StorageSlotName, StorageMapPatchEntries>,
27}
28
29// STORAGE MAP INFO CONVERSION
30// ================================================================================================
31
32impl TryFrom<proto::rpc::SyncAccountStorageMapsResponse> for StorageMapInfo {
33    type Error = RpcError;
34
35    fn try_from(value: proto::rpc::SyncAccountStorageMapsResponse) -> Result<Self, Self::Error> {
36        let pagination_info = value.pagination_info.ok_or(
37            proto::rpc::SyncAccountStorageMapsResponse::missing_field(stringify!(pagination_info)),
38        )?;
39
40        let mut updates = value
41            .updates
42            .into_iter()
43            .map(storage_map_update_from_proto)
44            .collect::<Result<Vec<_>, _>>()?;
45
46        // The node may report the same `(slot, key)` in more than one block, folding the updates
47        // in ascending block order lets the latest block win, with an empty value encoding a
48        // cleared entry.
49        updates.sort_by_key(|(block_num, ..)| *block_num);
50        let mut map_entries: BTreeMap<StorageSlotName, StorageMapPatchEntries> = BTreeMap::new();
51        for (_, slot_name, key, value) in updates {
52            map_entries.entry(slot_name).or_default().insert(key, value);
53        }
54
55        Ok(Self {
56            chain_tip: pagination_info.chain_tip.into(),
57            block_number: pagination_info.block_num.into(),
58            map_entries,
59        })
60    }
61}
62
63// STORAGE MAP UPDATE
64// ================================================================================================
65
66/// Converts a single proto storage-map update into its block number, slot name, key, and new value.
67fn storage_map_update_from_proto(
68    value: proto::rpc::StorageMapUpdate,
69) -> Result<(BlockNumber, StorageSlotName, StorageMapKey, Word), RpcError> {
70    let block_num = BlockNumber::from(value.block_num);
71
72    let slot_name = StorageSlotName::new(value.slot_name)
73        .map_err(|err| RpcError::InvalidResponse(err.to_string()))?;
74
75    let key: StorageMapKey = value
76        .key
77        .ok_or(proto::rpc::StorageMapUpdate::missing_field(stringify!(key)))?
78        .try_into()?;
79
80    let map_value: Word = value
81        .value
82        .ok_or(proto::rpc::StorageMapUpdate::missing_field(stringify!(value)))?
83        .try_into()?;
84
85    Ok((block_num, slot_name, key, map_value))
86}