1use alloc::boxed::Box;
2use alloc::collections::BTreeMap;
3use alloc::vec::Vec;
4use core::fmt::{self, Debug, Display, Formatter};
5
6use miden_protocol::Word;
7use miden_protocol::account::{
8 Account, AccountCode, AccountHeader, AccountId, AccountStorage, AccountStorageHeader,
9 StorageMap, StorageMapKey, StorageSlot, StorageSlotHeader, StorageSlotName, StorageSlotType,
10};
11use miden_protocol::asset::{Asset, AssetVault};
12use miden_protocol::block::BlockNumber;
13use miden_protocol::block::account_tree::AccountWitness;
14use miden_protocol::crypto::merkle::SparseMerklePath;
15use miden_protocol::crypto::merkle::smt::SmtProof;
16use miden_tx::utils::ToHex;
17use miden_tx::utils::serde::{Deserializable, Serializable};
18use thiserror::Error;
19
20use crate::alloc::string::ToString;
21use crate::rpc::RpcError;
22use crate::rpc::domain::MissingFieldHelper;
23use crate::rpc::errors::RpcConversionError;
24use crate::rpc::generated::rpc::account_request::account_detail_request::storage_map_detail_request::{MapKeys, SlotData};
25use crate::rpc::generated::rpc::account_request::account_detail_request::StorageMapDetailRequest;
26use crate::rpc::generated::{self as proto};
27
28#[derive(Debug)]
33pub enum FetchedAccount {
34 Private(AccountId, AccountUpdateSummary),
37 Public(Box<Account>, AccountUpdateSummary),
40}
41
42impl FetchedAccount {
43 pub fn new_private(account_id: AccountId, summary: AccountUpdateSummary) -> Self {
46 Self::Private(account_id, summary)
47 }
48
49 pub fn new_public(account: Account, summary: AccountUpdateSummary) -> Self {
51 Self::Public(Box::new(account), summary)
52 }
53
54 pub fn account_id(&self) -> AccountId {
56 match self {
57 Self::Private(account_id, _) => *account_id,
58 Self::Public(account, _) => account.id(),
59 }
60 }
61
62 pub fn commitment(&self) -> Word {
64 match self {
65 Self::Private(_, summary) | Self::Public(_, summary) => summary.commitment,
66 }
67 }
68
69 pub fn account(&self) -> Option<&Account> {
71 match self {
72 Self::Private(..) => None,
73 Self::Public(account, _) => Some(account.as_ref()),
74 }
75 }
76}
77
78impl From<FetchedAccount> for Option<Account> {
79 fn from(acc: FetchedAccount) -> Self {
80 match acc {
81 FetchedAccount::Private(..) => None,
82 FetchedAccount::Public(account, _) => Some(*account),
83 }
84 }
85}
86
87#[derive(Debug)]
92pub struct AccountUpdateSummary {
93 pub commitment: Word,
95 pub last_block_num: BlockNumber,
97}
98
99impl AccountUpdateSummary {
100 pub fn new(commitment: Word, last_block_num: BlockNumber) -> Self {
102 Self { commitment, last_block_num }
103 }
104}
105
106impl Display for proto::account::AccountId {
110 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
111 f.write_fmt(format_args!("0x{}", self.id.to_hex()))
112 }
113}
114
115impl Debug for proto::account::AccountId {
116 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
117 Display::fmt(self, f)
118 }
119}
120
121impl From<AccountId> for proto::account::AccountId {
125 fn from(account_id: AccountId) -> Self {
126 Self { id: account_id.to_bytes() }
127 }
128}
129
130impl TryFrom<proto::account::AccountId> for AccountId {
134 type Error = RpcConversionError;
135
136 fn try_from(account_id: proto::account::AccountId) -> Result<Self, Self::Error> {
137 AccountId::read_from_bytes(&account_id.id).map_err(|_| RpcConversionError::NotAValidFelt)
138 }
139}
140
141impl TryInto<AccountHeader> for proto::account::AccountHeader {
145 type Error = crate::rpc::RpcError;
146
147 fn try_into(self) -> Result<AccountHeader, Self::Error> {
148 use miden_protocol::Felt;
149
150 use crate::rpc::domain::MissingFieldHelper;
151
152 let proto::account::AccountHeader {
153 account_id,
154 nonce,
155 vault_root,
156 storage_commitment,
157 code_commitment,
158 } = self;
159
160 let account_id: AccountId = account_id
161 .ok_or(proto::account::AccountHeader::missing_field(stringify!(account_id)))?
162 .try_into()?;
163 let vault_root = vault_root
164 .ok_or(proto::account::AccountHeader::missing_field(stringify!(vault_root)))?
165 .try_into()?;
166 let storage_commitment = storage_commitment
167 .ok_or(proto::account::AccountHeader::missing_field(stringify!(storage_commitment)))?
168 .try_into()?;
169 let code_commitment = code_commitment
170 .ok_or(proto::account::AccountHeader::missing_field(stringify!(code_commitment)))?
171 .try_into()?;
172
173 Ok(AccountHeader::new(
174 account_id,
175 Felt::new(nonce),
176 vault_root,
177 storage_commitment,
178 code_commitment,
179 ))
180 }
181}
182
183impl TryInto<AccountStorageHeader> for proto::account::AccountStorageHeader {
187 type Error = crate::rpc::RpcError;
188
189 fn try_into(self) -> Result<AccountStorageHeader, Self::Error> {
190 use crate::rpc::RpcError;
191 use crate::rpc::domain::MissingFieldHelper;
192
193 let mut header_slots: Vec<StorageSlotHeader> = Vec::with_capacity(self.slots.len());
194
195 for slot in self.slots {
196 let slot_value: Word = slot
197 .commitment
198 .ok_or(proto::account::account_storage_header::StorageSlot::missing_field(
199 stringify!(commitment),
200 ))?
201 .try_into()?;
202
203 let slot_type = u8::try_from(slot.slot_type)
204 .map_err(|e| RpcError::InvalidResponse(e.to_string()))
205 .and_then(|v| {
206 StorageSlotType::try_from(v)
207 .map_err(|e| RpcError::InvalidResponse(e.to_string()))
208 })?;
209 let slot_name = StorageSlotName::new(slot.slot_name)
210 .map_err(|err| RpcError::InvalidResponse(err.to_string()))?;
211
212 header_slots.push(StorageSlotHeader::new(slot_name, slot_type, slot_value));
213 }
214
215 header_slots.sort_by_key(StorageSlotHeader::id);
216 AccountStorageHeader::new(header_slots)
217 .map_err(|err| RpcError::InvalidResponse(err.to_string()))
218 }
219}
220
221#[cfg(feature = "tonic")]
225impl proto::rpc::account_response::AccountDetails {
226 pub fn into_domain(
236 self,
237 known_account_codes: &BTreeMap<Word, AccountCode>,
238 storage_requirements: &AccountStorageRequirements,
239 ) -> Result<AccountDetails, crate::rpc::RpcError> {
240 use crate::rpc::RpcError;
241 use crate::rpc::domain::MissingFieldHelper;
242
243 let proto::rpc::account_response::AccountDetails {
244 header,
245 storage_details,
246 code,
247 vault_details,
248 } = self;
249 let header: AccountHeader = header
250 .ok_or(proto::rpc::account_response::AccountDetails::missing_field(stringify!(header)))?
251 .try_into()?;
252
253 let storage_details: AccountStorageDetails = storage_details
254 .ok_or(proto::rpc::account_response::AccountDetails::missing_field(stringify!(
255 storage_details
256 )))?
257 .try_into()?;
258
259 for map_detail in &storage_details.map_details {
263 let requested_keys = storage_requirements
264 .inner()
265 .get(&map_detail.slot_name)
266 .map(Vec::as_slice)
267 .unwrap_or_default();
268
269 if let StorageMapEntries::EntriesWithProofs(proofs) = &map_detail.entries {
270 if proofs.len() != requested_keys.len() {
271 return Err(RpcError::InvalidResponse(format!(
272 "expected {} proofs for storage map slot '{}', got {}",
273 requested_keys.len(),
274 map_detail.slot_name,
275 proofs.len(),
276 )));
277 }
278 for (proof, raw_key) in proofs.iter().zip(requested_keys.iter()) {
279 let hashed_key = raw_key.hash().as_word();
280 if proof.get(&hashed_key).is_none() {
281 return Err(RpcError::InvalidResponse(format!(
282 "proof for storage map key {} does not match the requested key",
283 raw_key.to_hex(),
284 )));
285 }
286 }
287 }
288 }
289
290 let code = {
294 let received_code = code.map(|c| AccountCode::read_from_bytes(&c)).transpose()?;
295 match received_code {
296 Some(code) => code,
297 None => known_account_codes
298 .get(&header.code_commitment())
299 .ok_or(RpcError::InvalidResponse(
300 "Account code was not provided, but the response did not contain it either"
301 .into(),
302 ))?
303 .clone(),
304 }
305 };
306
307 let vault_details = vault_details
308 .ok_or(proto::rpc::AccountVaultDetails::missing_field(stringify!(vault_details)))?
309 .try_into()?;
310
311 Ok(AccountDetails {
312 header,
313 storage_details,
314 code,
315 vault_details,
316 })
317 }
318}
319
320pub type AccountProofs = (BlockNumber, Vec<AccountProof>);
325
326#[derive(Clone, Debug)]
331pub struct AccountDetails {
332 pub header: AccountHeader,
333 pub storage_details: AccountStorageDetails,
334 pub code: AccountCode,
335 pub vault_details: AccountVaultDetails,
336}
337
338impl TryFrom<&AccountDetails> for Account {
339 type Error = RpcError;
340
341 fn try_from(details: &AccountDetails) -> Result<Self, Self::Error> {
347 if details.vault_details.too_many_assets {
348 return Err(RpcError::ExpectedDataMissing(
349 "cannot build account: vault has too many assets".into(),
350 ));
351 }
352
353 if let Some(slot_name) = details
354 .storage_details
355 .map_details
356 .iter()
357 .find(|m| m.too_many_entries)
358 .map(|m| &m.slot_name)
359 {
360 return Err(RpcError::ExpectedDataMissing(format!(
361 "cannot build account: storage map slot '{slot_name}' has too many entries",
362 )));
363 }
364
365 let mut slots: Vec<StorageSlot> = Vec::new();
366
367 for slot_header in details.storage_details.header.slots() {
368 match slot_header.slot_type() {
369 StorageSlotType::Value => {
370 slots.push(StorageSlot::with_value(
371 slot_header.name().clone(),
372 slot_header.value(),
373 ));
374 },
375 StorageSlotType::Map => {
376 let map_details = details
377 .storage_details
378 .find_map_details(slot_header.name())
379 .ok_or_else(|| {
380 RpcError::ExpectedDataMissing(format!(
381 "slot '{}' is a map but has no map_details in response",
382 slot_header.name()
383 ))
384 })?;
385
386 let storage_map = map_details
387 .entries
388 .clone()
389 .into_storage_map()
390 .ok_or_else(|| {
391 RpcError::ExpectedDataMissing(
392 "expected AllEntries for full account fetch, got EntriesWithProofs"
393 .into(),
394 )
395 })?
396 .map_err(|err| {
397 RpcError::InvalidResponse(format!(
398 "the rpc api returned a non-valid map entry: {err}"
399 ))
400 })?;
401
402 slots.push(StorageSlot::with_map(slot_header.name().clone(), storage_map));
403 },
404 }
405 }
406
407 let asset_vault = AssetVault::new(&details.vault_details.assets).map_err(|err| {
408 RpcError::InvalidResponse(format!("rpc api returned non-valid assets: {err}"))
409 })?;
410
411 let account_storage = AccountStorage::new(slots).map_err(|err| {
412 RpcError::InvalidResponse(format!("rpc api returned non-valid storage slots: {err}"))
413 })?;
414
415 Account::new(
416 details.header.id(),
417 asset_vault,
418 account_storage,
419 details.code.clone(),
420 details.header.nonce(),
421 None,
422 )
423 .map_err(|err| {
424 RpcError::InvalidResponse(format!(
425 "failed to construct account from rpc api response: {err}"
426 ))
427 })
428 }
429}
430
431#[derive(Clone, Debug)]
436pub struct AccountStorageDetails {
437 pub header: AccountStorageHeader,
439 pub map_details: Vec<AccountStorageMapDetails>,
441}
442
443impl AccountStorageDetails {
444 pub fn find_map_details(&self, target: &StorageSlotName) -> Option<&AccountStorageMapDetails> {
448 self.map_details.iter().find(|map_detail| map_detail.slot_name == *target)
449 }
450}
451
452impl TryFrom<proto::rpc::AccountStorageDetails> for AccountStorageDetails {
453 type Error = RpcError;
454
455 fn try_from(value: proto::rpc::AccountStorageDetails) -> Result<Self, Self::Error> {
456 let header = value
457 .header
458 .ok_or(proto::account::AccountStorageHeader::missing_field(stringify!(header)))?
459 .try_into()?;
460 let map_details = value
461 .map_details
462 .into_iter()
463 .map(core::convert::TryInto::try_into)
464 .collect::<Result<Vec<AccountStorageMapDetails>, RpcError>>()?;
465
466 Ok(Self { header, map_details })
467 }
468}
469
470#[derive(Clone, Debug)]
474pub struct AccountStorageMapDetails {
475 pub slot_name: StorageSlotName,
477 pub too_many_entries: bool,
481 pub entries: StorageMapEntries,
484}
485
486impl TryFrom<proto::rpc::account_storage_details::AccountStorageMapDetails>
487 for AccountStorageMapDetails
488{
489 type Error = RpcError;
490
491 fn try_from(
492 value: proto::rpc::account_storage_details::AccountStorageMapDetails,
493 ) -> Result<Self, Self::Error> {
494 use proto::rpc::account_storage_details::account_storage_map_details::Entries;
495
496 let slot_name = StorageSlotName::new(value.slot_name)
497 .map_err(|err| RpcError::ExpectedDataMissing(err.to_string()))?;
498 let too_many_entries = value.too_many_entries;
499
500 let entries = match value.entries {
501 Some(Entries::AllEntries(all_entries)) => {
502 let entries = all_entries
503 .entries
504 .into_iter()
505 .map(core::convert::TryInto::try_into)
506 .collect::<Result<Vec<StorageMapEntry>, RpcError>>()?;
507 StorageMapEntries::AllEntries(entries)
508 },
509 Some(Entries::EntriesWithProofs(entries_with_proofs)) => {
510 let proofs = entries_with_proofs
511 .entries
512 .into_iter()
513 .map(|entry| {
514 let proof: SmtProof = entry
515 .proof
516 .ok_or(RpcError::ExpectedDataMissing("proof".into()))?
517 .try_into()?;
518 Ok(proof)
519 })
520 .collect::<Result<Vec<SmtProof>, RpcError>>()?;
521 StorageMapEntries::EntriesWithProofs(proofs)
522 },
523 None => StorageMapEntries::AllEntries(Vec::new()),
524 };
525
526 Ok(Self { slot_name, too_many_entries, entries })
527 }
528}
529
530#[derive(Clone, Debug)]
535pub struct StorageMapEntry {
536 pub key: StorageMapKey,
537 pub value: Word,
538}
539
540impl TryFrom<proto::rpc::account_storage_details::account_storage_map_details::all_map_entries::StorageMapEntry>
541 for StorageMapEntry
542{
543 type Error = RpcError;
544
545 fn try_from(value: proto::rpc::account_storage_details::account_storage_map_details::all_map_entries::StorageMapEntry) -> Result<Self, Self::Error> {
546 let key: StorageMapKey =
547 value.key.ok_or(RpcError::ExpectedDataMissing("key".into()))?.try_into()?;
548 let value = value.value.ok_or(RpcError::ExpectedDataMissing("value".into()))?.try_into()?;
549 Ok(Self { key, value })
550 }
551}
552
553#[derive(Clone, Debug)]
559pub enum StorageMapEntries {
560 AllEntries(Vec<StorageMapEntry>),
562 EntriesWithProofs(Vec<SmtProof>),
564}
565
566impl StorageMapEntries {
567 pub fn into_storage_map(
572 self,
573 ) -> Option<Result<StorageMap, miden_protocol::errors::StorageMapError>> {
574 match self {
575 StorageMapEntries::AllEntries(entries) => {
576 Some(StorageMap::with_entries(entries.into_iter().map(|e| (e.key, e.value))))
577 },
578 StorageMapEntries::EntriesWithProofs(_) => None,
579 }
580 }
581}
582
583#[derive(Clone, Debug)]
587pub struct AccountVaultDetails {
588 pub too_many_assets: bool,
592 pub assets: Vec<Asset>,
595}
596
597impl TryFrom<proto::rpc::AccountVaultDetails> for AccountVaultDetails {
598 type Error = RpcError;
599
600 fn try_from(value: proto::rpc::AccountVaultDetails) -> Result<Self, Self::Error> {
601 let too_many_assets = value.too_many_assets;
602 let assets = value
603 .assets
604 .into_iter()
605 .map(Asset::try_from)
606 .collect::<Result<Vec<Asset>, _>>()?;
607
608 Ok(Self { too_many_assets, assets })
609 }
610}
611
612#[derive(Clone, Debug)]
617pub struct AccountProof {
618 account_witness: AccountWitness,
620 state_headers: Option<AccountDetails>,
622}
623
624impl AccountProof {
625 pub fn new(
627 account_witness: AccountWitness,
628 account_details: Option<AccountDetails>,
629 ) -> Result<Self, AccountProofError> {
630 if let Some(AccountDetails {
631 header: account_header,
632 storage_details: _,
633 code,
634 ..
635 }) = &account_details
636 {
637 if account_header.to_commitment() != account_witness.state_commitment() {
638 return Err(AccountProofError::InconsistentAccountCommitment);
639 }
640 if account_header.id() != account_witness.id() {
641 return Err(AccountProofError::InconsistentAccountId);
642 }
643 if code.commitment() != account_header.code_commitment() {
644 return Err(AccountProofError::InconsistentCodeCommitment);
645 }
646 }
647
648 Ok(Self {
649 account_witness,
650 state_headers: account_details,
651 })
652 }
653
654 pub fn account_id(&self) -> AccountId {
656 self.account_witness.id()
657 }
658
659 pub fn account_header(&self) -> Option<&AccountHeader> {
661 self.state_headers.as_ref().map(|account_details| &account_details.header)
662 }
663
664 pub fn storage_header(&self) -> Option<&AccountStorageHeader> {
666 self.state_headers
667 .as_ref()
668 .map(|account_details| &account_details.storage_details.header)
669 }
670
671 pub fn storage_details(&self) -> Option<&AccountStorageDetails> {
673 self.state_headers.as_ref().map(|d| &d.storage_details)
674 }
675
676 pub fn find_map_details(
678 &self,
679 slot_name: &StorageSlotName,
680 ) -> Option<&AccountStorageMapDetails> {
681 self.state_headers
682 .as_ref()
683 .and_then(|details| details.storage_details.find_map_details(slot_name))
684 }
685
686 pub fn account_code(&self) -> Option<&AccountCode> {
688 self.state_headers.as_ref().map(|headers| &headers.code)
689 }
690
691 pub fn code_commitment(&self) -> Option<Word> {
693 self.account_code().map(AccountCode::commitment)
694 }
695
696 pub fn account_commitment(&self) -> Word {
698 self.account_witness.state_commitment()
699 }
700
701 pub fn account_witness(&self) -> &AccountWitness {
702 &self.account_witness
703 }
704
705 pub fn merkle_proof(&self) -> &SparseMerklePath {
707 self.account_witness.path()
708 }
709
710 pub fn into_parts(self) -> (AccountWitness, Option<AccountDetails>) {
712 (self.account_witness, self.state_headers)
713 }
714}
715
716#[cfg(feature = "tonic")]
717impl TryFrom<proto::rpc::AccountResponse> for AccountProof {
718 type Error = RpcError;
719 fn try_from(account_proof: proto::rpc::AccountResponse) -> Result<Self, Self::Error> {
720 let Some(witness) = account_proof.witness else {
721 return Err(RpcError::ExpectedDataMissing(
722 "GetAccountProof returned an account without witness".to_string(),
723 ));
724 };
725
726 let details: Option<AccountDetails> = {
727 match account_proof.details {
728 None => None,
729 Some(details) => Some(
730 details
731 .into_domain(&BTreeMap::new(), &AccountStorageRequirements::default())?,
732 ),
733 }
734 };
735 AccountProof::new(witness.try_into()?, details)
736 .map_err(|err| RpcError::InvalidResponse(format!("{err}")))
737 }
738}
739
740impl TryFrom<proto::account::AccountWitness> for AccountWitness {
744 type Error = RpcError;
745
746 fn try_from(account_witness: proto::account::AccountWitness) -> Result<Self, Self::Error> {
747 let state_commitment = account_witness
748 .commitment
749 .ok_or(proto::account::AccountWitness::missing_field(stringify!(state_commitment)))?
750 .try_into()?;
751 let merkle_path = account_witness
752 .path
753 .ok_or(proto::account::AccountWitness::missing_field(stringify!(merkle_path)))?
754 .try_into()?;
755 let account_id = account_witness
756 .witness_id
757 .ok_or(proto::account::AccountWitness::missing_field(stringify!(witness_id)))?
758 .try_into()?;
759
760 let witness = AccountWitness::new(account_id, state_commitment, merkle_path).unwrap();
761 Ok(witness)
762 }
763}
764
765#[derive(Clone, Debug, Default, Eq, PartialEq)]
774pub struct AccountStorageRequirements(BTreeMap<StorageSlotName, Vec<StorageMapKey>>);
775
776impl AccountStorageRequirements {
777 pub fn new<'a>(
778 slots_and_keys: impl IntoIterator<
779 Item = (StorageSlotName, impl IntoIterator<Item = &'a StorageMapKey>),
780 >,
781 ) -> Self {
782 let map = slots_and_keys
783 .into_iter()
784 .map(|(slot_name, keys_iter)| {
785 let keys_vec: Vec<StorageMapKey> = keys_iter.into_iter().copied().collect();
786 (slot_name, keys_vec)
787 })
788 .collect();
789
790 AccountStorageRequirements(map)
791 }
792
793 pub fn inner(&self) -> &BTreeMap<StorageSlotName, Vec<StorageMapKey>> {
794 &self.0
795 }
796
797 pub fn keys_for_slot(&self, slot_name: &StorageSlotName) -> &[StorageMapKey] {
799 self.0.get(slot_name).map_or(&[], Vec::as_slice)
800 }
801}
802
803impl From<AccountStorageRequirements> for Vec<StorageMapDetailRequest> {
804 fn from(value: AccountStorageRequirements) -> Vec<StorageMapDetailRequest> {
805 let request_map = value.0;
806 let mut requests = Vec::with_capacity(request_map.len());
807 for (slot_name, map_keys) in request_map {
808 let slot_data = if map_keys.is_empty() {
809 Some(SlotData::AllEntries(true))
810 } else {
811 let keys = map_keys.into_iter().map(|key| Word::from(key).into()).collect();
812 Some(SlotData::MapKeys(MapKeys { map_keys: keys }))
813 };
814 requests.push(StorageMapDetailRequest {
815 slot_name: slot_name.to_string(),
816 slot_data,
817 });
818 }
819 requests
820 }
821}
822
823impl Serializable for AccountStorageRequirements {
824 fn write_into<W: miden_tx::utils::serde::ByteWriter>(&self, target: &mut W) {
825 target.write(&self.0);
826 }
827}
828
829impl Deserializable for AccountStorageRequirements {
830 fn read_from<R: miden_tx::utils::serde::ByteReader>(
831 source: &mut R,
832 ) -> Result<Self, miden_tx::utils::serde::DeserializationError> {
833 Ok(AccountStorageRequirements(source.read()?))
834 }
835}
836
837#[derive(Debug, Error)]
841pub enum AccountProofError {
842 #[error(
843 "the received account commitment doesn't match the received account header's commitment"
844 )]
845 InconsistentAccountCommitment,
846 #[error("the received account id doesn't match the received account header's id")]
847 InconsistentAccountId,
848 #[error(
849 "the received code commitment doesn't match the received account header's code commitment"
850 )]
851 InconsistentCodeCommitment,
852}