1use alloc::collections::BTreeMap;
2use alloc::vec::Vec;
3use core::fmt::{self, Debug, Formatter};
4
5use miden_protocol::account::{
6 Account, AccountCode, AccountHeader, AccountId, AccountStorage, AccountStorageHeader,
7 StorageMap, StorageMapKey, StorageSlot, StorageSlotName, StorageSlotType,
8};
9use miden_protocol::asset::{Asset, AssetVault};
10use miden_protocol::block::account_tree::AccountWitness;
11use miden_protocol::crypto::merkle::SparseMerklePath;
12use miden_protocol::crypto::merkle::smt::PartialSmt;
13use miden_objects::DecodeMessageExt;
14use miden_protocol::{EMPTY_WORD, Word};
15use miden_tx::utils::serde::{Deserializable, Serializable};
16use thiserror::Error;
17
18use crate::alloc::string::ToString;
19use crate::rpc::{AccountStateAt, RpcError};
20use crate::rpc::domain::MissingFieldHelper;
21use crate::rpc::generated::rpc::get_account_request::account_detail_request::storage_map_detail_request::{MapKeys, SlotData};
22use crate::rpc::generated::rpc::get_account_request::account_detail_request::{
23 StorageMapDetailRequest, StorageMapDetailRequests, StorageRequest,
24};
25use crate::rpc::generated::{self as proto};
26
27impl Debug for proto::rpc::RegisterAccountRequest {
32 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
33 f.debug_struct("RegisterAccountRequest")
34 .field("account_id", &self.account_id)
35 .finish_non_exhaustive()
36 }
37}
38
39#[cfg(feature = "tonic")]
43impl proto::rpc::get_account_response::AccountDetails {
44 pub fn into_domain(
58 self,
59 known_account_codes: &BTreeMap<Word, AccountCode>,
60 storage_requirements: &AccountStorageRequirements,
61 ) -> Result<AccountDetails, crate::rpc::RpcError> {
62 use crate::rpc::RpcError;
63 use crate::rpc::domain::MissingFieldHelper;
64
65 let proto::rpc::get_account_response::AccountDetails {
66 header,
67 storage_details,
68 code,
69 vault_details,
70 } = self;
71 let header: AccountHeader = header
72 .ok_or(proto::rpc::get_account_response::AccountDetails::missing_field(stringify!(
73 header
74 )))?
75 .decode_and_verify()?;
76
77 let storage_details: AccountStorageDetails = storage_details
78 .ok_or(proto::rpc::get_account_response::AccountDetails::missing_field(stringify!(
79 storage_details
80 )))?
81 .try_into()?;
82
83 storage_details.validate_against_request(storage_requirements)?;
84
85 let code = {
89 let received_code: Option<AccountCode> =
90 code.map(DecodeMessageExt::decode_and_verify).transpose()?;
91 match received_code {
92 Some(code) => code,
93 None => known_account_codes
94 .get(&header.code_commitment())
95 .ok_or(RpcError::InvalidResponse(
96 "Account code was not provided, but the response did not contain it either"
97 .into(),
98 ))?
99 .clone(),
100 }
101 };
102
103 let vault_details = vault_details
104 .ok_or(proto::rpc::AccountVaultDetails::missing_field(stringify!(vault_details)))?
105 .try_into()?;
106
107 Ok(AccountDetails {
108 header,
109 storage_details,
110 code,
111 vault_details,
112 })
113 }
114}
115
116#[derive(Clone, Debug)]
121pub struct AccountDetails {
122 pub header: AccountHeader,
123 pub storage_details: AccountStorageDetails,
124 pub code: AccountCode,
125 pub vault_details: AccountVaultDetails,
126}
127
128impl TryFrom<&AccountDetails> for Account {
129 type Error = RpcError;
130
131 fn try_from(details: &AccountDetails) -> Result<Self, Self::Error> {
137 if details.vault_details.too_many_assets {
138 return Err(RpcError::ExpectedDataMissing(
139 "cannot build account: vault has too many assets".into(),
140 ));
141 }
142
143 if let Some(slot_name) = details
144 .storage_details
145 .map_details
146 .iter()
147 .find(|m| m.is_limit_exceeded())
148 .map(|m| &m.slot_name)
149 {
150 return Err(RpcError::ExpectedDataMissing(format!(
151 "cannot build account: storage map slot '{slot_name}' has too many entries",
152 )));
153 }
154
155 let mut slots: Vec<StorageSlot> = Vec::new();
156
157 for slot_header in details.storage_details.header.slots() {
158 match slot_header.slot_type() {
159 StorageSlotType::Value => {
160 slots.push(StorageSlot::with_value(
161 slot_header.name().clone(),
162 slot_header.value(),
163 ));
164 },
165 StorageSlotType::Map => {
166 let map_details = details
167 .storage_details
168 .find_map_details(slot_header.name())
169 .ok_or_else(|| {
170 RpcError::ExpectedDataMissing(format!(
171 "slot '{}' is a map but has no map_details in response",
172 slot_header.name()
173 ))
174 })?;
175
176 let storage_map = map_details
177 .entries
178 .clone()
179 .into_storage_map()
180 .ok_or_else(|| {
181 RpcError::ExpectedDataMissing(format!(
182 "slot '{}' did not come back with all its entries, so the full \
183 account cannot be built",
184 slot_header.name(),
185 ))
186 })?
187 .map_err(|err| {
188 RpcError::InvalidResponse(format!(
189 "the rpc api returned a non-valid map entry: {err}"
190 ))
191 })?;
192
193 slots.push(StorageSlot::with_map(slot_header.name().clone(), storage_map));
194 },
195 }
196 }
197
198 let asset_vault = AssetVault::new(&details.vault_details.assets).map_err(|err| {
199 RpcError::InvalidResponse(format!("rpc api returned non-valid assets: {err}"))
200 })?;
201
202 let account_storage = AccountStorage::new(slots).map_err(|err| {
203 RpcError::InvalidResponse(format!("rpc api returned non-valid storage slots: {err}"))
204 })?;
205
206 Account::new(
207 details.header.id(),
208 asset_vault,
209 account_storage,
210 details.code.clone(),
211 details.header.nonce(),
212 None,
213 )
214 .map_err(|err| {
215 RpcError::InvalidResponse(format!(
216 "failed to construct account from rpc api response: {err}"
217 ))
218 })
219 }
220}
221
222#[derive(Clone, Debug)]
227pub struct AccountStorageDetails {
228 pub header: AccountStorageHeader,
230 pub map_details: Vec<AccountStorageMapDetails>,
232}
233
234impl AccountStorageDetails {
235 pub fn find_map_details(&self, target: &StorageSlotName) -> Option<&AccountStorageMapDetails> {
239 self.map_details.iter().find(|map_detail| map_detail.slot_name == *target)
240 }
241
242 pub fn validate_against_request(
248 &self,
249 storage_requirements: &AccountStorageRequirements,
250 ) -> Result<(), RpcError> {
251 for map_detail in &self.map_details {
252 let StorageMapEntries::PartialMap { map_keys, .. } = &map_detail.entries else {
253 continue;
254 };
255
256 let requested_keys = storage_requirements.keys_for_slot(&map_detail.slot_name);
257 if let Some(key) = requested_keys.iter().find(|key| !map_keys.contains(key)) {
258 return Err(RpcError::InvalidResponse(format!(
259 "partial storage map for slot '{}' does not cover requested key {}",
260 map_detail.slot_name,
261 key.to_hex(),
262 )));
263 }
264 if let Some(key) = map_keys.iter().find(|key| !requested_keys.contains(key)) {
265 return Err(RpcError::InvalidResponse(format!(
266 "partial storage map for slot '{}' covers key {}, which was not requested",
267 map_detail.slot_name,
268 key.to_hex(),
269 )));
270 }
271 }
272
273 Ok(())
274 }
275}
276
277impl TryFrom<proto::rpc::AccountStorageDetails> for AccountStorageDetails {
278 type Error = RpcError;
279
280 fn try_from(value: proto::rpc::AccountStorageDetails) -> Result<Self, Self::Error> {
281 let header: AccountStorageHeader = value
282 .header
283 .ok_or(proto::account::AccountStorageHeader::missing_field(stringify!(header)))?
284 .decode_and_verify()?;
285 let map_details = value
286 .map_details
287 .into_iter()
288 .map(core::convert::TryInto::try_into)
289 .collect::<Result<Vec<AccountStorageMapDetails>, RpcError>>()?;
290
291 for map_detail in &map_details {
295 let StorageMapEntries::PartialMap { partial_smt, .. } = &map_detail.entries else {
296 continue;
297 };
298
299 let slot = header
300 .slots()
301 .find(|slot| *slot.name() == map_detail.slot_name)
302 .ok_or_else(|| {
303 RpcError::InvalidResponse(format!(
304 "partial storage map references slot '{}', which is absent from the \
305 storage header",
306 map_detail.slot_name,
307 ))
308 })?;
309 if slot.slot_type() != StorageSlotType::Map {
310 return Err(RpcError::InvalidResponse(format!(
311 "partial storage map references slot '{}', which is not a map",
312 map_detail.slot_name,
313 )));
314 }
315 if partial_smt.root() != slot.value() {
316 return Err(RpcError::InvalidResponse(format!(
317 "partial storage map for slot '{}' has root {} but the storage header reports \
318 {}",
319 map_detail.slot_name,
320 partial_smt.root(),
321 slot.value(),
322 )));
323 }
324 }
325
326 Ok(Self { header, map_details })
327 }
328}
329
330#[derive(Clone, Debug)]
334pub struct AccountStorageMapDetails {
335 pub slot_name: StorageSlotName,
337 pub entries: StorageMapEntries,
339}
340
341impl AccountStorageMapDetails {
342 pub const MAX_PARTIAL_MAP_KEYS: usize = 64;
345
346 pub fn is_limit_exceeded(&self) -> bool {
350 matches!(self.entries, StorageMapEntries::LimitExceeded)
351 }
352}
353
354impl TryFrom<proto::rpc::account_storage_details::AccountStorageMapDetails>
355 for AccountStorageMapDetails
356{
357 type Error = RpcError;
358
359 fn try_from(
360 value: proto::rpc::account_storage_details::AccountStorageMapDetails,
361 ) -> Result<Self, Self::Error> {
362 use proto::rpc::account_storage_details::account_storage_map_details::Result as ProtoResult;
363
364 let slot_name = StorageSlotName::new(value.slot_name)
365 .map_err(|err| RpcError::ExpectedDataMissing(err.to_string()))?;
366
367 let entries = match value.result {
368 Some(ProtoResult::TooManyEntries(true)) => StorageMapEntries::LimitExceeded,
369 Some(ProtoResult::TooManyEntries(false)) => {
370 return Err(RpcError::InvalidResponse(
371 "too_many_entries must be true when set".into(),
372 ));
373 },
374 Some(ProtoResult::AllEntries(all_entries)) => {
375 let entries = all_entries
376 .entries
377 .into_iter()
378 .map(core::convert::TryInto::try_into)
379 .collect::<Result<Vec<StorageMapEntry>, RpcError>>()?;
380 StorageMapEntries::AllEntries(entries)
381 },
382 Some(ProtoResult::PartialMap(partial_map)) => {
383 if partial_map.map_keys.len() > Self::MAX_PARTIAL_MAP_KEYS {
384 return Err(RpcError::InvalidResponse(format!(
385 "partial storage map for slot '{slot_name}' contains {} keys, exceeding \
386 the limit of {}",
387 partial_map.map_keys.len(),
388 Self::MAX_PARTIAL_MAP_KEYS,
389 )));
390 }
391
392 let map_keys = partial_map
393 .map_keys
394 .into_iter()
395 .map(|key| Word::try_from(key).map(StorageMapKey::new))
396 .collect::<Result<Vec<_>, _>>()?;
397 if let Some(key) = first_duplicate_key(&map_keys) {
398 return Err(RpcError::InvalidResponse(format!(
399 "partial storage map for slot '{slot_name}' repeats key {}",
400 key.to_hex(),
401 )));
402 }
403
404 let partial_smt: PartialSmt = partial_map
405 .partial_smt
406 .ok_or(proto::rpc::account_storage_details::account_storage_map_details::PartialStorageMap::missing_field(
407 stringify!(partial_smt),
408 ))?
409 .decode_and_verify()?;
410
411 for key in &map_keys {
414 partial_smt.get_value(&key.hash().as_word()).map_err(|_| {
415 RpcError::InvalidResponse(format!(
416 "partial storage map for slot '{slot_name}' does not track key {}",
417 key.to_hex(),
418 ))
419 })?;
420 }
421
422 StorageMapEntries::PartialMap { map_keys, partial_smt }
423 },
424 None => {
425 return Err(RpcError::InvalidResponse(format!(
426 "storage map details for slot '{slot_name}' carry no result",
427 )));
428 },
429 };
430
431 Ok(Self { slot_name, entries })
432 }
433}
434
435fn first_duplicate_key(keys: &[StorageMapKey]) -> Option<&StorageMapKey> {
440 keys.iter()
441 .enumerate()
442 .find_map(|(index, key)| keys[..index].contains(key).then_some(key))
443}
444
445#[derive(Clone, Debug)]
450pub struct StorageMapEntry {
451 pub key: StorageMapKey,
452 pub value: Word,
453}
454
455impl TryFrom<proto::rpc::account_storage_details::account_storage_map_details::all_map_entries::StorageMapEntry>
456 for StorageMapEntry
457{
458 type Error = RpcError;
459
460 fn try_from(value: proto::rpc::account_storage_details::account_storage_map_details::all_map_entries::StorageMapEntry) -> Result<Self, Self::Error> {
461 let key = Word::try_from(value.key.ok_or(RpcError::ExpectedDataMissing("key".into()))?)
462 .map(StorageMapKey::new)?;
463 let value = value.value.ok_or(RpcError::ExpectedDataMissing("value".into()))?.try_into()?;
464 Ok(Self { key, value })
465 }
466}
467
468#[derive(Clone, Debug)]
474pub enum StorageMapEntries {
475 LimitExceeded,
478 AllEntries(Vec<StorageMapEntry>),
480 PartialMap {
486 map_keys: Vec<StorageMapKey>,
488 partial_smt: PartialSmt,
490 },
491}
492
493impl StorageMapEntries {
494 pub fn into_storage_map(
499 self,
500 ) -> Option<Result<StorageMap, miden_protocol::errors::StorageMapError>> {
501 match self {
502 StorageMapEntries::AllEntries(entries) => {
503 Some(StorageMap::with_entries(entries.into_iter().map(|e| (e.key, e.value))))
504 },
505 StorageMapEntries::LimitExceeded | StorageMapEntries::PartialMap { .. } => None,
506 }
507 }
508}
509
510#[derive(Clone, Debug)]
514pub struct AccountVaultDetails {
515 pub too_many_assets: bool,
518 pub assets: Vec<Asset>,
520}
521
522impl TryFrom<proto::rpc::AccountVaultDetails> for AccountVaultDetails {
523 type Error = RpcError;
524
525 fn try_from(value: proto::rpc::AccountVaultDetails) -> Result<Self, Self::Error> {
526 let too_many_assets = value.too_many_assets;
527 let assets = value
528 .assets
529 .into_iter()
530 .map(DecodeMessageExt::decode_and_verify)
531 .collect::<Result<Vec<Asset>, _>>()?;
532
533 Ok(Self { too_many_assets, assets })
534 }
535}
536
537#[derive(Clone, Debug)]
542pub struct AccountProof {
543 account_witness: AccountWitness,
545 state_headers: Option<AccountDetails>,
547}
548
549impl AccountProof {
550 pub fn new(
552 account_witness: AccountWitness,
553 account_details: Option<AccountDetails>,
554 ) -> Result<Self, AccountProofError> {
555 if let Some(AccountDetails {
556 header: account_header,
557 storage_details: _,
558 code,
559 ..
560 }) = &account_details
561 {
562 if account_header.to_commitment() != account_witness.state_commitment() {
563 return Err(AccountProofError::InconsistentAccountCommitment);
564 }
565 if account_header.id() != account_witness.id() {
566 return Err(AccountProofError::InconsistentAccountId);
567 }
568 if code.commitment() != account_header.code_commitment() {
569 return Err(AccountProofError::InconsistentCodeCommitment);
570 }
571 }
572
573 Ok(Self {
574 account_witness,
575 state_headers: account_details,
576 })
577 }
578
579 pub fn account_id(&self) -> AccountId {
581 self.account_witness.id()
582 }
583
584 pub fn account_header(&self) -> Option<&AccountHeader> {
586 self.state_headers.as_ref().map(|account_details| &account_details.header)
587 }
588
589 pub fn storage_header(&self) -> Option<&AccountStorageHeader> {
591 self.state_headers
592 .as_ref()
593 .map(|account_details| &account_details.storage_details.header)
594 }
595
596 pub fn storage_details(&self) -> Option<&AccountStorageDetails> {
598 self.state_headers.as_ref().map(|d| &d.storage_details)
599 }
600
601 pub fn vault_details(&self) -> Option<&AccountVaultDetails> {
603 self.state_headers.as_ref().map(|d| &d.vault_details)
604 }
605
606 pub fn find_map_details(
608 &self,
609 slot_name: &StorageSlotName,
610 ) -> Option<&AccountStorageMapDetails> {
611 self.state_headers
612 .as_ref()
613 .and_then(|details| details.storage_details.find_map_details(slot_name))
614 }
615
616 pub fn account_code(&self) -> Option<&AccountCode> {
618 self.state_headers.as_ref().map(|headers| &headers.code)
619 }
620
621 pub fn code_commitment(&self) -> Option<Word> {
623 self.account_code().map(AccountCode::commitment)
624 }
625
626 pub fn account_commitment(&self) -> Word {
628 self.account_witness.state_commitment()
629 }
630
631 pub fn account_witness(&self) -> &AccountWitness {
632 &self.account_witness
633 }
634
635 pub fn merkle_proof(&self) -> &SparseMerklePath {
637 self.account_witness.path()
638 }
639
640 pub fn into_parts(self) -> (AccountWitness, Option<AccountDetails>) {
642 (self.account_witness, self.state_headers)
643 }
644
645 pub fn into_details(self) -> Option<AccountDetails> {
647 self.state_headers
648 }
649
650 pub fn details_mut(&mut self) -> Option<&mut AccountDetails> {
656 self.state_headers.as_mut()
657 }
658}
659
660#[cfg(feature = "tonic")]
661impl TryFrom<proto::rpc::GetAccountResponse> for AccountProof {
662 type Error = RpcError;
663 fn try_from(account_proof: proto::rpc::GetAccountResponse) -> Result<Self, Self::Error> {
664 let Some(witness) = account_proof.witness else {
665 return Err(RpcError::ExpectedDataMissing(
666 "GetAccount returned an account without witness".to_string(),
667 ));
668 };
669
670 let details: Option<AccountDetails> = {
671 match account_proof.details {
672 None => None,
673 Some(details) => Some(
674 details
675 .into_domain(&BTreeMap::new(), &AccountStorageRequirements::default())?,
676 ),
677 }
678 };
679 AccountProof::new(witness.decode_and_verify()?, details)
680 .map_err(|err| RpcError::InvalidResponse(format!("{err}")))
681 }
682}
683
684#[derive(Clone, Debug, Default, Eq, PartialEq)]
693pub struct AccountStorageRequirements(BTreeMap<StorageSlotName, Vec<StorageMapKey>>);
694
695impl AccountStorageRequirements {
696 pub fn new<'a>(
702 slots_and_keys: impl IntoIterator<
703 Item = (StorageSlotName, impl IntoIterator<Item = &'a StorageMapKey>),
704 >,
705 ) -> Self {
706 let map = slots_and_keys
707 .into_iter()
708 .map(|(slot_name, keys_iter)| {
709 let mut keys_vec: Vec<StorageMapKey> = Vec::new();
710 for key in keys_iter {
711 if !keys_vec.contains(key) {
712 keys_vec.push(*key);
713 }
714 }
715 (slot_name, keys_vec)
716 })
717 .collect();
718
719 AccountStorageRequirements(map)
720 }
721
722 pub fn all_entries(slot_names: &[StorageSlotName]) -> Self {
725 AccountStorageRequirements(
726 slot_names.iter().map(|name| (name.clone(), Vec::new())).collect(),
727 )
728 }
729
730 pub fn inner(&self) -> &BTreeMap<StorageSlotName, Vec<StorageMapKey>> {
731 &self.0
732 }
733
734 pub fn keys_for_slot(&self, slot_name: &StorageSlotName) -> &[StorageMapKey] {
736 self.0.get(slot_name).map_or(&[], Vec::as_slice)
737 }
738}
739
740impl From<AccountStorageRequirements> for Vec<StorageMapDetailRequest> {
741 fn from(value: AccountStorageRequirements) -> Vec<StorageMapDetailRequest> {
742 let request_map = value.0;
743 let mut requests = Vec::with_capacity(request_map.len());
744 for (slot_name, map_keys) in request_map {
745 let slot_data = if map_keys.is_empty() {
746 Some(SlotData::AllEntries(true))
747 } else {
748 let keys = map_keys.into_iter().map(|key| Word::from(key).into()).collect();
749 Some(SlotData::MapKeys(MapKeys { map_keys: keys }))
750 };
751 requests.push(StorageMapDetailRequest {
752 slot_name: slot_name.to_string(),
753 slot_data,
754 });
755 }
756 requests
757 }
758}
759
760impl Serializable for AccountStorageRequirements {
761 fn write_into<W: miden_tx::utils::serde::ByteWriter>(&self, target: &mut W) {
762 target.write(&self.0);
763 }
764}
765
766impl Deserializable for AccountStorageRequirements {
767 fn read_from<R: miden_tx::utils::serde::ByteReader>(
768 source: &mut R,
769 ) -> Result<Self, miden_tx::utils::serde::DeserializationError> {
770 Ok(AccountStorageRequirements(source.read()?))
771 }
772}
773
774#[derive(Clone, Debug, Default)]
779pub enum VaultFetch {
780 #[default]
782 Skip,
783 Always,
785 IfChangedFrom(Word),
790}
791
792impl From<VaultFetch> for Option<proto::primitives::Word> {
793 fn from(vault: VaultFetch) -> Self {
797 match vault {
798 VaultFetch::Skip => None,
799 VaultFetch::Always => Some(EMPTY_WORD.into()),
800 VaultFetch::IfChangedFrom(commitment) => Some(commitment.into()),
801 }
802 }
803}
804
805#[derive(Clone, Debug, Default)]
811pub enum StorageMapFetch {
812 #[default]
814 Skip,
815 All,
819 Slots(AccountStorageRequirements),
822}
823
824impl From<StorageMapFetch> for Option<StorageRequest> {
825 fn from(storage: StorageMapFetch) -> Self {
826 match storage {
827 StorageMapFetch::Skip => None,
828 StorageMapFetch::All => Some(StorageRequest::AllStorageMaps(true)),
829 StorageMapFetch::Slots(reqs) => {
830 Some(StorageRequest::StorageMaps(StorageMapDetailRequests {
831 storage_maps: reqs.into(),
832 }))
833 },
834 }
835 }
836}
837
838#[derive(Clone, Debug, Default)]
840pub struct GetAccountRequest {
841 pub storage: StorageMapFetch,
843 pub at: AccountStateAt,
845 pub known_code: Option<AccountCode>,
848 pub vault: VaultFetch,
850}
851
852impl GetAccountRequest {
853 #[must_use]
857 pub fn new() -> Self {
858 Self {
859 storage: StorageMapFetch::Skip,
860 at: AccountStateAt::ChainTip,
861 known_code: None,
862 vault: VaultFetch::Skip,
863 }
864 }
865
866 #[must_use]
868 pub fn with_storage(mut self, storage: StorageMapFetch) -> Self {
869 self.storage = storage;
870 self
871 }
872
873 #[must_use]
875 pub fn at(mut self, at: AccountStateAt) -> Self {
876 self.at = at;
877 self
878 }
879
880 #[must_use]
883 pub fn with_known_code(mut self, known_code: Option<AccountCode>) -> Self {
884 self.known_code = known_code;
885 self
886 }
887
888 #[must_use]
890 pub fn with_vault(mut self, vault: VaultFetch) -> Self {
891 self.vault = vault;
892 self
893 }
894}
895
896#[derive(Debug, Error)]
900pub enum AccountProofError {
901 #[error(
902 "the received account commitment doesn't match the received account header's commitment"
903 )]
904 InconsistentAccountCommitment,
905 #[error("the received account id doesn't match the received account header's id")]
906 InconsistentAccountId,
907 #[error(
908 "the received code commitment doesn't match the received account header's code commitment"
909 )]
910 InconsistentCodeCommitment,
911}