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,
9 AccountCode,
10 AccountHeader,
11 AccountId,
12 AccountStorageHeader,
13 StorageMap,
14 StorageMapWitness,
15 StorageSlotHeader,
16 StorageSlotName,
17 StorageSlotType,
18};
19use miden_protocol::asset::Asset;
20use miden_protocol::block::BlockNumber;
21use miden_protocol::block::account_tree::AccountWitness;
22use miden_protocol::crypto::merkle::SparseMerklePath;
23use miden_protocol::crypto::merkle::smt::SmtProof;
24use miden_tx::utils::{Deserializable, Serializable, ToHex};
25use thiserror::Error;
26
27use crate::alloc::string::ToString;
28use crate::rpc::RpcError;
29use crate::rpc::domain::MissingFieldHelper;
30use crate::rpc::errors::RpcConversionError;
31use crate::rpc::generated::rpc::account_request::account_detail_request;
32use crate::rpc::generated::{self as proto};
33
34pub enum FetchedAccount {
39 Private(AccountId, AccountUpdateSummary),
42 Public(Box<Account>, AccountUpdateSummary),
45}
46
47impl FetchedAccount {
48 pub fn new_private(account_id: AccountId, summary: AccountUpdateSummary) -> Self {
51 Self::Private(account_id, summary)
52 }
53
54 pub fn new_public(account: Account, summary: AccountUpdateSummary) -> Self {
56 Self::Public(Box::new(account), summary)
57 }
58
59 pub fn account_id(&self) -> AccountId {
61 match self {
62 Self::Private(account_id, _) => *account_id,
63 Self::Public(account, _) => account.id(),
64 }
65 }
66
67 pub fn commitment(&self) -> Word {
69 match self {
70 Self::Private(_, summary) | Self::Public(_, summary) => summary.commitment,
71 }
72 }
73
74 pub fn account(&self) -> Option<&Account> {
76 match self {
77 Self::Private(..) => None,
78 Self::Public(account, _) => Some(account.as_ref()),
79 }
80 }
81}
82
83impl From<FetchedAccount> for Option<Account> {
84 fn from(acc: FetchedAccount) -> Self {
85 match acc {
86 FetchedAccount::Private(..) => None,
87 FetchedAccount::Public(account, _) => Some(*account),
88 }
89 }
90}
91
92pub struct AccountUpdateSummary {
97 pub commitment: Word,
99 pub last_block_num: BlockNumber,
101}
102
103impl AccountUpdateSummary {
104 pub fn new(commitment: Word, last_block_num: BlockNumber) -> Self {
106 Self { commitment, last_block_num }
107 }
108}
109
110impl Display for proto::account::AccountId {
114 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
115 f.write_fmt(format_args!("0x{}", self.id.to_hex()))
116 }
117}
118
119impl Debug for proto::account::AccountId {
120 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
121 Display::fmt(self, f)
122 }
123}
124
125impl From<AccountId> for proto::account::AccountId {
129 fn from(account_id: AccountId) -> Self {
130 Self { id: account_id.to_bytes() }
131 }
132}
133
134impl TryFrom<proto::account::AccountId> for AccountId {
138 type Error = RpcConversionError;
139
140 fn try_from(account_id: proto::account::AccountId) -> Result<Self, Self::Error> {
141 AccountId::read_from_bytes(&account_id.id).map_err(|_| RpcConversionError::NotAValidFelt)
142 }
143}
144
145impl TryInto<AccountHeader> for proto::account::AccountHeader {
149 type Error = crate::rpc::RpcError;
150
151 fn try_into(self) -> Result<AccountHeader, Self::Error> {
152 use miden_protocol::Felt;
153
154 use crate::rpc::domain::MissingFieldHelper;
155
156 let proto::account::AccountHeader {
157 account_id,
158 nonce,
159 vault_root,
160 storage_commitment,
161 code_commitment,
162 } = self;
163
164 let account_id: AccountId = account_id
165 .ok_or(proto::account::AccountHeader::missing_field(stringify!(account_id)))?
166 .try_into()?;
167 let vault_root = vault_root
168 .ok_or(proto::account::AccountHeader::missing_field(stringify!(vault_root)))?
169 .try_into()?;
170 let storage_commitment = storage_commitment
171 .ok_or(proto::account::AccountHeader::missing_field(stringify!(storage_commitment)))?
172 .try_into()?;
173 let code_commitment = code_commitment
174 .ok_or(proto::account::AccountHeader::missing_field(stringify!(code_commitment)))?
175 .try_into()?;
176
177 Ok(AccountHeader::new(
178 account_id,
179 Felt::new(nonce),
180 vault_root,
181 storage_commitment,
182 code_commitment,
183 ))
184 }
185}
186
187impl TryInto<AccountStorageHeader> for proto::account::AccountStorageHeader {
191 type Error = crate::rpc::RpcError;
192
193 fn try_into(self) -> Result<AccountStorageHeader, Self::Error> {
194 use crate::rpc::RpcError;
195 use crate::rpc::domain::MissingFieldHelper;
196
197 let mut header_slots: Vec<StorageSlotHeader> = Vec::with_capacity(self.slots.len());
198
199 for slot in self.slots {
200 let slot_value: Word = slot
201 .commitment
202 .ok_or(proto::account::account_storage_header::StorageSlot::missing_field(
203 stringify!(commitment),
204 ))?
205 .try_into()?;
206
207 let slot_type = u8::try_from(slot.slot_type)
208 .map_err(|e| RpcError::InvalidResponse(e.to_string()))
209 .and_then(|v| {
210 StorageSlotType::try_from(v)
211 .map_err(|e| RpcError::InvalidResponse(e.to_string()))
212 })?;
213 let slot_name = StorageSlotName::new(slot.slot_name)
214 .map_err(|err| RpcError::InvalidResponse(err.to_string()))?;
215
216 header_slots.push(StorageSlotHeader::new(slot_name, slot_type, slot_value));
217 }
218
219 header_slots.sort_by_key(StorageSlotHeader::id);
220 AccountStorageHeader::new(header_slots)
221 .map_err(|err| RpcError::InvalidResponse(err.to_string()))
222 }
223}
224
225#[cfg(feature = "tonic")]
229impl proto::rpc::account_response::AccountDetails {
230 pub fn into_domain(
240 self,
241 known_account_codes: &BTreeMap<Word, AccountCode>,
242 storage_requirements: &AccountStorageRequirements,
243 ) -> Result<AccountDetails, crate::rpc::RpcError> {
244 use crate::rpc::RpcError;
245 use crate::rpc::domain::MissingFieldHelper;
246
247 let proto::rpc::account_response::AccountDetails {
248 header,
249 storage_details,
250 code,
251 vault_details,
252 } = self;
253 let header: AccountHeader = header
254 .ok_or(proto::rpc::account_response::AccountDetails::missing_field(stringify!(header)))?
255 .try_into()?;
256
257 let storage_details: AccountStorageDetails = storage_details
258 .ok_or(proto::rpc::account_response::AccountDetails::missing_field(stringify!(
259 storage_details
260 )))?
261 .try_into()?;
262
263 for map_detail in &storage_details.map_details {
267 let requested_keys = storage_requirements
268 .inner()
269 .get(&map_detail.slot_name)
270 .map(Vec::as_slice)
271 .unwrap_or_default();
272
273 if let StorageMapEntries::EntriesWithProofs(witnesses) = &map_detail.entries {
274 for (witness, raw_key) in witnesses.iter().zip(requested_keys.iter()) {
275 let hashed_key = StorageMap::hash_key(*raw_key);
276 if witness.proof().get(&hashed_key).is_none() {
277 return Err(RpcError::InvalidResponse(format!(
278 "proof for storage map key {} does not match the requested key",
279 raw_key.to_hex(),
280 )));
281 }
282 }
283 }
284 }
285
286 let code = {
290 let received_code = code.map(|c| AccountCode::read_from_bytes(&c)).transpose()?;
291 match received_code {
292 Some(code) => code,
293 None => known_account_codes
294 .get(&header.code_commitment())
295 .ok_or(RpcError::InvalidResponse(
296 "Account code was not provided, but the response did not contain it either"
297 .into(),
298 ))?
299 .clone(),
300 }
301 };
302
303 let vault_details = vault_details
304 .ok_or(proto::rpc::AccountVaultDetails::missing_field(stringify!(vault_details)))?
305 .try_into()?;
306
307 Ok(AccountDetails {
308 header,
309 storage_details,
310 code,
311 vault_details,
312 })
313 }
314}
315
316pub type AccountProofs = (BlockNumber, Vec<AccountProof>);
321
322#[derive(Clone, Debug)]
327pub struct AccountDetails {
328 pub header: AccountHeader,
329 pub storage_details: AccountStorageDetails,
330 pub code: AccountCode,
331 pub vault_details: AccountVaultDetails,
332}
333
334#[derive(Clone, Debug)]
339pub struct AccountStorageDetails {
340 pub header: AccountStorageHeader,
342 pub map_details: Vec<AccountStorageMapDetails>,
344}
345
346impl AccountStorageDetails {
347 pub fn find_map_details(&self, target: &StorageSlotName) -> Option<&AccountStorageMapDetails> {
351 self.map_details.iter().find(|map_detail| map_detail.slot_name == *target)
352 }
353}
354
355impl TryFrom<proto::rpc::AccountStorageDetails> for AccountStorageDetails {
356 type Error = RpcError;
357
358 fn try_from(value: proto::rpc::AccountStorageDetails) -> Result<Self, Self::Error> {
359 let header = value
360 .header
361 .ok_or(proto::account::AccountStorageHeader::missing_field(stringify!(header)))?
362 .try_into()?;
363 let map_details = value
364 .map_details
365 .into_iter()
366 .map(core::convert::TryInto::try_into)
367 .collect::<Result<Vec<AccountStorageMapDetails>, RpcError>>()?;
368
369 Ok(Self { header, map_details })
370 }
371}
372
373#[derive(Clone, Debug)]
377pub struct AccountStorageMapDetails {
378 pub slot_name: StorageSlotName,
380 pub too_many_entries: bool,
384 pub entries: StorageMapEntries,
387}
388
389impl TryFrom<proto::rpc::account_storage_details::AccountStorageMapDetails>
390 for AccountStorageMapDetails
391{
392 type Error = RpcError;
393
394 fn try_from(
395 value: proto::rpc::account_storage_details::AccountStorageMapDetails,
396 ) -> Result<Self, Self::Error> {
397 use proto::rpc::account_storage_details::account_storage_map_details::Entries;
398
399 let slot_name = StorageSlotName::new(value.slot_name)
400 .map_err(|err| RpcError::ExpectedDataMissing(err.to_string()))?;
401 let too_many_entries = value.too_many_entries;
402
403 let entries = match value.entries {
404 Some(Entries::AllEntries(all_entries)) => {
405 let entries = all_entries
406 .entries
407 .into_iter()
408 .map(core::convert::TryInto::try_into)
409 .collect::<Result<Vec<StorageMapEntry>, RpcError>>()?;
410 StorageMapEntries::AllEntries(entries)
411 },
412 Some(Entries::EntriesWithProofs(entries_with_proofs)) => {
413 let witnesses = entries_with_proofs
414 .entries
415 .into_iter()
416 .map(|entry| {
417 let hashed_key: Word = entry
419 .key
420 .ok_or(RpcError::ExpectedDataMissing("key".into()))?
421 .try_into()?;
422 let proof: SmtProof = entry
423 .proof
424 .ok_or(RpcError::ExpectedDataMissing("proof".into()))?
425 .try_into()?;
426
427 let value = proof.get(&hashed_key).unwrap_or_default();
428 Ok(StorageMapWitness::new_unchecked(proof, [(hashed_key, value)]))
429 })
430 .collect::<Result<Vec<StorageMapWitness>, RpcError>>()?;
431 StorageMapEntries::EntriesWithProofs(witnesses)
432 },
433 None => StorageMapEntries::AllEntries(Vec::new()),
434 };
435
436 Ok(Self { slot_name, too_many_entries, entries })
437 }
438}
439
440#[derive(Clone, Debug)]
445pub struct StorageMapEntry {
446 pub key: Word,
447 pub value: Word,
448}
449
450impl TryFrom<proto::rpc::account_storage_details::account_storage_map_details::all_map_entries::StorageMapEntry>
451 for StorageMapEntry
452{
453 type Error = RpcError;
454
455 fn try_from(value: proto::rpc::account_storage_details::account_storage_map_details::all_map_entries::StorageMapEntry) -> Result<Self, Self::Error> {
456 let key = value.key.ok_or(RpcError::ExpectedDataMissing("key".into()))?.try_into()?;
457 let value = value.value.ok_or(RpcError::ExpectedDataMissing("value".into()))?.try_into()?;
458 Ok(Self { key, value })
459 }
460}
461
462#[derive(Clone, Debug)]
468pub enum StorageMapEntries {
469 AllEntries(Vec<StorageMapEntry>),
471 EntriesWithProofs(Vec<StorageMapWitness>),
473}
474
475impl StorageMapEntries {
476 pub fn into_storage_map(self) -> Result<StorageMap, miden_protocol::errors::StorageMapError> {
478 match self {
479 StorageMapEntries::AllEntries(entries) => {
480 StorageMap::with_entries(entries.into_iter().map(|e| (e.key, e.value)))
481 },
482 StorageMapEntries::EntriesWithProofs(witnesses) => {
483 let entries: Vec<_> =
484 witnesses.iter().flat_map(|w| w.entries().map(|(k, v)| (*k, *v))).collect();
485 StorageMap::with_entries(entries)
486 },
487 }
488 }
489}
490
491#[derive(Clone, Debug)]
495pub struct AccountVaultDetails {
496 pub too_many_assets: bool,
500 pub assets: Vec<Asset>,
503}
504
505impl TryFrom<proto::rpc::AccountVaultDetails> for AccountVaultDetails {
506 type Error = RpcError;
507
508 fn try_from(value: proto::rpc::AccountVaultDetails) -> Result<Self, Self::Error> {
509 let too_many_assets = value.too_many_assets;
510 let assets = value
511 .assets
512 .into_iter()
513 .map(|asset| {
514 let native_digest: Word = asset
515 .asset
516 .ok_or(proto::rpc::AccountVaultDetails::missing_field(stringify!(assets)))?
517 .try_into()?;
518 native_digest
519 .try_into()
520 .map_err(|_| RpcError::DeserializationError("asset".to_string()))
521 })
522 .collect::<Result<Vec<Asset>, RpcError>>()?;
523
524 Ok(Self { too_many_assets, assets })
525 }
526}
527
528#[derive(Clone, Debug)]
533pub struct AccountProof {
534 account_witness: AccountWitness,
536 state_headers: Option<AccountDetails>,
538}
539
540impl AccountProof {
541 pub fn new(
543 account_witness: AccountWitness,
544 account_details: Option<AccountDetails>,
545 ) -> Result<Self, AccountProofError> {
546 if let Some(AccountDetails {
547 header: account_header,
548 storage_details: _,
549 code,
550 ..
551 }) = &account_details
552 {
553 if account_header.commitment() != account_witness.state_commitment() {
554 return Err(AccountProofError::InconsistentAccountCommitment);
555 }
556 if account_header.id() != account_witness.id() {
557 return Err(AccountProofError::InconsistentAccountId);
558 }
559 if code.commitment() != account_header.code_commitment() {
560 return Err(AccountProofError::InconsistentCodeCommitment);
561 }
562 }
563
564 Ok(Self {
565 account_witness,
566 state_headers: account_details,
567 })
568 }
569
570 pub fn account_id(&self) -> AccountId {
572 self.account_witness.id()
573 }
574
575 pub fn account_header(&self) -> Option<&AccountHeader> {
577 self.state_headers.as_ref().map(|account_details| &account_details.header)
578 }
579
580 pub fn storage_header(&self) -> Option<&AccountStorageHeader> {
582 self.state_headers
583 .as_ref()
584 .map(|account_details| &account_details.storage_details.header)
585 }
586
587 pub fn storage_details(&self) -> Option<&AccountStorageDetails> {
589 self.state_headers.as_ref().map(|d| &d.storage_details)
590 }
591
592 pub fn find_map_details(
594 &self,
595 slot_name: &StorageSlotName,
596 ) -> Option<&AccountStorageMapDetails> {
597 self.state_headers
598 .as_ref()
599 .and_then(|details| details.storage_details.find_map_details(slot_name))
600 }
601
602 pub fn account_code(&self) -> Option<&AccountCode> {
604 self.state_headers.as_ref().map(|headers| &headers.code)
605 }
606
607 pub fn code_commitment(&self) -> Option<Word> {
609 self.account_code().map(AccountCode::commitment)
610 }
611
612 pub fn account_commitment(&self) -> Word {
614 self.account_witness.state_commitment()
615 }
616
617 pub fn account_witness(&self) -> &AccountWitness {
618 &self.account_witness
619 }
620
621 pub fn merkle_proof(&self) -> &SparseMerklePath {
623 self.account_witness.path()
624 }
625
626 pub fn into_parts(self) -> (AccountWitness, Option<AccountDetails>) {
628 (self.account_witness, self.state_headers)
629 }
630}
631
632#[cfg(feature = "tonic")]
633impl TryFrom<proto::rpc::AccountResponse> for AccountProof {
634 type Error = RpcError;
635 fn try_from(account_proof: proto::rpc::AccountResponse) -> Result<Self, Self::Error> {
636 let Some(witness) = account_proof.witness else {
637 return Err(RpcError::ExpectedDataMissing(
638 "GetAccountProof returned an account without witness".to_string(),
639 ));
640 };
641
642 let details: Option<AccountDetails> = {
643 match account_proof.details {
644 None => None,
645 Some(details) => Some(
646 details
647 .into_domain(&BTreeMap::new(), &AccountStorageRequirements::default())?,
648 ),
649 }
650 };
651 AccountProof::new(witness.try_into()?, details)
652 .map_err(|err| RpcError::InvalidResponse(format!("{err}")))
653 }
654}
655
656impl TryFrom<proto::account::AccountWitness> for AccountWitness {
660 type Error = RpcError;
661
662 fn try_from(account_witness: proto::account::AccountWitness) -> Result<Self, Self::Error> {
663 let state_commitment = account_witness
664 .commitment
665 .ok_or(proto::account::AccountWitness::missing_field(stringify!(state_commitment)))?
666 .try_into()?;
667 let merkle_path = account_witness
668 .path
669 .ok_or(proto::account::AccountWitness::missing_field(stringify!(merkle_path)))?
670 .try_into()?;
671 let account_id = account_witness
672 .witness_id
673 .ok_or(proto::account::AccountWitness::missing_field(stringify!(witness_id)))?
674 .try_into()?;
675
676 let witness = AccountWitness::new(account_id, state_commitment, merkle_path).unwrap();
677 Ok(witness)
678 }
679}
680
681pub type StorageMapKey = Word;
685
686#[derive(Clone, Debug, Default, Eq, PartialEq)]
692pub struct AccountStorageRequirements(BTreeMap<StorageSlotName, Vec<StorageMapKey>>);
693
694impl AccountStorageRequirements {
695 pub fn new<'a>(
696 slots_and_keys: impl IntoIterator<
697 Item = (StorageSlotName, impl IntoIterator<Item = &'a StorageMapKey>),
698 >,
699 ) -> Self {
700 let map = slots_and_keys
701 .into_iter()
702 .map(|(slot_name, keys_iter)| {
703 let keys_vec: Vec<StorageMapKey> = keys_iter.into_iter().copied().collect();
704 (slot_name, keys_vec)
705 })
706 .collect();
707
708 AccountStorageRequirements(map)
709 }
710
711 pub fn inner(&self) -> &BTreeMap<StorageSlotName, Vec<StorageMapKey>> {
712 &self.0
713 }
714}
715
716impl From<AccountStorageRequirements> for Vec<account_detail_request::StorageMapDetailRequest> {
717 fn from(
718 value: AccountStorageRequirements,
719 ) -> Vec<account_detail_request::StorageMapDetailRequest> {
720 use account_detail_request::storage_map_detail_request::{MapKeys, SlotData};
721 let request_map = value.0;
722 let mut requests = Vec::with_capacity(request_map.len());
723 for (slot_name, map_keys) in request_map {
724 let slot_data = if map_keys.is_empty() {
725 Some(SlotData::AllEntries(true))
726 } else {
727 Some(SlotData::MapKeys(MapKeys {
728 map_keys: map_keys.into_iter().map(Into::into).collect(),
729 }))
730 };
731 requests.push(account_detail_request::StorageMapDetailRequest {
732 slot_name: slot_name.to_string(),
733 slot_data,
734 });
735 }
736 requests
737 }
738}
739
740impl Serializable for AccountStorageRequirements {
741 fn write_into<W: miden_tx::utils::ByteWriter>(&self, target: &mut W) {
742 target.write(&self.0);
743 }
744}
745
746impl Deserializable for AccountStorageRequirements {
747 fn read_from<R: miden_tx::utils::ByteReader>(
748 source: &mut R,
749 ) -> Result<Self, miden_tx::utils::DeserializationError> {
750 Ok(AccountStorageRequirements(source.read()?))
751 }
752}
753
754#[derive(Debug, Error)]
758pub enum AccountProofError {
759 #[error(
760 "the received account commitment doesn't match the received account header's commitment"
761 )]
762 InconsistentAccountCommitment,
763 #[error("the received account id doesn't match the received account header's id")]
764 InconsistentAccountId,
765 #[error(
766 "the received code commitment doesn't match the received account header's code commitment"
767 )]
768 InconsistentCodeCommitment,
769}