1use alloc::collections::BTreeMap;
2use alloc::vec::Vec;
3use core::fmt::{self, Debug, Display, Formatter};
4
5use miden_objects::Word;
6use miden_objects::account::{
7 Account,
8 AccountCode,
9 AccountHeader,
10 AccountId,
11 AccountStorageHeader,
12};
13use miden_objects::block::{AccountWitness, BlockNumber};
14use miden_objects::crypto::merkle::{MerklePath, SmtProof};
15use miden_tx::utils::{Deserializable, Serializable, ToHex};
16use thiserror::Error;
17
18use crate::rpc::RpcError;
19use crate::rpc::domain::MissingFieldHelper;
20use crate::rpc::errors::RpcConversionError;
21use crate::rpc::generated::{self as proto};
22
23pub enum FetchedAccount {
28 Private(AccountId, AccountUpdateSummary),
31 Public(Account, AccountUpdateSummary),
34}
35
36impl FetchedAccount {
37 pub fn account_id(&self) -> AccountId {
39 match self {
40 Self::Private(account_id, _) => *account_id,
41 Self::Public(account, _) => account.id(),
42 }
43 }
44
45 pub fn commitment(&self) -> Word {
47 match self {
48 Self::Private(_, summary) | Self::Public(_, summary) => summary.commitment,
49 }
50 }
51
52 pub fn account(&self) -> Option<&Account> {
54 match self {
55 Self::Private(..) => None,
56 Self::Public(account, _) => Some(account),
57 }
58 }
59}
60
61impl From<FetchedAccount> for Option<Account> {
62 fn from(acc: FetchedAccount) -> Self {
63 match acc {
64 FetchedAccount::Private(..) => None,
65 FetchedAccount::Public(account, _) => Some(account),
66 }
67 }
68}
69
70pub struct AccountUpdateSummary {
75 pub commitment: Word,
77 pub last_block_num: u32,
79}
80
81impl AccountUpdateSummary {
82 pub fn new(commitment: Word, last_block_num: u32) -> Self {
84 Self { commitment, last_block_num }
85 }
86}
87
88impl Display for proto::account::AccountId {
92 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
93 f.write_fmt(format_args!("0x{}", self.id.to_hex()))
94 }
95}
96
97impl Debug for proto::account::AccountId {
98 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
99 Display::fmt(self, f)
100 }
101}
102
103impl From<AccountId> for proto::account::AccountId {
107 fn from(account_id: AccountId) -> Self {
108 Self { id: account_id.to_bytes() }
109 }
110}
111
112impl TryFrom<proto::account::AccountId> for AccountId {
116 type Error = RpcConversionError;
117
118 fn try_from(account_id: proto::account::AccountId) -> Result<Self, Self::Error> {
119 AccountId::read_from_bytes(&account_id.id).map_err(|_| RpcConversionError::NotAValidFelt)
120 }
121}
122
123impl proto::account::AccountHeader {
127 #[cfg(any(feature = "tonic", feature = "web-tonic"))]
128 pub fn into_domain(self, account_id: AccountId) -> Result<AccountHeader, crate::rpc::RpcError> {
129 use miden_objects::Felt;
130
131 use crate::rpc::domain::MissingFieldHelper;
132
133 let proto::account::AccountHeader {
134 nonce,
135 vault_root,
136 storage_commitment,
137 code_commitment,
138 } = self;
139 let vault_root = vault_root
140 .ok_or(proto::account::AccountHeader::missing_field(stringify!(vault_root)))?
141 .try_into()?;
142 let storage_commitment = storage_commitment
143 .ok_or(proto::account::AccountHeader::missing_field(stringify!(storage_commitment)))?
144 .try_into()?;
145 let code_commitment = code_commitment
146 .ok_or(proto::account::AccountHeader::missing_field(stringify!(code_commitment)))?
147 .try_into()?;
148
149 Ok(AccountHeader::new(
150 account_id,
151 Felt::new(nonce),
152 vault_root,
153 storage_commitment,
154 code_commitment,
155 ))
156 }
157}
158
159impl proto::rpc_store::account_proofs::account_proof::AccountStateHeader {
163 #[cfg(any(feature = "tonic", feature = "web-tonic"))]
173 pub fn into_domain(
174 self,
175 account_id: AccountId,
176 known_account_codes: &BTreeMap<Word, AccountCode>,
177 ) -> Result<StateHeaders, crate::rpc::RpcError> {
178 use crate::rpc::RpcError;
179 use crate::rpc::domain::MissingFieldHelper;
180 use crate::rpc::generated::rpc_store::account_proofs::account_proof::account_state_header::StorageSlotMapProof;
181
182 let proto::rpc_store::account_proofs::account_proof::AccountStateHeader {
183 header,
184 storage_header,
185 account_code,
186 storage_maps,
187 } = self;
188 let account_header = header
189 .ok_or(
190 proto::rpc_store::account_proofs::account_proof::AccountStateHeader::missing_field(
191 stringify!(header),
192 ),
193 )?
194 .into_domain(account_id)?;
195
196 let storage_header = AccountStorageHeader::read_from_bytes(&storage_header)?;
197
198 let code = {
202 let received_code =
203 account_code.map(|c| AccountCode::read_from_bytes(&c)).transpose()?;
204 match received_code {
205 Some(code) => code,
206 None => known_account_codes
207 .get(&account_header.code_commitment())
208 .ok_or(RpcError::InvalidResponse(
209 "Account code was not provided, but the response did not contain it either"
210 .into(),
211 ))?
212 .clone(),
213 }
214 };
215
216 let mut storage_slot_proofs: BTreeMap<u8, Vec<SmtProof>> = BTreeMap::new();
218 for StorageSlotMapProof { storage_slot, smt_proof } in storage_maps {
219 let proof = SmtProof::read_from_bytes(&smt_proof)?;
220 match storage_slot_proofs
221 .get_mut(&(u8::try_from(storage_slot).expect("there are no more than 256 slots")))
222 {
223 Some(list) => list.push(proof),
224 None => {
225 _ = storage_slot_proofs.insert(
226 u8::try_from(storage_slot).expect("only 256 storage slots"),
227 vec![proof],
228 );
229 },
230 }
231 }
232
233 Ok(StateHeaders {
234 account_header,
235 storage_header,
236 code,
237 storage_slots: storage_slot_proofs,
238 })
239 }
240}
241
242pub type AccountProofs = (BlockNumber, Vec<AccountProof>);
247
248#[derive(Clone, Debug)]
250pub struct StateHeaders {
251 pub account_header: AccountHeader,
253 pub storage_header: AccountStorageHeader,
254 pub code: AccountCode,
255 pub storage_slots: BTreeMap<StorageSlotIndex, Vec<SmtProof>>,
256}
257
258#[derive(Clone, Debug)]
260pub struct AccountProof {
261 account_witness: AccountWitness,
263 state_headers: Option<StateHeaders>,
265}
266
267impl AccountProof {
268 pub fn new(
270 account_witness: AccountWitness,
271 state_headers: Option<StateHeaders>,
272 ) -> Result<Self, AccountProofError> {
273 if let Some(StateHeaders {
274 account_header, storage_header: _, code, ..
275 }) = &state_headers
276 {
277 if account_header.commitment() != account_witness.state_commitment() {
278 return Err(AccountProofError::InconsistentAccountCommitment);
279 }
280 if account_header.id() != account_witness.id() {
281 return Err(AccountProofError::InconsistentAccountId);
282 }
283 if code.commitment() != account_header.code_commitment() {
284 return Err(AccountProofError::InconsistentCodeCommitment);
285 }
286 }
287
288 Ok(Self { account_witness, state_headers })
289 }
290
291 pub fn account_id(&self) -> AccountId {
293 self.account_witness.id()
294 }
295
296 pub fn account_header(&self) -> Option<&AccountHeader> {
298 self.state_headers.as_ref().map(|headers| &headers.account_header)
299 }
300
301 pub fn storage_header(&self) -> Option<&AccountStorageHeader> {
303 self.state_headers.as_ref().map(|headers| &headers.storage_header)
304 }
305
306 pub fn account_code(&self) -> Option<&AccountCode> {
308 self.state_headers.as_ref().map(|headers| &headers.code)
309 }
310
311 pub fn code_commitment(&self) -> Option<Word> {
313 self.account_code().map(AccountCode::commitment)
314 }
315
316 pub fn account_commitment(&self) -> Word {
318 self.account_witness.state_commitment()
319 }
320
321 pub fn account_witness(&self) -> &AccountWitness {
322 &self.account_witness
323 }
324
325 pub fn merkle_proof(&self) -> &MerklePath {
327 self.account_witness.path()
328 }
329
330 pub fn into_parts(self) -> (AccountWitness, Option<StateHeaders>) {
332 (self.account_witness, self.state_headers)
333 }
334}
335
336impl TryFrom<proto::account::AccountWitness> for AccountWitness {
340 type Error = RpcError;
341
342 fn try_from(account_witness: proto::account::AccountWitness) -> Result<Self, Self::Error> {
343 let state_commitment = account_witness
344 .commitment
345 .ok_or(proto::account::AccountWitness::missing_field(stringify!(state_commitment)))?
346 .try_into()?;
347 let merkle_path = account_witness
348 .path
349 .ok_or(proto::account::AccountWitness::missing_field(stringify!(merkle_path)))?
350 .try_into()?;
351 let account_id = account_witness
352 .witness_id
353 .ok_or(proto::account::AccountWitness::missing_field(stringify!(witness_id)))?
354 .try_into()?;
355
356 let witness = AccountWitness::new(account_id, state_commitment, merkle_path).unwrap();
357 Ok(witness)
358 }
359}
360
361pub type StorageSlotIndex = u8;
365pub type StorageMapKey = Word;
366
367#[derive(Clone, Debug, Default, Eq, PartialEq)]
370pub struct AccountStorageRequirements(BTreeMap<StorageSlotIndex, Vec<StorageMapKey>>);
371
372impl AccountStorageRequirements {
373 pub fn new<'a>(
374 slots_and_keys: impl IntoIterator<
375 Item = (StorageSlotIndex, impl IntoIterator<Item = &'a StorageMapKey>),
376 >,
377 ) -> Self {
378 let map = slots_and_keys
379 .into_iter()
380 .map(|(slot_index, keys_iter)| {
381 let keys_vec: Vec<StorageMapKey> = keys_iter.into_iter().copied().collect();
382 (slot_index, keys_vec)
383 })
384 .collect();
385
386 AccountStorageRequirements(map)
387 }
388
389 pub fn inner(&self) -> &BTreeMap<StorageSlotIndex, Vec<StorageMapKey>> {
390 &self.0
391 }
392}
393
394impl From<AccountStorageRequirements>
395 for Vec<proto::rpc_store::account_proofs_request::account_request::StorageRequest>
396{
397 fn from(
398 value: AccountStorageRequirements,
399 ) -> Vec<proto::rpc_store::account_proofs_request::account_request::StorageRequest> {
400 let mut requests = Vec::with_capacity(value.0.len());
401 for (slot_index, map_keys) in value.0 {
402 requests.push(
403 proto::rpc_store::account_proofs_request::account_request::StorageRequest {
404 storage_slot_index: u32::from(slot_index),
405 map_keys: map_keys
406 .into_iter()
407 .map(crate::rpc::generated::primitives::Digest::from)
408 .collect(),
409 },
410 );
411 }
412 requests
413 }
414}
415
416impl Serializable for AccountStorageRequirements {
417 fn write_into<W: miden_tx::utils::ByteWriter>(&self, target: &mut W) {
418 target.write(&self.0);
419 }
420}
421
422impl Deserializable for AccountStorageRequirements {
423 fn read_from<R: miden_tx::utils::ByteReader>(
424 source: &mut R,
425 ) -> Result<Self, miden_tx::utils::DeserializationError> {
426 Ok(AccountStorageRequirements(source.read()?))
427 }
428}
429
430#[derive(Debug, Error)]
434pub enum AccountProofError {
435 #[error(
436 "the received account commitment doesn't match the received account header's commitment"
437 )]
438 InconsistentAccountCommitment,
439 #[error("the received account id doesn't match the received account header's id")]
440 InconsistentAccountId,
441 #[error(
442 "the received code commitment doesn't match the received account header's code commitment"
443 )]
444 InconsistentCodeCommitment,
445}