1use alloc::{collections::BTreeMap, string::String, vec::Vec};
2use core::fmt::{self, Debug, Display, Formatter};
3
4use miden_objects::{
5 Digest,
6 account::{Account, AccountCode, AccountHeader, AccountId, AccountStorageHeader},
7 block::{AccountWitness, BlockNumber},
8 crypto::merkle::{MerklePath, SmtProof},
9};
10use miden_tx::utils::{Deserializable, Serializable, ToHex};
11use thiserror::Error;
12
13use crate::rpc::{
14 RpcError,
15 errors::RpcConversionError,
16 generated::{
17 account::{AccountHeader as ProtoAccountHeader, AccountId as ProtoAccountId},
18 requests::get_account_proofs_request,
19 responses::{
20 AccountStateHeader as ProtoAccountStateHeader, AccountWitness as ProtoAccountWitness,
21 },
22 },
23};
24
25pub enum FetchedAccount {
30 Private(AccountId, AccountUpdateSummary),
33 Public(Account, AccountUpdateSummary),
36}
37
38impl FetchedAccount {
39 pub fn account_id(&self) -> AccountId {
41 match self {
42 Self::Private(account_id, _) => *account_id,
43 Self::Public(account, _) => account.id(),
44 }
45 }
46
47 pub fn commitment(&self) -> Digest {
49 match self {
50 Self::Private(_, summary) | Self::Public(_, summary) => summary.commitment,
51 }
52 }
53
54 pub fn account(&self) -> Option<&Account> {
56 match self {
57 Self::Private(..) => None,
58 Self::Public(account, _) => Some(account),
59 }
60 }
61}
62
63pub struct AccountUpdateSummary {
68 pub commitment: Digest,
70 pub last_block_num: u32,
72}
73
74impl AccountUpdateSummary {
75 pub fn new(commitment: Digest, last_block_num: u32) -> Self {
77 Self { commitment, last_block_num }
78 }
79}
80
81impl Display for ProtoAccountId {
85 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
86 f.write_fmt(format_args!("0x{}", self.id.to_hex()))
87 }
88}
89
90impl Debug for ProtoAccountId {
91 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
92 Display::fmt(self, f)
93 }
94}
95
96impl From<AccountId> for ProtoAccountId {
100 fn from(account_id: AccountId) -> Self {
101 Self { id: account_id.to_bytes() }
102 }
103}
104
105impl TryFrom<ProtoAccountId> for AccountId {
109 type Error = RpcConversionError;
110
111 fn try_from(account_id: ProtoAccountId) -> Result<Self, Self::Error> {
112 AccountId::read_from_bytes(&account_id.id).map_err(|_| RpcConversionError::NotAValidFelt)
113 }
114}
115
116impl ProtoAccountHeader {
120 #[cfg(any(feature = "tonic", feature = "web-tonic"))]
121 pub fn into_domain(self, account_id: AccountId) -> Result<AccountHeader, crate::rpc::RpcError> {
122 use alloc::string::String;
123
124 use miden_objects::Felt;
125
126 use crate::rpc::RpcError;
127
128 let ProtoAccountHeader {
129 nonce,
130 vault_root,
131 storage_commitment,
132 code_commitment,
133 } = self;
134 let vault_root = vault_root
135 .ok_or(RpcError::ExpectedDataMissing(String::from("AccountHeader.VaultRoot")))?
136 .try_into()?;
137 let storage_commitment = storage_commitment
138 .ok_or(RpcError::ExpectedDataMissing(String::from("AccountHeader.StorageCommitment")))?
139 .try_into()?;
140 let code_commitment = code_commitment
141 .ok_or(RpcError::ExpectedDataMissing(String::from("AccountHeader.CodeCommitment")))?
142 .try_into()?;
143
144 Ok(AccountHeader::new(
145 account_id,
146 Felt::new(nonce),
147 vault_root,
148 storage_commitment,
149 code_commitment,
150 ))
151 }
152}
153
154impl ProtoAccountStateHeader {
158 #[cfg(any(feature = "tonic", feature = "web-tonic"))]
168 pub fn into_domain(
169 self,
170 account_id: AccountId,
171 known_account_codes: &BTreeMap<Digest, AccountCode>,
172 ) -> Result<StateHeaders, crate::rpc::RpcError> {
173 use crate::rpc::{RpcError, generated::responses::StorageSlotMapProof};
174
175 let ProtoAccountStateHeader {
176 header,
177 storage_header,
178 account_code,
179 storage_maps,
180 } = self;
181 let account_header = header
182 .ok_or(RpcError::ExpectedDataMissing("Account.StateHeader".into()))?
183 .into_domain(account_id)?;
184
185 let storage_header = AccountStorageHeader::read_from_bytes(&storage_header)?;
186
187 let code = {
191 let received_code =
192 account_code.map(|c| AccountCode::read_from_bytes(&c)).transpose()?;
193 match received_code {
194 Some(code) => code,
195 None => known_account_codes
196 .get(&account_header.code_commitment())
197 .ok_or(RpcError::InvalidResponse(
198 "Account code was not provided, but the response did not contain it either"
199 .into(),
200 ))?
201 .clone(),
202 }
203 };
204
205 let mut storage_slot_proofs: BTreeMap<u8, Vec<SmtProof>> = BTreeMap::new();
207 for StorageSlotMapProof { storage_slot, smt_proof } in storage_maps {
208 let proof = SmtProof::read_from_bytes(&smt_proof)?;
209 match storage_slot_proofs
210 .get_mut(&(u8::try_from(storage_slot).expect("there are no more than 256 slots")))
211 {
212 Some(list) => list.push(proof),
213 None => {
214 _ = storage_slot_proofs.insert(
215 u8::try_from(storage_slot).expect("only 256 storage slots"),
216 vec![proof],
217 );
218 },
219 }
220 }
221
222 Ok(StateHeaders {
223 account_header,
224 storage_header,
225 code,
226 storage_slots: storage_slot_proofs,
227 })
228 }
229}
230
231pub type AccountProofs = (BlockNumber, Vec<AccountProof>);
236
237#[derive(Clone, Debug)]
239pub struct StateHeaders {
240 pub account_header: AccountHeader,
242 pub storage_header: AccountStorageHeader,
243 pub code: AccountCode,
244 pub storage_slots: BTreeMap<StorageSlotIndex, Vec<SmtProof>>,
245}
246
247#[derive(Clone, Debug)]
249pub struct AccountProof {
250 account_witness: AccountWitness,
252 state_headers: Option<StateHeaders>,
254}
255
256impl AccountProof {
257 pub fn new(
259 account_witness: AccountWitness,
260 state_headers: Option<StateHeaders>,
261 ) -> Result<Self, AccountProofError> {
262 if let Some(StateHeaders {
263 account_header, storage_header: _, code, ..
264 }) = &state_headers
265 {
266 if account_header.commitment() != account_witness.state_commitment() {
267 return Err(AccountProofError::InconsistentAccountCommitment);
268 }
269 if account_header.id() != account_witness.id() {
270 return Err(AccountProofError::InconsistentAccountId);
271 }
272 if code.commitment() != account_header.code_commitment() {
273 return Err(AccountProofError::InconsistentCodeCommitment);
274 }
275 }
276
277 Ok(Self { account_witness, state_headers })
278 }
279
280 pub fn account_id(&self) -> AccountId {
282 self.account_witness.id()
283 }
284
285 pub fn account_header(&self) -> Option<&AccountHeader> {
287 self.state_headers.as_ref().map(|headers| &headers.account_header)
288 }
289
290 pub fn storage_header(&self) -> Option<&AccountStorageHeader> {
292 self.state_headers.as_ref().map(|headers| &headers.storage_header)
293 }
294
295 pub fn account_code(&self) -> Option<&AccountCode> {
297 self.state_headers.as_ref().map(|headers| &headers.code)
298 }
299
300 pub fn code_commitment(&self) -> Option<Digest> {
302 self.account_code().map(AccountCode::commitment)
303 }
304
305 pub fn account_commitment(&self) -> Digest {
307 self.account_witness.state_commitment()
308 }
309
310 pub fn account_witness(&self) -> &AccountWitness {
311 &self.account_witness
312 }
313
314 pub fn merkle_proof(&self) -> &MerklePath {
316 self.account_witness.path()
317 }
318
319 pub fn into_parts(self) -> (AccountWitness, Option<StateHeaders>) {
321 (self.account_witness, self.state_headers)
322 }
323}
324
325impl TryFrom<ProtoAccountWitness> for AccountWitness {
329 type Error = RpcError;
330
331 fn try_from(account_witness: ProtoAccountWitness) -> Result<Self, Self::Error> {
332 let state_commitment = account_witness
333 .commitment
334 .ok_or(RpcError::ExpectedDataMissing(String::from("AccountWitness.StateCommitment")))?
335 .try_into()?;
336 let merkle_path = account_witness
337 .path
338 .ok_or(RpcError::ExpectedDataMissing(String::from("AccountWitness.MerklePath")))?
339 .try_into()?;
340 let account_id = account_witness
341 .witness_id
342 .ok_or(RpcError::ExpectedDataMissing(String::from("AccountWitness.WitnessId")))?
343 .try_into()?;
344
345 let witness = AccountWitness::new(account_id, state_commitment, merkle_path).unwrap();
346 Ok(witness)
347 }
348}
349
350pub type StorageSlotIndex = u8;
354pub type StorageMapKey = Digest;
355
356#[derive(Clone, Debug, Default, Eq, PartialEq)]
359pub struct AccountStorageRequirements(BTreeMap<StorageSlotIndex, Vec<StorageMapKey>>);
360
361impl AccountStorageRequirements {
362 pub fn new<'a>(
363 slots_and_keys: impl IntoIterator<
364 Item = (StorageSlotIndex, impl IntoIterator<Item = &'a StorageMapKey>),
365 >,
366 ) -> Self {
367 let map = slots_and_keys
368 .into_iter()
369 .map(|(slot_index, keys_iter)| {
370 let keys_vec: Vec<StorageMapKey> = keys_iter.into_iter().copied().collect();
371 (slot_index, keys_vec)
372 })
373 .collect();
374
375 AccountStorageRequirements(map)
376 }
377
378 pub fn inner(&self) -> &BTreeMap<StorageSlotIndex, Vec<StorageMapKey>> {
379 &self.0
380 }
381}
382
383impl From<AccountStorageRequirements> for Vec<get_account_proofs_request::StorageRequest> {
384 fn from(value: AccountStorageRequirements) -> Vec<get_account_proofs_request::StorageRequest> {
385 let mut requests = Vec::with_capacity(value.0.len());
386 for (slot_index, map_keys) in value.0 {
387 requests.push(get_account_proofs_request::StorageRequest {
388 storage_slot_index: u32::from(slot_index),
389 map_keys: map_keys
390 .into_iter()
391 .map(crate::rpc::generated::digest::Digest::from)
392 .collect(),
393 });
394 }
395 requests
396 }
397}
398
399impl Serializable for AccountStorageRequirements {
400 fn write_into<W: miden_tx::utils::ByteWriter>(&self, target: &mut W) {
401 target.write(&self.0);
402 }
403}
404
405impl Deserializable for AccountStorageRequirements {
406 fn read_from<R: miden_tx::utils::ByteReader>(
407 source: &mut R,
408 ) -> Result<Self, miden_tx::utils::DeserializationError> {
409 Ok(AccountStorageRequirements(source.read()?))
410 }
411}
412
413#[derive(Debug, Error)]
417pub enum AccountProofError {
418 #[error(
419 "the received account commitment doesn't match the received account header's commitment"
420 )]
421 InconsistentAccountCommitment,
422 #[error("the received account id doesn't match the received account header's id")]
423 InconsistentAccountId,
424 #[error(
425 "the received code commitment doesn't match the received account header's code commitment"
426 )]
427 InconsistentCodeCommitment,
428}