-
Notifications
You must be signed in to change notification settings - Fork 87
refactor: [4/4] unify get_account_details and get_account_proof[s] into get_account
#1385
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: bernhard-partial-storage-map-queries
Are you sure you want to change the base?
Changes from 7 commits
58ed218
7a0eab4
fd260ad
2dd3f20
687a4a1
f000d5d
d2211e3
84f3bb2
5ed7cb2
4ecb76c
badee2d
890191f
b085793
39da912
ba9ed1b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: not for this PR, but I'd try to break up this file into a couple of smaller files. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -32,7 +32,6 @@ use miden_objects::note::{ | |
| NoteType, | ||
| }; | ||
| use miden_objects::transaction::{InputNotes, PartialBlockchain, TransactionArgs}; | ||
| use miden_objects::utils::Deserializable; | ||
| use miden_objects::{Felt, Word, ZERO}; | ||
| use miden_tx::auth::BasicAuthenticator; | ||
| use miden_tx::utils::Serializable; | ||
|
|
@@ -90,45 +89,227 @@ async fn fetch_counter_value( | |
| account_id: AccountId, | ||
| ) -> Result<Option<u64>> { | ||
| let id_bytes: [u8; 15] = account_id.into(); | ||
| let req = miden_node_proto::generated::account::AccountId { id: id_bytes.to_vec() }; | ||
| let resp = rpc_client.get_account_details(req).await?.into_inner(); | ||
| if let Some(raw) = resp.details { | ||
| let account = Account::read_from_bytes(&raw) | ||
| .map_err(|e| anyhow::anyhow!("failed to deserialize account details: {e}"))?; | ||
| let account_id_proto = | ||
| miden_node_proto::generated::account::AccountId { id: id_bytes.to_vec() }; | ||
|
|
||
| // Request account details with storage header (but no storage maps needed) | ||
| let request = miden_node_proto::generated::rpc::AccountRequest { | ||
| account_id: Some(account_id_proto), | ||
| block_num: None, | ||
| details: Some(miden_node_proto::generated::rpc::account_request::AccountDetailRequest { | ||
| code_commitment: None, | ||
| asset_vault_commitment: None, | ||
| storage_maps: vec![], | ||
| }), | ||
| }; | ||
drahnr marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| let storage_slot = account.storage().slots().first().expect("storage slot is always value"); | ||
| let word = storage_slot.value(); | ||
| let value = word.as_elements().last().expect("a word is always 4 elements").as_int(); | ||
| let resp = rpc_client.get_account(request).await?.into_inner(); | ||
|
|
||
| Ok(Some(value)) | ||
| } else { | ||
| Ok(None) | ||
| // Extract the counter value from the storage header | ||
| if let Some(details) = resp.details { | ||
| let storage_details = details.storage_details.context("missing storage details")?; | ||
|
|
||
| let storage_header = storage_details.header.context("missing storage header")?; | ||
|
|
||
| // The counter value is in the first storage slot (index 0) | ||
| let first_slot = storage_header.slots.first().context("no storage slots found")?; | ||
|
|
||
| // For value slots, the slot data is the value itself (not a hash commitment) | ||
| let slot_value: Word = first_slot | ||
| .commitment | ||
| .as_ref() | ||
| .context("missing storage slot value")? | ||
| .try_into() | ||
| .context("failed to convert slot value to word")?; | ||
|
|
||
| // The counter value is stored in the last element of the Word | ||
| let value = slot_value.as_elements().last().expect("word has 4 elements").as_int(); | ||
|
|
||
| return Ok(Some(value)); | ||
| } | ||
|
|
||
| Ok(None) | ||
| } | ||
|
|
||
| /// Fetch an account from RPC and reconstruct the full Account. | ||
| /// | ||
| /// Uses dummy commitments to force the server to return all data (code, vault, storage header). | ||
| /// Storage map slots will have empty maps since we don't request map entries. | ||
| async fn fetch_wallet_account( | ||
| rpc_client: &mut RpcClient, | ||
| account_id: AccountId, | ||
| ) -> Result<Option<Account>> { | ||
| use miden_objects::account::AccountCode; | ||
| use miden_objects::asset::AssetVault; | ||
| use miden_objects::utils::Deserializable; | ||
|
|
||
| let id_bytes: [u8; 15] = account_id.into(); | ||
| let req = miden_node_proto::generated::account::AccountId { id: id_bytes.to_vec() }; | ||
| let resp = rpc_client.get_account_details(req).await; | ||
| let account_id_proto = | ||
| miden_node_proto::generated::account::AccountId { id: id_bytes.to_vec() }; | ||
|
|
||
| // Pass dummy commitments to force server to return code and vault | ||
| // (server returns data only if commitment differs from what we send) | ||
| let dummy_commitment: miden_node_proto::generated::primitives::Digest = Word::default().into(); | ||
|
|
||
| let request = miden_node_proto::generated::rpc::AccountRequest { | ||
| account_id: Some(account_id_proto), | ||
| block_num: None, | ||
| details: Some(miden_node_proto::generated::rpc::account_request::AccountDetailRequest { | ||
| code_commitment: Some(dummy_commitment), | ||
| asset_vault_commitment: Some(dummy_commitment), | ||
| storage_maps: vec![], | ||
| }), | ||
| }; | ||
|
|
||
| // If the RPC call fails, return None | ||
| if resp.is_err() { | ||
| return Ok(None); | ||
| } | ||
| let response = match rpc_client.get_account(request).await { | ||
| Ok(response) => response.into_inner(), | ||
| Err(e) => { | ||
| warn!(account.id = %account_id, err = %e, "failed to fetch wallet account via RPC"); | ||
| return Ok(None); | ||
| }, | ||
| }; | ||
|
|
||
| let Some(account_details) = resp.expect("Previously checked for error").into_inner().details | ||
| else { | ||
| let Some(details) = response.details else { | ||
| if response.witness.is_some() { | ||
| info!( | ||
| account.id = %account_id, | ||
| "account found on-chain but cannot reconstruct full account from RPC response" | ||
| ); | ||
| } | ||
| return Ok(None); | ||
| }; | ||
| let account = Account::read_from_bytes(&account_details) | ||
| .map_err(|e| anyhow::anyhow!("failed to deserialize account details: {e}"))?; | ||
|
|
||
| let header = details.header.context("missing account header")?; | ||
| let nonce: u64 = header.nonce; | ||
|
|
||
| let code = details | ||
| .code | ||
| .map(|code_bytes| AccountCode::read_from_bytes(&code_bytes)) | ||
| .transpose() | ||
| .context("failed to deserialize account code")? | ||
| .context("server did not return account code")?; | ||
|
|
||
| let vault = match details.vault_details { | ||
| Some(vault_details) if vault_details.too_many_assets => { | ||
| anyhow::bail!("account {account_id} has too many assets, cannot fetch full account"); | ||
| }, | ||
| Some(vault_details) => { | ||
| let assets: Vec<miden_objects::asset::Asset> = vault_details | ||
| .assets | ||
| .into_iter() | ||
| .map(TryInto::try_into) | ||
| .collect::<Result<_, _>>() | ||
| .context("failed to convert assets")?; | ||
| AssetVault::new(&assets).context("failed to create vault")? | ||
| }, | ||
| None => anyhow::bail!("server did not return asset vault for account {account_id}"), | ||
| }; | ||
|
|
||
| let storage_details = details.storage_details.context("missing storage details")?; | ||
| let storage = build_account_storage(account_id, storage_details)?; | ||
|
|
||
| let account = Account::new(account_id, vault, storage, code, Felt::new(nonce), None) | ||
| .context("failed to create account")?; | ||
|
|
||
| // Sanity check: verify reconstructed account matches header commitments | ||
| let expected_code_commitment: Word = header | ||
| .code_commitment | ||
| .context("missing code commitment in header")? | ||
| .try_into() | ||
| .context("invalid code commitment")?; | ||
| let expected_vault_root: Word = header | ||
| .vault_root | ||
| .context("missing vault root in header")? | ||
| .try_into() | ||
| .context("invalid vault root")?; | ||
| let expected_storage_commitment: Word = header | ||
| .storage_commitment | ||
| .context("missing storage commitment in header")? | ||
| .try_into() | ||
| .context("invalid storage commitment")?; | ||
|
|
||
| anyhow::ensure!( | ||
| account.code().commitment() == expected_code_commitment, | ||
| "code commitment mismatch: rebuilt={:?}, expected={:?}", | ||
| account.code().commitment(), | ||
| expected_code_commitment | ||
| ); | ||
| anyhow::ensure!( | ||
| account.vault().root() == expected_vault_root, | ||
| "vault root mismatch: rebuilt={:?}, expected={:?}", | ||
| account.vault().root(), | ||
| expected_vault_root | ||
| ); | ||
| anyhow::ensure!( | ||
| account.storage().to_commitment() == expected_storage_commitment, | ||
| "storage commitment mismatch: rebuilt={:?}, expected={:?}", | ||
| account.storage().to_commitment(), | ||
| expected_storage_commitment | ||
| ); | ||
|
|
||
| info!(account.id = %account_id, "fetched wallet account from RPC"); | ||
| Ok(Some(account)) | ||
| } | ||
|
|
||
| /// Build account storage from the storage details returned by the server. | ||
| fn build_account_storage( | ||
| account_id: AccountId, | ||
| storage_details: miden_node_proto::generated::rpc::AccountStorageDetails, | ||
| ) -> Result<miden_objects::account::AccountStorage> { | ||
| use miden_objects::account::{AccountStorage, StorageSlot}; | ||
|
|
||
| let storage_header = storage_details.header.context("missing storage header")?; | ||
|
|
||
| // Build a map of slot_name -> map entries from the response | ||
| let map_entries: std::collections::HashMap<String, Vec<(Word, Word)>> = storage_details | ||
| .map_details | ||
| .into_iter() | ||
| .filter_map(|map_detail| { | ||
| let entries = map_detail.entries?.entries; | ||
| let converted: Vec<(Word, Word)> = entries | ||
| .into_iter() | ||
| .filter_map(|e| { | ||
| let key: Word = e.key?.try_into().ok()?; | ||
| let value: Word = e.value?.try_into().ok()?; | ||
| Some((key, value)) | ||
| }) | ||
| .collect(); | ||
| Some((map_detail.slot_name, converted)) | ||
| }) | ||
| .collect(); | ||
drahnr marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| let mut slots = Vec::new(); | ||
| for slot in storage_header.slots { | ||
| let slot_name = miden_objects::account::StorageSlotName::new(slot.slot_name.clone()) | ||
| .context("invalid slot name")?; | ||
| let commitment: Word = slot | ||
| .commitment | ||
| .context("missing slot commitment")? | ||
| .try_into() | ||
| .context("invalid slot commitment")?; | ||
|
|
||
| // slot_type: 0 = Value, 1 = Map | ||
| let storage_slot = if slot.slot_type == 0 { | ||
| StorageSlot::with_value(slot_name, commitment) | ||
| } else { | ||
| let entries = map_entries.get(&slot.slot_name).cloned().unwrap_or_default(); | ||
| if entries.is_empty() { | ||
| warn!( | ||
| account.id = %account_id, | ||
| slot_name = %slot.slot_name, | ||
| "storage map slot has no entries (not requested or empty)" | ||
| ); | ||
| } | ||
| let storage_map = miden_objects::account::StorageMap::with_entries(entries) | ||
| .context("failed to create storage map")?; | ||
| StorageSlot::with_map(slot_name, storage_map) | ||
|
||
| }; | ||
| slots.push(storage_slot); | ||
| } | ||
|
|
||
| AccountStorage::new(slots).context("failed to create account storage") | ||
| } | ||
|
|
||
| async fn setup_increment_task( | ||
| config: MonitorConfig, | ||
| rpc_client: &mut RpcClient, | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.