Skip to main content

miden_node_proto/domain/
account.rs

1use std::fmt::{Debug, Display, Formatter};
2
3use miden_node_utils::formatting::format_opt;
4use miden_node_utils::limiter::{QueryParamLimiter, QueryParamStorageMapKeyTotalLimit};
5use miden_protocol::Word;
6use miden_protocol::account::{
7    Account,
8    AccountHeader,
9    AccountId,
10    AccountStorageHeader,
11    StorageMap,
12    StorageMapKey,
13    StorageSlotHeader,
14    StorageSlotName,
15    StorageSlotType,
16};
17use miden_protocol::asset::{Asset, AssetVault};
18use miden_protocol::block::BlockNumber;
19use miden_protocol::block::account_tree::AccountWitness;
20use miden_protocol::crypto::merkle::SparseMerklePath;
21use miden_protocol::crypto::merkle::smt::SmtProof;
22use miden_protocol::note::NoteAttachment;
23use miden_protocol::utils::serde::{Deserializable, DeserializationError, Serializable};
24use miden_standards::note::{NetworkAccountTarget, NetworkAccountTargetError};
25use thiserror::Error;
26
27use super::try_convert;
28use crate::decode;
29use crate::decode::{ConversionResultExt, GrpcDecodeExt};
30use crate::errors::ConversionError;
31use crate::generated::{self as proto};
32
33#[cfg(test)]
34mod tests;
35
36// ACCOUNT ID
37// ================================================================================================
38
39impl Display for proto::account::AccountId {
40    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
41        write!(f, "0x")?;
42        for byte in &self.id {
43            write!(f, "{byte:02x}")?;
44        }
45        Ok(())
46    }
47}
48
49impl Debug for proto::account::AccountId {
50    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
51        Display::fmt(self, f)
52    }
53}
54
55// FROM PROTO ACCOUNT ID
56// ------------------------------------------------------------------------------------------------
57
58impl TryFrom<proto::account::AccountId> for AccountId {
59    type Error = ConversionError;
60
61    fn try_from(account_id: proto::account::AccountId) -> Result<Self, Self::Error> {
62        AccountId::read_from_bytes(&account_id.id)
63            .map_err(|_| ConversionError::message("value is not in the range 0..MODULUS"))
64    }
65}
66
67// INTO PROTO ACCOUNT ID
68// ------------------------------------------------------------------------------------------------
69
70impl From<&AccountId> for proto::account::AccountId {
71    fn from(account_id: &AccountId) -> Self {
72        (*account_id).into()
73    }
74}
75
76impl From<AccountId> for proto::account::AccountId {
77    fn from(account_id: AccountId) -> Self {
78        Self { id: account_id.to_bytes() }
79    }
80}
81
82// ACCOUNT UPDATE
83// ================================================================================================
84
85#[derive(Debug, PartialEq)]
86pub struct AccountSummary {
87    pub account_id: AccountId,
88    pub account_commitment: Word,
89    pub block_num: BlockNumber,
90}
91
92impl From<&AccountSummary> for proto::account::AccountSummary {
93    fn from(update: &AccountSummary) -> Self {
94        Self {
95            account_id: Some(update.account_id.into()),
96            account_commitment: Some(update.account_commitment.into()),
97            block_num: update.block_num.as_u32(),
98        }
99    }
100}
101
102#[derive(Debug, PartialEq)]
103pub struct AccountInfo {
104    pub summary: AccountSummary,
105    pub details: Option<Account>,
106}
107
108impl From<&AccountInfo> for proto::account::AccountDetails {
109    fn from(AccountInfo { summary, details }: &AccountInfo) -> Self {
110        Self {
111            summary: Some(summary.into()),
112            details: details.as_ref().map(Serializable::to_bytes),
113        }
114    }
115}
116
117// ACCOUNT STORAGE HEADER
118//================================================================================================
119
120impl TryFrom<proto::account::AccountStorageHeader> for AccountStorageHeader {
121    type Error = ConversionError;
122
123    fn try_from(value: proto::account::AccountStorageHeader) -> Result<Self, Self::Error> {
124        let proto::account::AccountStorageHeader { slots } = value;
125
126        let slot_headers = slots
127            .into_iter()
128            .map(|slot| {
129                let decoder = slot.decoder();
130                let slot_name = StorageSlotName::new(slot.slot_name)?;
131                let slot_type = storage_slot_type_from_raw(slot.slot_type)?;
132                let commitment = decode!(decoder, slot.commitment)?;
133                Ok(StorageSlotHeader::new(slot_name, slot_type, commitment))
134            })
135            .collect::<Result<Vec<_>, ConversionError>>()
136            .context("slots")?;
137
138        Ok(AccountStorageHeader::new(slot_headers)?)
139    }
140}
141
142// ACCOUNT REQUEST
143// ================================================================================================
144
145/// Represents a request for an account proof.
146pub struct AccountRequest {
147    pub account_id: AccountId,
148    // If not present, the latest account proof references the latest available
149    pub block_num: Option<BlockNumber>,
150    pub details: Option<AccountDetailRequest>,
151}
152
153impl TryFrom<proto::rpc::AccountRequest> for AccountRequest {
154    type Error = ConversionError;
155
156    fn try_from(value: proto::rpc::AccountRequest) -> Result<Self, Self::Error> {
157        let decoder = value.decoder();
158        let proto::rpc::AccountRequest { account_id, block_num, details } = value;
159
160        let account_id = decode!(decoder, account_id)?;
161        let block_num = block_num.map(Into::into);
162
163        let details = details.map(TryFrom::try_from).transpose().context("details")?;
164
165        Ok(AccountRequest { account_id, block_num, details })
166    }
167}
168
169/// Represents a request for account details alongside specific storage data.
170pub struct AccountDetailRequest {
171    pub code_commitment: Option<Word>,
172    pub asset_vault_commitment: Option<Word>,
173    pub storage_requests: Vec<StorageMapRequest>,
174}
175
176impl TryFrom<proto::rpc::account_request::AccountDetailRequest> for AccountDetailRequest {
177    type Error = ConversionError;
178
179    fn try_from(
180        value: proto::rpc::account_request::AccountDetailRequest,
181    ) -> Result<Self, Self::Error> {
182        let proto::rpc::account_request::AccountDetailRequest {
183            code_commitment,
184            asset_vault_commitment,
185            storage_maps,
186        } = value;
187
188        let code_commitment =
189            code_commitment.map(TryFrom::try_from).transpose().context("code_commitment")?;
190        let asset_vault_commitment = asset_vault_commitment
191            .map(TryFrom::try_from)
192            .transpose()
193            .context("asset_vault_commitment")?;
194        let storage_requests =
195            try_convert(storage_maps).collect::<Result<_, _>>().context("storage_maps")?;
196
197        Ok(AccountDetailRequest {
198            code_commitment,
199            asset_vault_commitment,
200            storage_requests,
201        })
202    }
203}
204
205#[derive(Debug, Clone, PartialEq, Eq)]
206pub struct StorageMapRequest {
207    pub slot_name: StorageSlotName,
208    pub slot_data: SlotData,
209}
210
211impl TryFrom<proto::rpc::account_request::account_detail_request::StorageMapDetailRequest>
212    for StorageMapRequest
213{
214    type Error = ConversionError;
215
216    fn try_from(
217        value: proto::rpc::account_request::account_detail_request::StorageMapDetailRequest,
218    ) -> Result<Self, Self::Error> {
219        let decoder = value.decoder();
220        let proto::rpc::account_request::account_detail_request::StorageMapDetailRequest {
221            slot_name,
222            slot_data,
223        } = value;
224
225        let slot_name = StorageSlotName::new(slot_name).context("slot_name")?;
226        let slot_data = decode!(decoder, 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
240    TryFrom<
241        proto::rpc::account_request::account_detail_request::storage_map_detail_request::SlotData,
242    > for SlotData
243{
244    type Error = ConversionError;
245
246    fn try_from(
247        value: proto::rpc::account_request::account_detail_request::storage_map_detail_request::SlotData,
248    ) -> Result<Self, Self::Error> {
249        use proto::rpc::account_request::account_detail_request::storage_map_detail_request::SlotData as ProtoSlotData;
250
251        Ok(match value {
252            ProtoSlotData::AllEntries(true) => SlotData::All,
253            ProtoSlotData::AllEntries(false) => {
254                return Err(ConversionError::message("enum variant discriminant out of range"));
255            },
256            ProtoSlotData::MapKeys(keys) => {
257                let keys = try_convert(keys.map_keys).collect::<Result<Vec<_>, _>>()?;
258                SlotData::MapKeys(keys)
259            },
260        })
261    }
262}
263
264// ACCOUNT HEADER CONVERSIONS
265//================================================================================================
266
267impl TryFrom<proto::account::AccountHeader> for AccountHeader {
268    type Error = ConversionError;
269
270    fn try_from(value: proto::account::AccountHeader) -> Result<Self, Self::Error> {
271        let decoder = value.decoder();
272        let proto::account::AccountHeader {
273            account_id,
274            vault_root,
275            storage_commitment,
276            code_commitment,
277            nonce,
278        } = value;
279
280        let account_id = decode!(decoder, account_id)?;
281        let vault_root = decode!(decoder, vault_root)?;
282        let storage_commitment = decode!(decoder, storage_commitment)?;
283        let code_commitment = decode!(decoder, code_commitment)?;
284        let nonce = nonce
285            .try_into()
286            .map_err(|e| ConversionError::message(format!("{e}")))
287            .context("nonce")?;
288
289        Ok(AccountHeader::new(
290            account_id,
291            nonce,
292            vault_root,
293            storage_commitment,
294            code_commitment,
295        ))
296    }
297}
298
299impl From<AccountHeader> for proto::account::AccountHeader {
300    fn from(header: AccountHeader) -> Self {
301        proto::account::AccountHeader {
302            account_id: Some(header.id().into()),
303            vault_root: Some(header.vault_root().into()),
304            storage_commitment: Some(header.storage_commitment().into()),
305            code_commitment: Some(header.code_commitment().into()),
306            nonce: header.nonce().as_canonical_u64(),
307        }
308    }
309}
310
311impl From<AccountStorageHeader> for proto::account::AccountStorageHeader {
312    fn from(value: AccountStorageHeader) -> Self {
313        let slots = value
314            .slots()
315            .map(|slot_header| proto::account::account_storage_header::StorageSlot {
316                slot_name: slot_header.name().to_string(),
317                slot_type: storage_slot_type_to_raw(slot_header.slot_type()),
318                commitment: Some(proto::primitives::Digest::from(slot_header.value())),
319            })
320            .collect();
321
322        Self { slots }
323    }
324}
325
326// ACCOUNT VAULT DETAILS
327//================================================================================================
328
329/// Account vault details
330///
331/// When an account contains a large number of assets (>
332/// [`AccountVaultDetails::MAX_RETURN_ENTRIES`]), including all assets in a single RPC response
333/// creates performance issues. In such cases, the `LimitExceeded` variant indicates to the client
334/// to use the `SyncAccountVault` endpoint instead.
335#[derive(Debug, Clone, PartialEq, Eq)]
336pub enum AccountVaultDetails {
337    /// The vault has too many assets to return inline.
338    /// Clients must use `SyncAccountVault` endpoint instead.
339    LimitExceeded,
340
341    /// The assets in the vault (up to `MAX_RETURN_ENTRIES`).
342    Assets(Vec<Asset>),
343}
344
345impl AccountVaultDetails {
346    /// Maximum number of vault entries that can be returned in a single response.
347    /// Accounts with more assets will have `LimitExceeded` variant.
348    pub const MAX_RETURN_ENTRIES: usize = 1000;
349
350    pub fn new(vault: &AssetVault) -> Self {
351        if vault.assets().nth(Self::MAX_RETURN_ENTRIES).is_some() {
352            Self::LimitExceeded
353        } else {
354            Self::Assets(Vec::from_iter(vault.assets()))
355        }
356    }
357
358    pub fn empty() -> Self {
359        Self::Assets(Vec::new())
360    }
361
362    /// Creates `AccountVaultDetails` from a list of assets.
363    pub fn from_assets(assets: Vec<Asset>) -> Self {
364        if assets.len() > Self::MAX_RETURN_ENTRIES {
365            Self::LimitExceeded
366        } else {
367            Self::Assets(assets)
368        }
369    }
370}
371
372impl TryFrom<proto::rpc::AccountVaultDetails> for AccountVaultDetails {
373    type Error = ConversionError;
374
375    fn try_from(value: proto::rpc::AccountVaultDetails) -> Result<Self, Self::Error> {
376        let proto::rpc::AccountVaultDetails { too_many_assets, assets } = value;
377
378        if too_many_assets {
379            Ok(Self::LimitExceeded)
380        } else {
381            let parsed_assets = Result::<Vec<_>, ConversionError>::from_iter(
382                assets.into_iter().map(Asset::try_from),
383            )?;
384            Ok(Self::Assets(parsed_assets))
385        }
386    }
387}
388
389impl From<AccountVaultDetails> for proto::rpc::AccountVaultDetails {
390    fn from(value: AccountVaultDetails) -> Self {
391        match value {
392            AccountVaultDetails::LimitExceeded => Self {
393                too_many_assets: true,
394                assets: Vec::new(),
395            },
396            AccountVaultDetails::Assets(assets) => Self {
397                too_many_assets: false,
398                assets: Vec::from_iter(assets.into_iter().map(proto::primitives::Asset::from)),
399            },
400        }
401    }
402}
403
404// ACCOUNT STORAGE MAP DETAILS
405//================================================================================================
406
407/// Details about an account storage map slot.
408#[derive(Debug, Clone, PartialEq, Eq)]
409pub struct AccountStorageMapDetails {
410    pub slot_name: StorageSlotName,
411    pub entries: StorageMapEntries,
412}
413
414/// Storage map entries for an account storage slot.
415///
416/// When a storage map contains many entries (> [`AccountStorageMapDetails::MAX_RETURN_ENTRIES`]),
417/// returning all entries in a single RPC response creates performance issues. In such cases,
418/// the `LimitExceeded` variant indicates to the client to use the `SyncAccountStorageMaps` endpoint
419/// instead.
420#[derive(Debug, Clone, PartialEq, Eq)]
421pub enum StorageMapEntries {
422    /// The map has too many entries to return inline.
423    /// Clients must use `SyncAccountStorageMaps` endpoint instead.
424    LimitExceeded,
425
426    /// All storage map entries (key-value pairs) without proofs.
427    /// Used when all entries are requested for small maps.
428    AllEntries(Vec<(StorageMapKey, Word)>),
429
430    /// Specific entries with their SMT proofs for client-side verification.
431    /// Used when specific keys are requested from the storage map.
432    EntriesWithProofs(Vec<SmtProof>),
433}
434
435impl AccountStorageMapDetails {
436    /// Maximum number of storage map entries that can be returned in a single response.
437    pub const MAX_RETURN_ENTRIES: usize = 1000;
438
439    /// Maximum number of SMT proofs that can be returned in a single response.
440    ///
441    /// This limit is more restrictive than [`Self::MAX_RETURN_ENTRIES`] because SMT proofs
442    /// are larger (up to 64 inner nodes each) and more CPU-intensive to generate.
443    ///
444    /// This is defined by [`QueryParamStorageMapKeyTotalLimit::LIMIT`] and used both in RPC
445    /// validation and store-level enforcement to ensure consistent limits.
446    pub const MAX_SMT_PROOF_ENTRIES: usize = QueryParamStorageMapKeyTotalLimit::LIMIT;
447
448    /// Creates storage map details with all entries from the storage map.
449    ///
450    /// If the storage map has too many entries (> `MAX_RETURN_ENTRIES`),
451    /// returns `LimitExceeded` variant.
452    pub fn from_all_entries(slot_name: StorageSlotName, storage_map: &StorageMap) -> Self {
453        if storage_map.num_entries() > Self::MAX_RETURN_ENTRIES {
454            Self {
455                slot_name,
456                entries: StorageMapEntries::LimitExceeded,
457            }
458        } else {
459            let entries = Vec::from_iter(storage_map.entries().map(|(k, v)| (*k, *v)));
460            Self {
461                slot_name,
462                entries: StorageMapEntries::AllEntries(entries),
463            }
464        }
465    }
466
467    /// Creates storage map details from forest-queried entries.
468    ///
469    /// Returns `LimitExceeded` if too many entries.
470    pub fn from_forest_entries(
471        slot_name: StorageSlotName,
472        entries: Vec<(StorageMapKey, Word)>,
473    ) -> Self {
474        if entries.len() > Self::MAX_RETURN_ENTRIES {
475            Self {
476                slot_name,
477                entries: StorageMapEntries::LimitExceeded,
478            }
479        } else {
480            Self {
481                slot_name,
482                entries: StorageMapEntries::AllEntries(entries),
483            }
484        }
485    }
486
487    /// Creates storage map details from pre-computed SMT proofs.
488    ///
489    /// Use this when the caller has already obtained the proofs from an `SmtForest`.
490    /// Returns `LimitExceeded` if too many proofs are provided.
491    pub fn from_proofs(slot_name: StorageSlotName, proofs: Vec<SmtProof>) -> Self {
492        if proofs.len() > Self::MAX_SMT_PROOF_ENTRIES {
493            Self {
494                slot_name,
495                entries: StorageMapEntries::LimitExceeded,
496            }
497        } else {
498            Self {
499                slot_name,
500                entries: StorageMapEntries::EntriesWithProofs(proofs),
501            }
502        }
503    }
504
505    /// Creates storage map details indicating the limit was exceeded.
506    pub fn limit_exceeded(slot_name: StorageSlotName) -> Self {
507        Self {
508            slot_name,
509            entries: StorageMapEntries::LimitExceeded,
510        }
511    }
512}
513
514impl TryFrom<proto::rpc::account_storage_details::AccountStorageMapDetails>
515    for AccountStorageMapDetails
516{
517    type Error = ConversionError;
518
519    fn try_from(
520        value: proto::rpc::account_storage_details::AccountStorageMapDetails,
521    ) -> Result<Self, Self::Error> {
522        use proto::rpc::account_storage_details::account_storage_map_details::{
523            AllMapEntries,
524            Entries as ProtoEntries,
525            MapEntriesWithProofs,
526        };
527
528        let proto::rpc::account_storage_details::AccountStorageMapDetails {
529            slot_name,
530            too_many_entries,
531            entries,
532        } = value;
533
534        let slot_name = StorageSlotName::new(slot_name).context("slot_name")?;
535
536        let entries = if too_many_entries {
537            StorageMapEntries::LimitExceeded
538        } else {
539            match entries {
540                None => {
541                    return Err(ConversionError::missing_field::<
542                        proto::rpc::account_storage_details::AccountStorageMapDetails,
543                    >("entries"));
544                },
545                Some(ProtoEntries::AllEntries(AllMapEntries { entries })) => {
546                    let entries = entries
547                        .into_iter()
548                        .map(|entry| {
549                            let decoder = entry.decoder();
550                            let key = StorageMapKey::new(decode!(decoder, entry.key)?);
551                            let value = decode!(decoder, entry.value)?;
552                            Ok((key, value))
553                        })
554                        .collect::<Result<Vec<_>, ConversionError>>()
555                        .context("entries")?;
556                    StorageMapEntries::AllEntries(entries)
557                },
558                Some(ProtoEntries::EntriesWithProofs(MapEntriesWithProofs { entries })) => {
559                    let proofs = entries
560                        .into_iter()
561                        .map(|entry| {
562                            let decoder = entry.decoder();
563                            decode!(decoder, entry.proof)
564                        })
565                        .collect::<Result<Vec<_>, ConversionError>>()
566                        .context("entries")?;
567                    StorageMapEntries::EntriesWithProofs(proofs)
568                },
569            }
570        };
571
572        Ok(Self { slot_name, entries })
573    }
574}
575
576impl From<AccountStorageMapDetails>
577    for proto::rpc::account_storage_details::AccountStorageMapDetails
578{
579    fn from(value: AccountStorageMapDetails) -> Self {
580        use proto::rpc::account_storage_details::account_storage_map_details::{
581            AllMapEntries,
582            Entries as ProtoEntries,
583            MapEntriesWithProofs,
584        };
585
586        let AccountStorageMapDetails { slot_name, entries } = value;
587
588        let (too_many_entries, proto_entries) = match entries {
589            StorageMapEntries::LimitExceeded => (true, None),
590            StorageMapEntries::AllEntries(entries) => {
591                let all = AllMapEntries {
592                    entries: Vec::from_iter(entries.into_iter().map(|(key, value)| {
593                        proto::rpc::account_storage_details::account_storage_map_details::all_map_entries::StorageMapEntry {
594                            key: Some(key.into()),
595                            value: Some(value.into()),
596                        }
597                    })),
598                };
599                (false, Some(ProtoEntries::AllEntries(all)))
600            },
601            StorageMapEntries::EntriesWithProofs(proofs) => {
602                use miden_protocol::crypto::merkle::smt::SmtLeaf;
603
604                let with_proofs = MapEntriesWithProofs {
605                    entries: Vec::from_iter(proofs.into_iter().map(|proof| {
606                        // Get key/value from the leaf before consuming the proof
607                        let (key, value) = match proof.leaf() {
608                            SmtLeaf::Empty(_) => {
609                                (miden_protocol::EMPTY_WORD, miden_protocol::EMPTY_WORD)
610                            },
611                            SmtLeaf::Single((k, v)) => (*k, *v),
612                            SmtLeaf::Multiple(entries) => entries.iter().next().map_or(
613                                (miden_protocol::EMPTY_WORD, miden_protocol::EMPTY_WORD),
614                                |(k, v)| (*k, *v),
615                            ),
616                        };
617                        let smt_opening = proto::primitives::SmtOpening::from(proof);
618                        proto::rpc::account_storage_details::account_storage_map_details::map_entries_with_proofs::StorageMapEntryWithProof {
619                            key: Some(key.into()),
620                            value: Some(value.into()),
621                            proof: Some(smt_opening),
622                        }
623                    })),
624                };
625                (false, Some(ProtoEntries::EntriesWithProofs(with_proofs)))
626            },
627        };
628
629        Self {
630            slot_name: slot_name.to_string(),
631            too_many_entries,
632            entries: proto_entries,
633        }
634    }
635}
636
637#[derive(Debug, Clone, PartialEq)]
638pub struct AccountStorageDetails {
639    pub header: AccountStorageHeader,
640    pub map_details: Vec<AccountStorageMapDetails>,
641}
642
643impl AccountStorageDetails {
644    /// Creates storage details where all map slots indicate limit exceeded.
645    pub fn all_limits_exceeded(
646        header: AccountStorageHeader,
647        slot_names: impl IntoIterator<Item = StorageSlotName>,
648    ) -> Self {
649        Self {
650            header,
651            map_details: Vec::from_iter(
652                slot_names.into_iter().map(AccountStorageMapDetails::limit_exceeded),
653            ),
654        }
655    }
656}
657
658impl TryFrom<proto::rpc::AccountStorageDetails> for AccountStorageDetails {
659    type Error = ConversionError;
660
661    fn try_from(value: proto::rpc::AccountStorageDetails) -> Result<Self, Self::Error> {
662        let decoder = value.decoder();
663        let proto::rpc::AccountStorageDetails { header, map_details } = value;
664
665        let header = decode!(decoder, header)?;
666
667        let map_details =
668            try_convert(map_details).collect::<Result<Vec<_>, _>>().context("map_details")?;
669
670        Ok(Self { header, map_details })
671    }
672}
673
674impl From<AccountStorageDetails> for proto::rpc::AccountStorageDetails {
675    fn from(value: AccountStorageDetails) -> Self {
676        let AccountStorageDetails { header, map_details } = value;
677
678        Self {
679            header: Some(header.into()),
680            map_details: map_details.into_iter().map(Into::into).collect(),
681        }
682    }
683}
684
685fn storage_slot_type_from_raw(slot_type: u32) -> Result<StorageSlotType, ConversionError> {
686    Ok(match slot_type {
687        0 => StorageSlotType::Value,
688        1 => StorageSlotType::Map,
689        _ => return Err(ConversionError::message("enum variant discriminant out of range")),
690    })
691}
692
693const fn storage_slot_type_to_raw(slot_type: StorageSlotType) -> u32 {
694    match slot_type {
695        StorageSlotType::Value => 0,
696        StorageSlotType::Map => 1,
697    }
698}
699
700// ACCOUNT PROOF RESPONSE
701//================================================================================================
702
703/// Represents the response to an account proof request.
704pub struct AccountResponse {
705    pub block_num: BlockNumber,
706    pub witness: AccountWitness,
707    pub details: Option<AccountDetails>,
708}
709
710impl TryFrom<proto::rpc::AccountResponse> for AccountResponse {
711    type Error = ConversionError;
712
713    fn try_from(value: proto::rpc::AccountResponse) -> Result<Self, Self::Error> {
714        let decoder = value.decoder();
715        let proto::rpc::AccountResponse { block_num, witness, details } = value;
716
717        let block_num = block_num
718            .ok_or(ConversionError::missing_field::<proto::rpc::AccountResponse>("block_num"))?
719            .into();
720
721        let witness = decode!(decoder, witness)?;
722
723        let details = details.map(TryFrom::try_from).transpose().context("details")?;
724
725        Ok(AccountResponse { block_num, witness, details })
726    }
727}
728
729impl From<AccountResponse> for proto::rpc::AccountResponse {
730    fn from(value: AccountResponse) -> Self {
731        let AccountResponse { block_num, witness, details } = value;
732
733        Self {
734            witness: Some(witness.into()),
735            details: details.map(Into::into),
736            block_num: Some(block_num.into()),
737        }
738    }
739}
740
741// ACCOUNT DETAILS
742//================================================================================================
743
744/// Represents account details returned in response to an account proof request.
745pub struct AccountDetails {
746    pub account_header: AccountHeader,
747    pub account_code: Option<Vec<u8>>,
748    pub vault_details: AccountVaultDetails,
749    pub storage_details: AccountStorageDetails,
750}
751
752impl AccountDetails {
753    /// Creates account details where all storage map slots indicate limit exceeded.
754    pub fn with_storage_limits_exceeded(
755        account_header: AccountHeader,
756        account_code: Option<Vec<u8>>,
757        vault_details: AccountVaultDetails,
758        storage_header: AccountStorageHeader,
759        slot_names: impl IntoIterator<Item = StorageSlotName>,
760    ) -> Self {
761        Self {
762            account_header,
763            account_code,
764            vault_details,
765            storage_details: AccountStorageDetails::all_limits_exceeded(storage_header, slot_names),
766        }
767    }
768}
769
770impl TryFrom<proto::rpc::account_response::AccountDetails> for AccountDetails {
771    type Error = ConversionError;
772
773    fn try_from(value: proto::rpc::account_response::AccountDetails) -> Result<Self, Self::Error> {
774        let decoder = value.decoder();
775        let proto::rpc::account_response::AccountDetails {
776            header,
777            code,
778            vault_details,
779            storage_details,
780        } = value;
781
782        let account_header = decode!(decoder, header)?;
783
784        let storage_details = decode!(decoder, storage_details)?;
785
786        let vault_details = decode!(decoder, vault_details)?;
787        let account_code = code;
788
789        Ok(AccountDetails {
790            account_header,
791            account_code,
792            vault_details,
793            storage_details,
794        })
795    }
796}
797
798impl From<AccountDetails> for proto::rpc::account_response::AccountDetails {
799    fn from(value: AccountDetails) -> Self {
800        let AccountDetails {
801            account_header,
802            storage_details,
803            account_code,
804            vault_details,
805        } = value;
806
807        let header = Some(proto::account::AccountHeader::from(account_header));
808        let storage_details = Some(storage_details.into());
809        let code = account_code;
810        let vault_details = Some(vault_details.into());
811
812        Self {
813            header,
814            storage_details,
815            code,
816            vault_details,
817        }
818    }
819}
820
821// ACCOUNT WITNESS
822// ================================================================================================
823
824impl TryFrom<proto::account::AccountWitness> for AccountWitness {
825    type Error = ConversionError;
826
827    fn try_from(account_witness: proto::account::AccountWitness) -> Result<Self, Self::Error> {
828        let decoder = account_witness.decoder();
829        let witness_id = decode!(decoder, account_witness.witness_id)?;
830        let commitment = decode!(decoder, account_witness.commitment)?;
831        let path = decode!(decoder, account_witness.path)?;
832
833        AccountWitness::new(witness_id, commitment, path).map_err(|err| {
834            ConversionError::deserialization(
835                "AccountWitness",
836                DeserializationError::InvalidValue(err.to_string()),
837            )
838        })
839    }
840}
841
842impl From<AccountWitness> for proto::account::AccountWitness {
843    fn from(witness: AccountWitness) -> Self {
844        Self {
845            account_id: Some(witness.id().into()),
846            witness_id: Some(witness.id().into()),
847            commitment: Some(witness.state_commitment().into()),
848            path: Some(witness.into_proof().into_parts().0.into()),
849        }
850    }
851}
852
853// ACCOUNT WITNESS RECORD
854// ================================================================================================
855
856#[derive(Clone, Debug, PartialEq, Eq)]
857pub struct AccountWitnessRecord {
858    pub account_id: AccountId,
859    pub witness: AccountWitness,
860}
861
862impl TryFrom<proto::account::AccountWitness> for AccountWitnessRecord {
863    type Error = ConversionError;
864
865    fn try_from(
866        account_witness_record: proto::account::AccountWitness,
867    ) -> Result<Self, Self::Error> {
868        let decoder = account_witness_record.decoder();
869        let witness_id = decode!(decoder, account_witness_record.witness_id)?;
870        let commitment = decode!(decoder, account_witness_record.commitment)?;
871        let account_id = decode!(decoder, account_witness_record.account_id)?;
872        let path: SparseMerklePath = decode!(decoder, account_witness_record.path)?;
873
874        let witness = AccountWitness::new(witness_id, commitment, path).map_err(|err| {
875            ConversionError::deserialization(
876                "AccountWitness",
877                DeserializationError::InvalidValue(err.to_string()),
878            )
879        })?;
880
881        Ok(Self { account_id, witness })
882    }
883}
884
885impl From<AccountWitnessRecord> for proto::account::AccountWitness {
886    fn from(from: AccountWitnessRecord) -> Self {
887        Self {
888            account_id: Some(from.account_id.into()),
889            witness_id: Some(from.witness.id().into()),
890            commitment: Some(from.witness.state_commitment().into()),
891            path: Some(from.witness.path().clone().into()),
892        }
893    }
894}
895
896// ACCOUNT STATE
897// ================================================================================================
898
899/// Information needed from the store to verify account in transaction.
900#[derive(Debug)]
901pub struct AccountState {
902    /// Account ID
903    pub account_id: AccountId,
904    /// The account commitment in the store corresponding to tx's account ID
905    pub account_commitment: Option<Word>,
906}
907
908impl Display for AccountState {
909    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
910        f.write_fmt(format_args!(
911            "{{ account_id: {}, account_commitment: {} }}",
912            self.account_id,
913            format_opt(self.account_commitment.as_ref()),
914        ))
915    }
916}
917
918impl TryFrom<proto::store::transaction_inputs::AccountTransactionInputRecord> for AccountState {
919    type Error = ConversionError;
920
921    fn try_from(
922        from: proto::store::transaction_inputs::AccountTransactionInputRecord,
923    ) -> Result<Self, Self::Error> {
924        let decoder = from.decoder();
925        let account_id = decode!(decoder, from.account_id)?;
926
927        let account_commitment = decode!(decoder, from.account_commitment)?;
928
929        // If the commitment is equal to `Word::empty()`, it signifies that this is a new
930        // account which is not yet present in the Store.
931        let account_commitment = if account_commitment == Word::empty() {
932            None
933        } else {
934            Some(account_commitment)
935        };
936
937        Ok(Self { account_id, account_commitment })
938    }
939}
940
941impl From<AccountState> for proto::store::transaction_inputs::AccountTransactionInputRecord {
942    fn from(from: AccountState) -> Self {
943        Self {
944            account_id: Some(from.account_id.into()),
945            account_commitment: from.account_commitment.map(Into::into),
946        }
947    }
948}
949
950// ASSET
951// ================================================================================================
952
953impl TryFrom<proto::primitives::Asset> for Asset {
954    type Error = ConversionError;
955
956    fn try_from(asset: proto::primitives::Asset) -> Result<Self, Self::Error> {
957        let decoder = asset.decoder();
958        let key_word: Word = decode!(decoder, asset.key)?;
959        let value_word: Word = decode!(decoder, asset.value)?;
960
961        let asset = Asset::from_key_value_words(key_word, value_word)?;
962        Ok(asset)
963    }
964}
965
966impl From<Asset> for proto::primitives::Asset {
967    fn from(asset_from: Asset) -> Self {
968        proto::primitives::Asset {
969            key: Some(asset_from.to_key_word().into()),
970            value: Some(asset_from.to_value_word().into()),
971        }
972    }
973}
974
975// NETWORK ACCOUNT PREFIX
976// ================================================================================================
977
978pub type AccountPrefix = u32;
979
980/// Newtype wrapper for network account IDs.
981///
982/// Provides type safety for accounts that are meant for network execution.
983/// This wraps the full `AccountId` of a network account, typically extracted
984/// from a `NetworkAccountTarget` attachment.
985#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
986pub struct NetworkAccountId(AccountId);
987
988impl std::fmt::Display for NetworkAccountId {
989    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
990        std::fmt::Display::fmt(&self.0, f)
991    }
992}
993
994impl NetworkAccountId {
995    /// Returns the inner `AccountId`.
996    pub fn inner(&self) -> AccountId {
997        self.0
998    }
999
1000    /// Gets the 30-bit prefix of the account ID used for tag matching.
1001    pub fn prefix(&self) -> AccountPrefix {
1002        get_account_id_tag_prefix(self.0)
1003    }
1004}
1005
1006impl TryFrom<AccountId> for NetworkAccountId {
1007    type Error = NetworkAccountError;
1008
1009    fn try_from(id: AccountId) -> Result<Self, Self::Error> {
1010        if !id.is_network() {
1011            return Err(NetworkAccountError::NotNetworkAccount(id));
1012        }
1013        Ok(NetworkAccountId(id))
1014    }
1015}
1016
1017impl TryFrom<&NoteAttachment> for NetworkAccountId {
1018    type Error = NetworkAccountError;
1019
1020    fn try_from(attachment: &NoteAttachment) -> Result<Self, Self::Error> {
1021        let target = NetworkAccountTarget::try_from(attachment)
1022            .map_err(NetworkAccountError::InvalidAttachment)?;
1023        Ok(NetworkAccountId(target.target_id()))
1024    }
1025}
1026
1027impl TryFrom<NoteAttachment> for NetworkAccountId {
1028    type Error = NetworkAccountError;
1029
1030    fn try_from(attachment: NoteAttachment) -> Result<Self, Self::Error> {
1031        NetworkAccountId::try_from(&attachment)
1032    }
1033}
1034
1035impl From<NetworkAccountId> for AccountId {
1036    fn from(value: NetworkAccountId) -> Self {
1037        value.inner()
1038    }
1039}
1040
1041impl From<NetworkAccountId> for u32 {
1042    /// Returns the 30-bit prefix of the network account ID.
1043    /// This is used for note tag matching.
1044    fn from(value: NetworkAccountId) -> Self {
1045        value.prefix()
1046    }
1047}
1048
1049#[derive(Debug, Error)]
1050pub enum NetworkAccountError {
1051    #[error("account ID {0} is not a valid network account ID")]
1052    NotNetworkAccount(AccountId),
1053    #[error("invalid network account attachment: {0}")]
1054    InvalidAttachment(#[source] NetworkAccountTargetError),
1055}
1056
1057/// Gets the 30-bit prefix of the account ID.
1058fn get_account_id_tag_prefix(id: AccountId) -> AccountPrefix {
1059    (id.prefix().as_u64() >> 34) as AccountPrefix
1060}