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#[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
48impl 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#[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
96pub 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#[derive(Debug)]
136pub struct GetAccountRequest {
137 pub account_id: AccountId,
138 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#[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#[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#[derive(Debug, Clone, PartialEq, Eq)]
281pub enum AccountVaultDetails {
282 LimitExceeded,
285
286 Assets(Vec<Asset>),
288}
289
290impl AccountVaultDetails {
291 pub const MAX_RETURN_ENTRIES: usize = 1000;
294
295 pub fn empty() -> Self {
296 Self::Assets(Vec::new())
297 }
298
299 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#[derive(Debug, Clone, PartialEq, Eq)]
345pub struct AccountStorageMapDetails {
346 pub slot_name: StorageSlotName,
347 pub entries: StorageMapEntries,
348}
349
350#[derive(Debug, Clone, PartialEq, Eq)]
357pub enum StorageMapEntries {
358 LimitExceeded,
361
362 AllEntries(Vec<(StorageMapKey, Word)>),
365
366 PartialMap {
369 map_keys: Vec<StorageMapKey>,
370 partial_smt: PartialSmt,
371 },
372}
373
374impl AccountStorageMapDetails {
375 pub const MAX_RETURN_ENTRIES: usize = 1000;
377
378 pub const MAX_SMT_PROOF_ENTRIES: usize = QueryParamStorageMapKeyTotalLimit::LIMIT;
386
387 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 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 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 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 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
655pub 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 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
707pub 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 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 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}