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
63impl From<FetchedAccount> for Option<Account> {
64 fn from(acc: FetchedAccount) -> Self {
65 match acc {
66 FetchedAccount::Private(..) => None,
67 FetchedAccount::Public(account, _) => Some(account),
68 }
69 }
70}
71
72pub struct AccountUpdateSummary {
77 pub commitment: Digest,
79 pub last_block_num: u32,
81}
82
83impl AccountUpdateSummary {
84 pub fn new(commitment: Digest, last_block_num: u32) -> Self {
86 Self { commitment, last_block_num }
87 }
88}
89
90impl Display for ProtoAccountId {
94 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
95 f.write_fmt(format_args!("0x{}", self.id.to_hex()))
96 }
97}
98
99impl Debug for ProtoAccountId {
100 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
101 Display::fmt(self, f)
102 }
103}
104
105impl From<AccountId> for ProtoAccountId {
109 fn from(account_id: AccountId) -> Self {
110 Self { id: account_id.to_bytes() }
111 }
112}
113
114impl TryFrom<ProtoAccountId> for AccountId {
118 type Error = RpcConversionError;
119
120 fn try_from(account_id: ProtoAccountId) -> Result<Self, Self::Error> {
121 AccountId::read_from_bytes(&account_id.id).map_err(|_| RpcConversionError::NotAValidFelt)
122 }
123}
124
125impl ProtoAccountHeader {
129 #[cfg(any(feature = "tonic", feature = "web-tonic"))]
130 pub fn into_domain(self, account_id: AccountId) -> Result<AccountHeader, crate::rpc::RpcError> {
131 use alloc::string::String;
132
133 use miden_objects::Felt;
134
135 use crate::rpc::RpcError;
136
137 let ProtoAccountHeader {
138 nonce,
139 vault_root,
140 storage_commitment,
141 code_commitment,
142 } = self;
143 let vault_root = vault_root
144 .ok_or(RpcError::ExpectedDataMissing(String::from("AccountHeader.VaultRoot")))?
145 .try_into()?;
146 let storage_commitment = storage_commitment
147 .ok_or(RpcError::ExpectedDataMissing(String::from("AccountHeader.StorageCommitment")))?
148 .try_into()?;
149 let code_commitment = code_commitment
150 .ok_or(RpcError::ExpectedDataMissing(String::from("AccountHeader.CodeCommitment")))?
151 .try_into()?;
152
153 Ok(AccountHeader::new(
154 account_id,
155 Felt::new(nonce),
156 vault_root,
157 storage_commitment,
158 code_commitment,
159 ))
160 }
161}
162
163impl ProtoAccountStateHeader {
167 #[cfg(any(feature = "tonic", feature = "web-tonic"))]
177 pub fn into_domain(
178 self,
179 account_id: AccountId,
180 known_account_codes: &BTreeMap<Digest, AccountCode>,
181 ) -> Result<StateHeaders, crate::rpc::RpcError> {
182 use crate::rpc::{RpcError, generated::responses::StorageSlotMapProof};
183
184 let ProtoAccountStateHeader {
185 header,
186 storage_header,
187 account_code,
188 storage_maps,
189 } = self;
190 let account_header = header
191 .ok_or(RpcError::ExpectedDataMissing("Account.StateHeader".into()))?
192 .into_domain(account_id)?;
193
194 let storage_header = AccountStorageHeader::read_from_bytes(&storage_header)?;
195
196 let code = {
200 let received_code =
201 account_code.map(|c| AccountCode::read_from_bytes(&c)).transpose()?;
202 match received_code {
203 Some(code) => code,
204 None => known_account_codes
205 .get(&account_header.code_commitment())
206 .ok_or(RpcError::InvalidResponse(
207 "Account code was not provided, but the response did not contain it either"
208 .into(),
209 ))?
210 .clone(),
211 }
212 };
213
214 let mut storage_slot_proofs: BTreeMap<u8, Vec<SmtProof>> = BTreeMap::new();
216 for StorageSlotMapProof { storage_slot, smt_proof } in storage_maps {
217 let proof = SmtProof::read_from_bytes(&smt_proof)?;
218 match storage_slot_proofs
219 .get_mut(&(u8::try_from(storage_slot).expect("there are no more than 256 slots")))
220 {
221 Some(list) => list.push(proof),
222 None => {
223 _ = storage_slot_proofs.insert(
224 u8::try_from(storage_slot).expect("only 256 storage slots"),
225 vec![proof],
226 );
227 },
228 }
229 }
230
231 Ok(StateHeaders {
232 account_header,
233 storage_header,
234 code,
235 storage_slots: storage_slot_proofs,
236 })
237 }
238}
239
240pub type AccountProofs = (BlockNumber, Vec<AccountProof>);
245
246#[derive(Clone, Debug)]
248pub struct StateHeaders {
249 pub account_header: AccountHeader,
251 pub storage_header: AccountStorageHeader,
252 pub code: AccountCode,
253 pub storage_slots: BTreeMap<StorageSlotIndex, Vec<SmtProof>>,
254}
255
256#[derive(Clone, Debug)]
258pub struct AccountProof {
259 account_witness: AccountWitness,
261 state_headers: Option<StateHeaders>,
263}
264
265impl AccountProof {
266 pub fn new(
268 account_witness: AccountWitness,
269 state_headers: Option<StateHeaders>,
270 ) -> Result<Self, AccountProofError> {
271 if let Some(StateHeaders {
272 account_header, storage_header: _, code, ..
273 }) = &state_headers
274 {
275 if account_header.commitment() != account_witness.state_commitment() {
276 return Err(AccountProofError::InconsistentAccountCommitment);
277 }
278 if account_header.id() != account_witness.id() {
279 return Err(AccountProofError::InconsistentAccountId);
280 }
281 if code.commitment() != account_header.code_commitment() {
282 return Err(AccountProofError::InconsistentCodeCommitment);
283 }
284 }
285
286 Ok(Self { account_witness, state_headers })
287 }
288
289 pub fn account_id(&self) -> AccountId {
291 self.account_witness.id()
292 }
293
294 pub fn account_header(&self) -> Option<&AccountHeader> {
296 self.state_headers.as_ref().map(|headers| &headers.account_header)
297 }
298
299 pub fn storage_header(&self) -> Option<&AccountStorageHeader> {
301 self.state_headers.as_ref().map(|headers| &headers.storage_header)
302 }
303
304 pub fn account_code(&self) -> Option<&AccountCode> {
306 self.state_headers.as_ref().map(|headers| &headers.code)
307 }
308
309 pub fn code_commitment(&self) -> Option<Digest> {
311 self.account_code().map(AccountCode::commitment)
312 }
313
314 pub fn account_commitment(&self) -> Digest {
316 self.account_witness.state_commitment()
317 }
318
319 pub fn account_witness(&self) -> &AccountWitness {
320 &self.account_witness
321 }
322
323 pub fn merkle_proof(&self) -> &MerklePath {
325 self.account_witness.path()
326 }
327
328 pub fn into_parts(self) -> (AccountWitness, Option<StateHeaders>) {
330 (self.account_witness, self.state_headers)
331 }
332}
333
334impl TryFrom<ProtoAccountWitness> for AccountWitness {
338 type Error = RpcError;
339
340 fn try_from(account_witness: ProtoAccountWitness) -> Result<Self, Self::Error> {
341 let state_commitment = account_witness
342 .commitment
343 .ok_or(RpcError::ExpectedDataMissing(String::from("AccountWitness.StateCommitment")))?
344 .try_into()?;
345 let merkle_path = account_witness
346 .path
347 .ok_or(RpcError::ExpectedDataMissing(String::from("AccountWitness.MerklePath")))?
348 .try_into()?;
349 let account_id = account_witness
350 .witness_id
351 .ok_or(RpcError::ExpectedDataMissing(String::from("AccountWitness.WitnessId")))?
352 .try_into()?;
353
354 let witness = AccountWitness::new(account_id, state_commitment, merkle_path).unwrap();
355 Ok(witness)
356 }
357}
358
359pub type StorageSlotIndex = u8;
363pub type StorageMapKey = Digest;
364
365#[derive(Clone, Debug, Default, Eq, PartialEq)]
368pub struct AccountStorageRequirements(BTreeMap<StorageSlotIndex, Vec<StorageMapKey>>);
369
370impl AccountStorageRequirements {
371 pub fn new<'a>(
372 slots_and_keys: impl IntoIterator<
373 Item = (StorageSlotIndex, impl IntoIterator<Item = &'a StorageMapKey>),
374 >,
375 ) -> Self {
376 let map = slots_and_keys
377 .into_iter()
378 .map(|(slot_index, keys_iter)| {
379 let keys_vec: Vec<StorageMapKey> = keys_iter.into_iter().copied().collect();
380 (slot_index, keys_vec)
381 })
382 .collect();
383
384 AccountStorageRequirements(map)
385 }
386
387 pub fn inner(&self) -> &BTreeMap<StorageSlotIndex, Vec<StorageMapKey>> {
388 &self.0
389 }
390}
391
392impl From<AccountStorageRequirements> for Vec<get_account_proofs_request::StorageRequest> {
393 fn from(value: AccountStorageRequirements) -> Vec<get_account_proofs_request::StorageRequest> {
394 let mut requests = Vec::with_capacity(value.0.len());
395 for (slot_index, map_keys) in value.0 {
396 requests.push(get_account_proofs_request::StorageRequest {
397 storage_slot_index: u32::from(slot_index),
398 map_keys: map_keys
399 .into_iter()
400 .map(crate::rpc::generated::digest::Digest::from)
401 .collect(),
402 });
403 }
404 requests
405 }
406}
407
408impl Serializable for AccountStorageRequirements {
409 fn write_into<W: miden_tx::utils::ByteWriter>(&self, target: &mut W) {
410 target.write(&self.0);
411 }
412}
413
414impl Deserializable for AccountStorageRequirements {
415 fn read_from<R: miden_tx::utils::ByteReader>(
416 source: &mut R,
417 ) -> Result<Self, miden_tx::utils::DeserializationError> {
418 Ok(AccountStorageRequirements(source.read()?))
419 }
420}
421
422#[derive(Debug, Error)]
426pub enum AccountProofError {
427 #[error(
428 "the received account commitment doesn't match the received account header's commitment"
429 )]
430 InconsistentAccountCommitment,
431 #[error("the received account id doesn't match the received account header's id")]
432 InconsistentAccountId,
433 #[error(
434 "the received code commitment doesn't match the received account header's code commitment"
435 )]
436 InconsistentCodeCommitment,
437}