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