Skip to main content

miden_node_proto/domain/
account.rs

1use std::collections::HashSet;
2use std::fmt::{Debug, Formatter};
3
4use miden_node_utils::limiter::{QueryParamLimiter, QueryParamStorageMapKeyTotalLimit};
5use miden_protobuf::{ConversionResultExt, DecodeMessage, Decoded, RequiredField, Verify, decode};
6use miden_protocol::Word;
7#[cfg(test)]
8use miden_protocol::account::StorageSlotHeader;
9use miden_protocol::account::{
10    Account,
11    AccountCode,
12    AccountHeader,
13    AccountId,
14    AccountStorageHeader,
15    StorageMap,
16    StorageMapKey,
17    StorageSlotName,
18    StorageSlotType,
19};
20use miden_protocol::asset::Asset;
21use miden_protocol::block::BlockNumber;
22use miden_protocol::block::account_tree::AccountWitness;
23use miden_protocol::crypto::merkle::MerkleError;
24use miden_protocol::crypto::merkle::smt::{PartialSmt, SmtProof};
25
26use crate::errors::ConversionError;
27use crate::generated::{self as proto};
28
29#[cfg(test)]
30mod tests;
31
32// ACCOUNT UPDATE
33// ================================================================================================
34
35#[derive(Debug, PartialEq)]
36pub struct AccountSummary {
37    pub account_id: AccountId,
38    pub account_commitment: Word,
39    pub block_num: BlockNumber,
40}
41
42#[derive(Debug, PartialEq)]
43pub struct AccountInfo {
44    pub summary: AccountSummary,
45    pub details: Option<Account>,
46}
47
48// REGISTER ACCOUNT REQUEST
49// ================================================================================================
50
51impl Debug for proto::miden::node::v1::RegisterAccountRequest {
52    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
53        f.debug_struct("RegisterAccountRequest")
54            .field("account_id", &self.account_id)
55            .finish_non_exhaustive()
56    }
57}
58
59/// Decoded registration fields. The invitation code remains unvalidated.
60#[must_use = "decoded fields have not been verified"]
61pub struct DecodedRegisterAccountRequest {
62    pub invitation_code: String,
63    pub account_id: Decoded<proto::account::AccountId>,
64}
65
66impl Debug for DecodedRegisterAccountRequest {
67    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
68        f.debug_struct("DecodedRegisterAccountRequest")
69            .field("account_id", &self.account_id)
70            .finish_non_exhaustive()
71    }
72}
73
74impl DecodeMessage for proto::miden::node::v1::RegisterAccountRequest {
75    type Decoded = DecodedRegisterAccountRequest;
76}
77
78impl TryFrom<proto::miden::node::v1::RegisterAccountRequest> for DecodedRegisterAccountRequest {
79    type Error = ConversionError;
80
81    fn try_from(
82        request: proto::miden::node::v1::RegisterAccountRequest,
83    ) -> Result<Self, Self::Error> {
84        Ok(Self {
85            invitation_code: request.invitation_code,
86            account_id: decode(
87                RequiredField::<proto::miden::node::v1::RegisterAccountRequest, _>::new(
88                    "account_id",
89                    request.account_id,
90                ),
91            )?,
92        })
93    }
94}
95
96/// A registration request with a verified account ID. The invitation code remains unvalidated.
97pub struct RegisterAccountRequest {
98    pub invitation_code: String,
99    pub account_id: AccountId,
100}
101
102impl Debug for RegisterAccountRequest {
103    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
104        f.debug_struct("RegisterAccountRequest")
105            .field("account_id", &self.account_id)
106            .finish_non_exhaustive()
107    }
108}
109
110impl Verify for DecodedRegisterAccountRequest {
111    type Verified = RegisterAccountRequest;
112    type Error = ConversionError;
113
114    fn verify(self) -> Result<Self::Verified, Self::Error> {
115        Ok(RegisterAccountRequest {
116            invitation_code: self.invitation_code,
117            account_id: self.account_id.verify().context("account_id")?,
118        })
119    }
120}
121
122impl Verify for proto::miden::node::v1::DecodedIsAccountAllowedRequest {
123    type Verified = AccountId;
124    type Error = ConversionError;
125
126    fn verify(self) -> Result<Self::Verified, Self::Error> {
127        self.account_id.verify().context("account_id")
128    }
129}
130
131// ACCOUNT REQUEST
132// ================================================================================================
133
134/// Represents a request for an account proof.
135#[derive(Debug)]
136pub struct GetAccountRequest {
137    pub account_id: AccountId,
138    // If not present, the latest account proof references the latest available
139    pub block_num: Option<BlockNumber>,
140    pub details: Option<AccountDetailRequest>,
141}
142
143impl Verify for proto::miden::node::v1::DecodedGetAccountRequest {
144    type Verified = GetAccountRequest;
145    type Error = ConversionError;
146
147    fn verify(self) -> Result<Self::Verified, Self::Error> {
148        let Self { account_id, block_num, details } = self;
149
150        let account_id = account_id.verify().context("account_id")?;
151        let block_num = block_num.verify()?;
152
153        let details = details.verify()?;
154
155        Ok(GetAccountRequest { account_id, block_num, details })
156    }
157}
158
159/// Represents a request for account details alongside specific storage data.
160#[derive(Debug)]
161pub struct AccountDetailRequest {
162    pub code_commitment: Option<Word>,
163    pub asset_vault_commitment: Option<Word>,
164    pub storage_request: AccountStorageRequest,
165}
166
167#[derive(Debug, Clone, PartialEq, Eq)]
168pub enum AccountStorageRequest {
169    None,
170    AllStorageMaps,
171    Explicit(Vec<StorageMapRequest>),
172}
173
174impl Verify for proto::miden::node::v1::get_account_request::DecodedAccountDetailRequest {
175    type Verified = AccountDetailRequest;
176    type Error = ConversionError;
177
178    fn verify(self) -> Result<Self::Verified, Self::Error> {
179        use proto::miden::node::v1::get_account_request::account_detail_request::DecodedStorageRequest as ProtoStorageRequest;
180
181        let Self {
182            code_commitment,
183            asset_vault_commitment,
184            storage_request,
185        } = self;
186
187        let storage_request = match storage_request.into_inner() {
188            None => AccountStorageRequest::None,
189            Some(ProtoStorageRequest::AllStorageMaps(true)) => {
190                AccountStorageRequest::AllStorageMaps
191            },
192            Some(ProtoStorageRequest::AllStorageMaps(false)) => {
193                return Err(ConversionError::message("all_storage_maps must be true when set"));
194            },
195            Some(ProtoStorageRequest::StorageMaps(requests)) => {
196                let requests =
197                    requests.storage_maps.verify().context("storage_request.storage_maps")?;
198                AccountStorageRequest::Explicit(requests)
199            },
200        };
201
202        Ok(AccountDetailRequest {
203            code_commitment: code_commitment.into_inner(),
204            asset_vault_commitment: asset_vault_commitment.into_inner(),
205            storage_request,
206        })
207    }
208}
209
210#[derive(Debug, Clone, PartialEq, Eq)]
211pub struct StorageMapRequest {
212    pub slot_name: StorageSlotName,
213    pub slot_data: SlotData,
214}
215
216impl Verify
217    for proto::miden::node::v1::get_account_request::account_detail_request::DecodedStorageMapDetailRequest
218{
219    type Verified = StorageMapRequest;
220    type Error = ConversionError;
221
222    fn verify(self) -> Result<Self::Verified, Self::Error> {
223        let Self { slot_name, slot_data } = self;
224
225        let slot_name = StorageSlotName::new(slot_name).context("slot_name")?;
226        let slot_data = slot_data.verify().context("slot_data")?;
227
228        Ok(StorageMapRequest { slot_name, slot_data })
229    }
230}
231
232/// Request of slot data values.
233#[derive(Debug, Clone, PartialEq, Eq)]
234pub enum SlotData {
235    All,
236    MapKeys(Vec<StorageMapKey>),
237}
238
239impl Verify for proto::miden::node::v1::get_account_request::account_detail_request::storage_map_detail_request::DecodedSlotData {
240    type Verified = SlotData;
241    type Error = ConversionError;
242
243    fn verify(self) -> Result<Self::Verified, Self::Error> {
244        use proto::miden::node::v1::get_account_request::account_detail_request::storage_map_detail_request::DecodedSlotData as ProtoSlotData;
245
246        Ok(match self {
247            ProtoSlotData::AllEntries(true) => SlotData::All,
248            ProtoSlotData::AllEntries(false) => {
249                return Err(ConversionError::message("enum variant discriminant out of range"));
250            },
251            ProtoSlotData::MapKeys(keys) => {
252                let keys = keys
253                    .map_keys
254                    .map(StorageMapKey::new);
255                if has_duplicate_storage_map_keys(&keys) {
256                    return Err(ConversionError::message(
257                        "storage map key request contains duplicate keys",
258                    ));
259                }
260                SlotData::MapKeys(keys)
261            },
262        })
263    }
264}
265
266fn has_duplicate_storage_map_keys(keys: &[StorageMapKey]) -> bool {
267    let mut seen = HashSet::with_capacity(keys.len());
268    keys.iter().any(|key| !seen.insert(key))
269}
270
271// ACCOUNT VAULT DETAILS
272//================================================================================================
273
274/// Account vault details
275///
276/// When an account contains a large number of assets (>
277/// [`AccountVaultDetails::MAX_RETURN_ENTRIES`]), including all assets in a single RPC response
278/// creates performance issues. In such cases, the `LimitExceeded` variant indicates to the client
279/// to use the `SyncAccountVault` endpoint instead.
280#[derive(Debug, Clone, PartialEq, Eq)]
281pub enum AccountVaultDetails {
282    /// The vault has too many assets to return inline. Clients must use `SyncAccountVault` endpoint
283    /// instead.
284    LimitExceeded,
285
286    /// The assets in the vault (up to `MAX_RETURN_ENTRIES`).
287    Assets(Vec<Asset>),
288}
289
290impl AccountVaultDetails {
291    /// Maximum number of vault entries that can be returned in a single response. Accounts with
292    /// more assets will have `LimitExceeded` variant.
293    pub const MAX_RETURN_ENTRIES: usize = 1000;
294
295    pub fn empty() -> Self {
296        Self::Assets(Vec::new())
297    }
298
299    /// Creates `AccountVaultDetails` from a list of assets.
300    pub fn from_assets(assets: Vec<Asset>) -> Self {
301        if assets.len() > Self::MAX_RETURN_ENTRIES {
302            Self::LimitExceeded
303        } else {
304            Self::Assets(assets)
305        }
306    }
307}
308
309impl Verify for proto::miden::node::v1::DecodedAccountVaultDetails {
310    type Verified = AccountVaultDetails;
311    type Error = ConversionError;
312
313    fn verify(self) -> Result<Self::Verified, Self::Error> {
314        let Self { too_many_assets, assets } = self;
315
316        if too_many_assets {
317            Ok(AccountVaultDetails::LimitExceeded)
318        } else {
319            let parsed_assets = assets.verify()?;
320            Ok(AccountVaultDetails::Assets(parsed_assets))
321        }
322    }
323}
324
325impl From<AccountVaultDetails> for proto::miden::node::v1::AccountVaultDetails {
326    fn from(value: AccountVaultDetails) -> Self {
327        match value {
328            AccountVaultDetails::LimitExceeded => Self {
329                too_many_assets: true,
330                assets: Vec::new(),
331            },
332            AccountVaultDetails::Assets(assets) => Self {
333                too_many_assets: false,
334                assets: assets.into_iter().map(proto::asset::Asset::from).collect::<Vec<_>>(),
335            },
336        }
337    }
338}
339
340// ACCOUNT STORAGE MAP DETAILS
341//================================================================================================
342
343/// Details about an account storage map slot.
344#[derive(Debug, Clone, PartialEq, Eq)]
345pub struct AccountStorageMapDetails {
346    pub slot_name: StorageSlotName,
347    pub entries: StorageMapEntries,
348}
349
350/// Storage map entries for an account storage slot.
351///
352/// When a storage map contains many entries (> [`AccountStorageMapDetails::MAX_RETURN_ENTRIES`]),
353/// returning all entries in a single RPC response creates performance issues. In such cases,
354/// the `LimitExceeded` variant indicates to the client to use the `SyncAccountStorageMaps` endpoint
355/// instead.
356#[derive(Debug, Clone, PartialEq, Eq)]
357pub enum StorageMapEntries {
358    /// The map has too many entries to return inline. Clients must use `SyncAccountStorageMaps`
359    /// endpoint instead.
360    LimitExceeded,
361
362    /// All storage map entries (key-value pairs) without proofs. Used when all entries are
363    /// requested for small maps.
364    AllEntries(Vec<(StorageMapKey, Word)>),
365
366    /// Specific raw map keys covered by a single partial SMT. Used when specific keys are requested
367    /// from the storage map.
368    PartialMap {
369        map_keys: Vec<StorageMapKey>,
370        partial_smt: PartialSmt,
371    },
372}
373
374impl AccountStorageMapDetails {
375    /// Maximum number of storage map entries that can be returned in a single response.
376    pub const MAX_RETURN_ENTRIES: usize = 1000;
377
378    /// Maximum number of SMT proofs that can be returned in a single response.
379    ///
380    /// This limit is more restrictive than [`Self::MAX_RETURN_ENTRIES`] because SMT proofs
381    /// are larger (up to 64 inner nodes each) and more CPU-intensive to generate.
382    ///
383    /// This is defined by [`QueryParamStorageMapKeyTotalLimit::LIMIT`] and used both in RPC
384    /// validation and store-level enforcement to ensure consistent limits.
385    pub const MAX_SMT_PROOF_ENTRIES: usize = QueryParamStorageMapKeyTotalLimit::LIMIT;
386
387    /// Creates storage map details with all entries from the storage map.
388    ///
389    /// If the storage map has too many entries (> `MAX_RETURN_ENTRIES`),
390    /// returns `LimitExceeded` variant.
391    pub fn from_all_entries(slot_name: StorageSlotName, storage_map: &StorageMap) -> Self {
392        if storage_map.num_entries() > Self::MAX_RETURN_ENTRIES {
393            Self {
394                slot_name,
395                entries: StorageMapEntries::LimitExceeded,
396            }
397        } else {
398            let entries = storage_map.entries().map(|(k, v)| (*k, *v)).collect::<Vec<_>>();
399            Self {
400                slot_name,
401                entries: StorageMapEntries::AllEntries(entries),
402            }
403        }
404    }
405
406    /// Creates storage map details from forest-queried entries.
407    ///
408    /// Returns `LimitExceeded` if too many entries.
409    pub fn from_forest_entries(
410        slot_name: StorageSlotName,
411        entries: Vec<(StorageMapKey, Word)>,
412    ) -> Self {
413        if entries.len() > Self::MAX_RETURN_ENTRIES {
414            Self {
415                slot_name,
416                entries: StorageMapEntries::LimitExceeded,
417            }
418        } else {
419            Self {
420                slot_name,
421                entries: StorageMapEntries::AllEntries(entries),
422            }
423        }
424    }
425
426    /// Creates storage map details from pre-computed SMT proofs.
427    ///
428    /// Use this when the caller has already obtained the proofs from an `SmtForest`.
429    /// Returns `LimitExceeded` if too many proofs are provided.
430    pub fn from_proofs(
431        slot_name: StorageSlotName,
432        map_root: Word,
433        map_keys: Vec<StorageMapKey>,
434        proofs: Vec<SmtProof>,
435    ) -> Result<Self, MerkleError> {
436        if map_keys.len() != proofs.len() {
437            return Err(MerkleError::InternalError(format!(
438                "storage map key count {} does not match proof count {}",
439                map_keys.len(),
440                proofs.len()
441            )));
442        }
443        if has_duplicate_storage_map_keys(&map_keys) {
444            return Err(MerkleError::InternalError(
445                "storage map key list contains duplicate keys".into(),
446            ));
447        }
448
449        if map_keys.len() > Self::MAX_SMT_PROOF_ENTRIES {
450            return Ok(Self {
451                slot_name,
452                entries: StorageMapEntries::LimitExceeded,
453            });
454        }
455
456        let partial_smt = if proofs.is_empty() {
457            PartialSmt::new(map_root)
458        } else {
459            PartialSmt::from_proofs(proofs)?
460        };
461
462        if partial_smt.root() != map_root {
463            return Err(MerkleError::ConflictingRoots {
464                expected_root: map_root,
465                actual_root: partial_smt.root(),
466            });
467        }
468
469        for map_key in &map_keys {
470            partial_smt.get_value(&map_key.hash().as_word())?;
471        }
472
473        Ok(Self {
474            slot_name,
475            entries: StorageMapEntries::PartialMap { map_keys, partial_smt },
476        })
477    }
478
479    /// Creates storage map details indicating the limit was exceeded.
480    pub fn limit_exceeded(slot_name: StorageSlotName) -> Self {
481        Self {
482            slot_name,
483            entries: StorageMapEntries::LimitExceeded,
484        }
485    }
486}
487
488impl Verify for proto::miden::node::v1::account_storage_details::DecodedAccountStorageMapDetails {
489    type Verified = AccountStorageMapDetails;
490    type Error = ConversionError;
491
492    fn verify(self) -> Result<Self::Verified, Self::Error> {
493        use proto::miden::node::v1::account_storage_details::account_storage_map_details::{
494            DecodedAllMapEntries,
495            DecodedPartialStorageMap,
496            DecodedResult as ProtoResult,
497        };
498
499        let Self { slot_name, result } = self;
500
501        let slot_name = StorageSlotName::new(slot_name).context("slot_name")?;
502
503        let entries = match result {
504            ProtoResult::TooManyEntries(true) => StorageMapEntries::LimitExceeded,
505            ProtoResult::TooManyEntries(false) => {
506                return Err(ConversionError::message("too_many_entries must be true when set"));
507            },
508            ProtoResult::AllEntries(DecodedAllMapEntries { entries }) => {
509                let entries = entries.map(|entry| (StorageMapKey::new(entry.key), entry.value));
510                StorageMapEntries::AllEntries(entries)
511            },
512            ProtoResult::PartialMap(DecodedPartialStorageMap { map_keys, partial_smt }) => {
513                let map_keys = map_keys.into_inner();
514                if map_keys.len() > AccountStorageMapDetails::MAX_SMT_PROOF_ENTRIES {
515                    return Err(ConversionError::message(format!(
516                        "partial storage map contains {} keys, exceeding the limit of {}",
517                        map_keys.len(),
518                        AccountStorageMapDetails::MAX_SMT_PROOF_ENTRIES
519                    )));
520                }
521                let map_keys = map_keys.into_iter().map(StorageMapKey::new).collect::<Vec<_>>();
522                if has_duplicate_storage_map_keys(&map_keys) {
523                    return Err(ConversionError::message(
524                        "partial storage map contains duplicate keys",
525                    ));
526                }
527                let partial_smt: PartialSmt = partial_smt.verify().context("partial_smt")?;
528                for map_key in &map_keys {
529                    partial_smt.get_value(&map_key.hash().as_word()).context("map_keys")?;
530                }
531                StorageMapEntries::PartialMap { map_keys, partial_smt }
532            },
533        };
534
535        Ok(Self::Verified { slot_name, entries })
536    }
537}
538
539impl From<AccountStorageMapDetails>
540    for proto::miden::node::v1::account_storage_details::AccountStorageMapDetails
541{
542    fn from(value: AccountStorageMapDetails) -> Self {
543        use proto::miden::node::v1::account_storage_details::account_storage_map_details::{
544            AllMapEntries,
545            PartialStorageMap,
546            Result as ProtoResult,
547        };
548
549        let AccountStorageMapDetails { slot_name, entries } = value;
550
551        let result = match entries {
552            StorageMapEntries::LimitExceeded => ProtoResult::TooManyEntries(true),
553            StorageMapEntries::AllEntries(entries) => {
554                let all = AllMapEntries {
555                    entries: entries.into_iter().map(|(key, value)| {
556                        proto::miden::node::v1::account_storage_details::account_storage_map_details::all_map_entries::StorageMapEntry {
557                            key: Some(Word::from(key).into()),
558                            value: Some(value.into()),
559                        }
560                    }).collect::<Vec<_>>(),
561                };
562                ProtoResult::AllEntries(all)
563            },
564            StorageMapEntries::PartialMap { map_keys, partial_smt } => {
565                ProtoResult::PartialMap(PartialStorageMap {
566                    map_keys: map_keys
567                        .into_iter()
568                        .map(|key| proto::primitives::Word::from(Word::from(key)))
569                        .collect(),
570                    partial_smt: Some(partial_smt.into()),
571                })
572            },
573        };
574
575        Self {
576            slot_name: slot_name.to_string(),
577            result: Some(result),
578        }
579    }
580}
581
582#[derive(Debug, Clone, PartialEq)]
583pub struct AccountStorageDetails {
584    pub header: AccountStorageHeader,
585    pub map_details: Vec<AccountStorageMapDetails>,
586}
587
588impl AccountStorageDetails {
589    /// Creates storage details where all map slots indicate limit exceeded.
590    pub fn all_limits_exceeded(
591        header: AccountStorageHeader,
592        slot_names: impl IntoIterator<Item = StorageSlotName>,
593    ) -> Self {
594        Self {
595            header,
596            map_details: slot_names
597                .into_iter()
598                .map(AccountStorageMapDetails::limit_exceeded)
599                .collect::<Vec<_>>(),
600        }
601    }
602}
603
604impl Verify for proto::miden::node::v1::DecodedAccountStorageDetails {
605    type Verified = AccountStorageDetails;
606    type Error = ConversionError;
607
608    fn verify(self) -> Result<Self::Verified, Self::Error> {
609        let Self { header, map_details } = self;
610
611        let header: AccountStorageHeader = header.verify().context("header")?;
612
613        let map_details = map_details.verify()?;
614
615        for map_detail in &map_details {
616            let StorageMapEntries::PartialMap { partial_smt, .. } = &map_detail.entries else {
617                continue;
618            };
619
620            let slot = header.find_slot_header_by_name(&map_detail.slot_name).ok_or_else(|| {
621                ConversionError::message(format!(
622                    "partial storage map references unknown slot {}",
623                    map_detail.slot_name
624                ))
625            })?;
626            if slot.slot_type() != StorageSlotType::Map {
627                return Err(ConversionError::message(format!(
628                    "partial storage map references non-map slot {}",
629                    map_detail.slot_name
630                )));
631            }
632            if partial_smt.root() != slot.value() {
633                return Err(ConversionError::message(format!(
634                    "partial storage map root for slot {} does not match storage header",
635                    map_detail.slot_name
636                )));
637            }
638        }
639
640        Ok(Self::Verified { header, map_details })
641    }
642}
643
644impl From<AccountStorageDetails> for proto::miden::node::v1::AccountStorageDetails {
645    fn from(value: AccountStorageDetails) -> Self {
646        let AccountStorageDetails { header, map_details } = value;
647
648        Self {
649            header: Some(header.into()),
650            map_details: map_details.into_iter().map(Into::into).collect(),
651        }
652    }
653}
654
655// ACCOUNT PROOF RESPONSE
656//================================================================================================
657
658/// Represents the response to an account proof request.
659pub struct GetAccountResponse {
660    pub block_num: BlockNumber,
661    pub witness: AccountWitness,
662    pub details: Option<AccountDetails>,
663}
664
665impl Verify for proto::miden::node::v1::DecodedGetAccountResponse {
666    type Verified = GetAccountResponse;
667    type Error = ConversionError;
668
669    /// Check that supplied details match the account witness. The caller must authenticate the
670    /// witness root and block against trusted chain state.
671    fn verify(self) -> Result<Self::Verified, Self::Error> {
672        let Self { block_num, witness, details } = self;
673
674        let block_num = block_num.verify().context("block_num")?;
675
676        let witness = witness.verify().context("witness")?;
677
678        let details = details.verify()?;
679
680        if let Some(details) = &details {
681            if details.account_header.id() != witness.id() {
682                return Err(ConversionError::message("account ID does not match witness")
683                    .context("details.header.account_id"));
684            }
685            if details.account_header.to_commitment() != witness.state_commitment() {
686                return Err(ConversionError::message("account commitment does not match witness")
687                    .context("details.header"));
688            }
689        }
690
691        Ok(GetAccountResponse { block_num, witness, details })
692    }
693}
694
695impl From<GetAccountResponse> for proto::miden::node::v1::GetAccountResponse {
696    fn from(value: GetAccountResponse) -> Self {
697        let GetAccountResponse { block_num, witness, details } = value;
698
699        Self {
700            witness: Some(witness.into()),
701            details: details.map(Into::into),
702            block_num: Some(block_num.into()),
703        }
704    }
705}
706
707// ACCOUNT DETAILS
708//================================================================================================
709
710/// Represents account details returned in response to an account proof request.
711pub struct AccountDetails {
712    pub account_header: AccountHeader,
713    pub account_code: Option<AccountCode>,
714    pub vault_details: AccountVaultDetails,
715    pub storage_details: AccountStorageDetails,
716}
717
718impl AccountDetails {
719    /// Creates account details where all storage map slots indicate limit exceeded.
720    pub fn with_storage_limits_exceeded(
721        account_header: AccountHeader,
722        account_code: Option<AccountCode>,
723        vault_details: AccountVaultDetails,
724        storage_header: AccountStorageHeader,
725        slot_names: impl IntoIterator<Item = StorageSlotName>,
726    ) -> Self {
727        Self {
728            account_header,
729            account_code,
730            vault_details,
731            storage_details: AccountStorageDetails::all_limits_exceeded(storage_header, slot_names),
732        }
733    }
734}
735
736impl Verify for proto::miden::node::v1::get_account_response::DecodedAccountDetails {
737    type Verified = AccountDetails;
738    type Error = ConversionError;
739
740    /// Verify storage and supplied code commitments against the account header. This does not
741    /// authenticate the header against trusted chain state.
742    fn verify(self) -> Result<Self::Verified, Self::Error> {
743        let Self {
744            header,
745            code,
746            vault_details,
747            storage_details,
748        } = self;
749
750        let account_header = header.verify().context("header")?;
751
752        let storage_details = storage_details.verify().context("storage_details")?;
753        if storage_details.header.to_commitment() != account_header.storage_commitment() {
754            return Err(ConversionError::message(
755                "storage commitment does not match account header",
756            )
757            .context("storage_details.header"));
758        }
759
760        let vault_details = vault_details.verify().context("vault_details")?;
761        let account_code = code.verify()?;
762        if let Some(code) = &account_code
763            && code.commitment() != account_header.code_commitment()
764        {
765            return Err(ConversionError::message("code commitment does not match account header")
766                .context("code"));
767        }
768
769        Ok(AccountDetails {
770            account_header,
771            account_code,
772            vault_details,
773            storage_details,
774        })
775    }
776}
777
778impl From<AccountDetails> for proto::miden::node::v1::get_account_response::AccountDetails {
779    fn from(value: AccountDetails) -> Self {
780        let AccountDetails {
781            account_header,
782            storage_details,
783            account_code,
784            vault_details,
785        } = value;
786
787        let header = Some(proto::account::AccountHeader::from(account_header));
788        let storage_details = Some(storage_details.into());
789        let code = account_code.map(Into::into);
790        let vault_details = Some(vault_details.into());
791
792        Self {
793            header,
794            storage_details,
795            code,
796            vault_details,
797        }
798    }
799}