diff --git a/Cargo.lock b/Cargo.lock index e911e94d0..c55f178fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2490,7 +2490,7 @@ dependencies = [ [[package]] name = "miden-block-prover" version = "0.13.0" -source = "git+https://github.com/0xMiden/miden-base.git?branch=next#eb0c396d48506c30c3fdc859ddaabbb8bfaf6b00" +source = "git+https://github.com/0xMiden/miden-base.git?branch=sergerad-tx-inputs-foreign-acc-inputs#0b46cc5ca19b8257446108c38ae2e79188aec83c" dependencies = [ "miden-protocol", "thiserror 2.0.17", @@ -2980,7 +2980,7 @@ dependencies = [ [[package]] name = "miden-protocol" version = "0.13.0" -source = "git+https://github.com/0xMiden/miden-base.git?branch=next#eb0c396d48506c30c3fdc859ddaabbb8bfaf6b00" +source = "git+https://github.com/0xMiden/miden-base.git?branch=sergerad-tx-inputs-foreign-acc-inputs#0b46cc5ca19b8257446108c38ae2e79188aec83c" dependencies = [ "bech32", "fs-err", @@ -3010,7 +3010,7 @@ dependencies = [ [[package]] name = "miden-protocol-macros" version = "0.13.0" -source = "git+https://github.com/0xMiden/miden-base.git?branch=next#eb0c396d48506c30c3fdc859ddaabbb8bfaf6b00" +source = "git+https://github.com/0xMiden/miden-base.git?branch=sergerad-tx-inputs-foreign-acc-inputs#0b46cc5ca19b8257446108c38ae2e79188aec83c" dependencies = [ "proc-macro2", "quote", @@ -3100,7 +3100,7 @@ dependencies = [ [[package]] name = "miden-standards" version = "0.13.0" -source = "git+https://github.com/0xMiden/miden-base.git?branch=next#eb0c396d48506c30c3fdc859ddaabbb8bfaf6b00" +source = "git+https://github.com/0xMiden/miden-base.git?branch=sergerad-tx-inputs-foreign-acc-inputs#0b46cc5ca19b8257446108c38ae2e79188aec83c" dependencies = [ "fs-err", "miden-assembly", @@ -3117,7 +3117,7 @@ dependencies = [ [[package]] name = "miden-testing" version = "0.13.0" -source = "git+https://github.com/0xMiden/miden-base.git?branch=next#eb0c396d48506c30c3fdc859ddaabbb8bfaf6b00" +source = "git+https://github.com/0xMiden/miden-base.git?branch=sergerad-tx-inputs-foreign-acc-inputs#0b46cc5ca19b8257446108c38ae2e79188aec83c" dependencies = [ "anyhow", "itertools 0.14.0", @@ -3135,7 +3135,7 @@ dependencies = [ [[package]] name = "miden-tx" version = "0.13.0" -source = "git+https://github.com/0xMiden/miden-base.git?branch=next#eb0c396d48506c30c3fdc859ddaabbb8bfaf6b00" +source = "git+https://github.com/0xMiden/miden-base.git?branch=sergerad-tx-inputs-foreign-acc-inputs#0b46cc5ca19b8257446108c38ae2e79188aec83c" dependencies = [ "miden-processor", "miden-protocol", @@ -3148,7 +3148,7 @@ dependencies = [ [[package]] name = "miden-tx-batch-prover" version = "0.13.0" -source = "git+https://github.com/0xMiden/miden-base.git?branch=next#eb0c396d48506c30c3fdc859ddaabbb8bfaf6b00" +source = "git+https://github.com/0xMiden/miden-base.git?branch=sergerad-tx-inputs-foreign-acc-inputs#0b46cc5ca19b8257446108c38ae2e79188aec83c" dependencies = [ "miden-protocol", "miden-tx", diff --git a/Cargo.toml b/Cargo.toml index 53e5182bb..d0b33a4fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,12 +49,12 @@ miden-node-validator = { path = "crates/validator", version = "0.13" } miden-remote-prover-client = { path = "crates/remote-prover-client", version = "0.13" } # miden-base aka protocol dependencies. These should be updated in sync. -miden-block-prover = { branch = "next", git = "https://github.com/0xMiden/miden-base.git" } -miden-protocol = { branch = "next", default-features = false, git = "https://github.com/0xMiden/miden-base.git" } -miden-standards = { branch = "next", git = "https://github.com/0xMiden/miden-base.git" } -miden-testing = { branch = "next", git = "https://github.com/0xMiden/miden-base.git" } -miden-tx = { branch = "next", default-features = false, git = "https://github.com/0xMiden/miden-base.git" } -miden-tx-batch-prover = { branch = "next", git = "https://github.com/0xMiden/miden-base.git" } +miden-block-prover = { branch = "sergerad-tx-inputs-foreign-acc-inputs", git = "https://github.com/0xMiden/miden-base.git" } +miden-protocol = { branch = "sergerad-tx-inputs-foreign-acc-inputs", default-features = false, git = "https://github.com/0xMiden/miden-base.git" } +miden-standards = { branch = "sergerad-tx-inputs-foreign-acc-inputs", git = "https://github.com/0xMiden/miden-base.git" } +miden-testing = { branch = "sergerad-tx-inputs-foreign-acc-inputs", git = "https://github.com/0xMiden/miden-base.git" } +miden-tx = { branch = "sergerad-tx-inputs-foreign-acc-inputs", default-features = false, git = "https://github.com/0xMiden/miden-base.git" } +miden-tx-batch-prover = { branch = "sergerad-tx-inputs-foreign-acc-inputs", git = "https://github.com/0xMiden/miden-base.git" } # Other miden dependencies. These should align with those expected by miden-base. miden-air = { features = ["std", "testing"], version = "0.20" } diff --git a/crates/ntx-builder/src/actor/execute.rs b/crates/ntx-builder/src/actor/execute.rs index 83c1d09c9..a13da38e5 100644 --- a/crates/ntx-builder/src/actor/execute.rs +++ b/crates/ntx-builder/src/actor/execute.rs @@ -6,10 +6,11 @@ use miden_protocol::account::{ Account, AccountId, PartialAccount, + StorageMap, StorageMapWitness, StorageSlotContent, }; -use miden_protocol::asset::{AssetVaultKey, AssetWitness}; +use miden_protocol::asset::{AssetVault, AssetVaultKey, AssetWitness}; use miden_protocol::block::{BlockHeader, BlockNumber}; use miden_protocol::note::{Note, NoteScript}; use miden_protocol::transaction::{ @@ -339,9 +340,49 @@ impl DataStore for NtxDataStore { fn get_foreign_account_inputs( &self, foreign_account_id: AccountId, - _ref_block: BlockNumber, + ref_block: BlockNumber, ) -> impl FutureMaybeSend> { - async move { Err(DataStoreError::AccountNotFound(foreign_account_id)) } + debug_assert_eq!(ref_block, self.reference_header.block_num()); + + let store = self.store.clone(); + async move { + if foreign_account_id == self.account.id() { + return Err(DataStoreError::Other { + error_msg: format!( + "requested account with id {foreign_account_id} is local, not foreign" + ) + .into(), + source: None, + }); + } + + // Retrieve the account proof from the store. + let account_proof = store + .get_account(foreign_account_id, Some(ref_block.as_u32())) + .await + .map_err(|err| DataStoreError::Other { + error_msg: format!("failed to get account proof from store: {err}").into(), + source: Some(Box::new(err)), + })?; + + // Construct account from account proof account details. + let account_details = account_proof.details.ok_or_else(|| DataStoreError::Other { + error_msg: "account proof does not contain account details".into(), + source: None, + })?; + let partial_account = PartialAccount::try_from(&account_details).map_err(|err| { + DataStoreError::Other { + error_msg: format!( + "failed to construct partial account from account details: {err}" + ) + .into(), + source: Some(Box::new(err)), + } + })?; + + // Return partial account and witness. + Ok(AccountInputs::new(partial_account, account_proof.witness)) + } } fn get_vault_asset_witnesses( @@ -350,26 +391,42 @@ impl DataStore for NtxDataStore { vault_root: Word, vault_keys: BTreeSet, ) -> impl FutureMaybeSend, DataStoreError>> { + let store = self.store.clone(); async move { - if self.account.id() != account_id { - return Err(DataStoreError::AccountNotFound(account_id)); - } - - if self.account.vault().root() != vault_root { - return Err(DataStoreError::Other { - error_msg: "vault root mismatch".into(), - source: None, - }); - } - - Result::, _>::from_iter(vault_keys.into_iter().map(|vault_key| { - AssetWitness::new(self.account.vault().open(vault_key).into()).map_err(|err| { - DataStoreError::Other { - error_msg: "failed to open vault asset tree".into(), + if self.account.id() == account_id { + if self.account.vault().root() != vault_root { + return Err(DataStoreError::Other { + error_msg: "vault root mismatch".into(), + source: None, + }); + } + get_asset_witnesses(vault_keys, self.account.vault()) + } else { + // Get foreign account. + let account_proof = store + .get_account(account_id, self.reference_header.block_num().as_u32().into()) + .await + .map_err(|err| DataStoreError::Other { + error_msg: format!("Failed to get account inputs from store: {err}").into(), source: Some(Box::new(err)), - } - }) - })) + })?; + + // Construct vault from account details. + let account_details = + account_proof.details.ok_or_else(|| DataStoreError::Other { + error_msg: "account proof does not contain account details".into(), + source: None, + })?; + let asset_vault = + AssetVault::new(&account_details.vault_details.assets).map_err(|err| { + DataStoreError::Other { + error_msg: format!("failed to create asset vault: {err}").into(), + source: Some(Box::new(err)), + } + })?; + + get_asset_witnesses(vault_keys, &asset_vault) + } } } @@ -379,28 +436,53 @@ impl DataStore for NtxDataStore { map_root: Word, map_key: Word, ) -> impl FutureMaybeSend> { + let store = self.store.clone(); async move { - if self.account.id() != account_id { - return Err(DataStoreError::AccountNotFound(account_id)); - } - - let mut map_witness = None; - for slot in self.account.storage().slots() { - if let StorageSlotContent::Map(map) = slot.content() { - if map.root() == map_root { - map_witness = Some(map.open(&map_key)); + let map_witness = if self.account.id() == account_id { + // Search through local account's storage slots. + self.account.storage().slots().iter().find_map(|slot| { + if let StorageSlotContent::Map(map) = slot.content() { + if map.root() == map_root { + Some(map.open(&map_key)) + } else { + None + } + } else { + None } - } - } - - if let Some(map_witness) = map_witness { - Ok(map_witness) + }) } else { - Err(DataStoreError::Other { - error_msg: "account storage does not contain the expected root".into(), - source: None, + // Get foreign account. + let account_proof = store + .get_account(account_id, self.reference_header.block_num().as_u32().into()) + .await + .map_err(|err| DataStoreError::Other { + error_msg: format!("failed to get account proof from store: {err}").into(), + source: Some(Box::new(err)), + })?; + let account_details = + account_proof.details.ok_or_else(|| DataStoreError::Other { + error_msg: "account proof does not contain account details".into(), + source: None, + })?; + + // Search through foreign account's storage maps. + account_details.storage_details.map_details.iter().find_map(|map_details| { + let storage_map = + StorageMap::with_entries(map_details.map_entries.iter().copied()) + .expect("no duplicate entries"); + if storage_map.root() == map_root { + Some(storage_map.open(&map_key)) + } else { + None + } }) - } + }; + + map_witness.ok_or_else(|| DataStoreError::Other { + error_msg: "account storage does not contain the expected root".into(), + source: None, + }) } } @@ -445,3 +527,18 @@ impl MastForestStore for NtxDataStore { self.mast_store.get(procedure_hash) } } + +// HELPERS +// ================================================================================================ + +fn get_asset_witnesses( + vault_keys: BTreeSet, + vault: &AssetVault, +) -> Result, DataStoreError> { + Result::, _>::from_iter(vault_keys.into_iter().map(|vault_key| { + AssetWitness::new(vault.open(vault_key).into()).map_err(|err| DataStoreError::Other { + error_msg: "failed to open vault asset tree".into(), + source: Some(Box::new(err)), + }) + })) +} diff --git a/crates/ntx-builder/src/store.rs b/crates/ntx-builder/src/store.rs index 3b6a565b6..f69ed1ec8 100644 --- a/crates/ntx-builder/src/store.rs +++ b/crates/ntx-builder/src/store.rs @@ -1,10 +1,11 @@ use std::time::Duration; use miden_node_proto::clients::{Builder, StoreNtxBuilderClient}; -use miden_node_proto::domain::account::NetworkAccountPrefix; +use miden_node_proto::domain::account::{AccountProofResponse, NetworkAccountPrefix}; use miden_node_proto::domain::note::NetworkNote; use miden_node_proto::errors::ConversionError; use miden_node_proto::generated::rpc::BlockRange; +use miden_node_proto::generated::rpc::account_proof_request::AccountDetailRequest; use miden_node_proto::generated::{self as proto}; use miden_node_proto::try_convert; use miden_protocol::Word; @@ -12,6 +13,7 @@ use miden_protocol::account::{Account, AccountId}; use miden_protocol::block::{BlockHeader, BlockNumber}; use miden_protocol::crypto::merkle::mmr::{Forest, MmrPeaks, PartialMmr}; use miden_protocol::note::NoteScript; +use miden_protocol::utils::Serializable; use miden_tx::utils::Deserializable; use thiserror::Error; use tracing::{info, instrument}; @@ -253,6 +255,26 @@ impl StoreClient { Ok(None) } } + + #[instrument(target = COMPONENT, name = "store.client.get_account", skip_all, err)] + pub async fn get_account( + &self, + account_id: AccountId, + ref_block: Option, + ) -> Result { + let request = proto::rpc::AccountProofRequest { + account_id: Some(proto::account::AccountId { id: account_id.to_bytes() }), + block_num: ref_block.map(|block_num| proto::blockchain::BlockNumber { block_num }), + details: Some(AccountDetailRequest { + code_commitment: None, + asset_vault_commitment: None, + storage_maps: Vec::new(), + }), + }; + + let response = self.inner.clone().get_account_proof(request).await?.into_inner(); + response.try_into().map_err(StoreError::DeserializationError) + } } // Store errors diff --git a/crates/proto/src/domain/account.rs b/crates/proto/src/domain/account.rs index 2ef2be02c..8b8b1cf1b 100644 --- a/crates/proto/src/domain/account.rs +++ b/crates/proto/src/domain/account.rs @@ -1,23 +1,27 @@ use std::fmt::{Debug, Display, Formatter}; use miden_node_utils::formatting::format_opt; -use miden_protocol::Word; use miden_protocol::account::{ Account, + AccountCode, AccountHeader, AccountId, + AccountStorage, AccountStorageHeader, + PartialAccount, + PartialStorage, StorageMap, StorageSlotHeader, StorageSlotName, StorageSlotType, }; -use miden_protocol::asset::{Asset, AssetVault}; +use miden_protocol::asset::{Asset, AssetVault, PartialVault}; use miden_protocol::block::BlockNumber; use miden_protocol::block::account_tree::AccountWitness; use miden_protocol::crypto::merkle::SparseMerklePath; use miden_protocol::note::{NoteExecutionMode, NoteTag}; use miden_protocol::utils::{Deserializable, DeserializationError, Serializable}; +use miden_protocol::{Word, ZERO}; use thiserror::Error; use super::try_convert; @@ -519,6 +523,42 @@ pub struct AccountDetails { pub storage_details: AccountStorageDetails, } +impl TryFrom<&AccountDetails> for PartialAccount { + type Error = ConversionError; + + fn try_from(account_details: &AccountDetails) -> Result { + // Derive account code. + let account_code = account_details + .account_code + .as_ref() + .ok_or(ConversionError::AccountCodeMissing)?; + let account_code = AccountCode::from_bytes(account_code)?; + + // Derive partial storage. + let account_storage = AccountStorage::new(Vec::new())?; // TODO(currentpr): how to get storage? + let partial_storage = if account_details.account_header.nonce() == ZERO { + PartialStorage::new_full(account_storage) + } else { + PartialStorage::new_minimal(&account_storage) + }; + + // Derive partial vault. + let asset_vault = AssetVault::new(&account_details.vault_details.assets)?; + let partial_vault = PartialVault::new_minimal(&asset_vault); + + // Construct partial account. + let partial_account = PartialAccount::new( + account_details.account_header.id(), + account_details.account_header.nonce(), + account_code, + partial_storage, + partial_vault, + None, // TODO(currentpr): Seed? + )?; + Ok(partial_account) + } +} + /// Represents the response to an account proof request. pub struct AccountProofResponse { pub block_num: BlockNumber, diff --git a/crates/proto/src/errors/mod.rs b/crates/proto/src/errors/mod.rs index 49f0f30bd..69a7e6184 100644 --- a/crates/proto/src/errors/mod.rs +++ b/crates/proto/src/errors/mod.rs @@ -5,7 +5,7 @@ use std::num::TryFromIntError; pub use miden_node_grpc_error_macro::GrpcError; use miden_protocol::crypto::merkle::smt::{SmtLeafError, SmtProofError}; use miden_protocol::utils::DeserializationError; -use miden_protocol::{AccountError, AssetError, FeeError, StorageSlotNameError}; +use miden_protocol::{AccountError, AssetError, AssetVaultError, FeeError, StorageSlotNameError}; use thiserror::Error; use crate::domain::note::NetworkNoteError; @@ -17,8 +17,12 @@ mod test_macro; pub enum ConversionError { #[error("asset error")] AssetError(#[from] AssetError), + #[error("asset vault error")] + AssetVaultError(#[from] AssetVaultError), #[error("account error")] AccountError(#[from] AccountError), + #[error("account code missing")] + AccountCodeMissing, #[error("fee parameters error")] FeeError(#[from] FeeError), #[error("hex error")] diff --git a/crates/proto/src/generated/store.rs b/crates/proto/src/generated/store.rs index 451922429..c4e578fbb 100644 --- a/crates/proto/src/generated/store.rs +++ b/crates/proto/src/generated/store.rs @@ -2482,6 +2482,31 @@ pub mod ntx_builder_client { .insert(GrpcMethod::new("store.NtxBuilder", "GetNoteScriptByRoot")); self.inner.unary(req, path, codec).await } + /// Returns the latest state proof of the specified account. + pub async fn get_account_proof( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic_prost::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/store.NtxBuilder/GetAccountProof", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("store.NtxBuilder", "GetAccountProof")); + self.inner.unary(req, path, codec).await + } } } /// Generated server implementations. @@ -2548,6 +2573,14 @@ pub mod ntx_builder_server { tonic::Response, tonic::Status, >; + /// Returns the latest state proof of the specified account. + async fn get_account_proof( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; } /// Store API for the network transaction builder component #[derive(Debug)] @@ -2922,6 +2955,53 @@ pub mod ntx_builder_server { }; Box::pin(fut) } + "/store.NtxBuilder/GetAccountProof" => { + #[allow(non_camel_case_types)] + struct GetAccountProofSvc(pub Arc); + impl< + T: NtxBuilder, + > tonic::server::UnaryService + for GetAccountProofSvc { + type Response = super::super::rpc::AccountProofResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request< + super::super::rpc::AccountProofRequest, + >, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_account_proof(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = GetAccountProofSvc(inner); + let codec = tonic_prost::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } _ => { Box::pin(async move { let mut response = http::Response::new( diff --git a/crates/store/src/server/ntx_builder.rs b/crates/store/src/server/ntx_builder.rs index 4106a121d..77b15382a 100644 --- a/crates/store/src/server/ntx_builder.rs +++ b/crates/store/src/server/ntx_builder.rs @@ -219,4 +219,27 @@ impl ntx_builder_server::NtxBuilder for StoreApi { script: note_script.map(Into::into), })) } + + // TODO: this is duplicate to rpc_api.rs + #[instrument( + parent = None, + target = COMPONENT, + name = "store.ntx_builder_server.get_account_proof", + skip_all, + level = "debug", + ret(level = "debug"), + err + )] + async fn get_account_proof( + &self, + request: Request, + ) -> Result, Status> { + debug!(target: COMPONENT, ?request); + let request = request.into_inner(); + let account_proof_request = request.try_into()?; + + let proof = self.state.get_account_proof(account_proof_request).await?; + + Ok(Response::new(proof.into())) + } } diff --git a/crates/validator/src/tx_validation/data_store.rs b/crates/validator/src/tx_validation/data_store.rs index a48c2e8e6..41294ed15 100644 --- a/crates/validator/src/tx_validation/data_store.rs +++ b/crates/validator/src/tx_validation/data_store.rs @@ -53,60 +53,92 @@ impl DataStore for TransactionInputsDataStore { foreign_account_id: AccountId, _ref_block: BlockNumber, ) -> impl FutureMaybeSend> { - async move { Err(DataStoreError::AccountNotFound(foreign_account_id)) } + async move { + if foreign_account_id == self.tx_inputs.account().id() { + return Err(DataStoreError::Other { + error_msg: format!( + "requested account with id {foreign_account_id} is local, not foreign" + ) + .into(), + source: None, + }); + } + + let foreign_inputs = self + .tx_inputs + .read_foreign_account_inputs(foreign_account_id) + .map_err(|err| DataStoreError::Other { + error_msg: format!("failed to read foreign account inputs: {err}").into(), + source: Some(Box::new(err)), + })?; + Ok(foreign_inputs) + } } fn get_vault_asset_witnesses( &self, account_id: AccountId, - vault_root: Word, + _vault_root: Word, vault_keys: BTreeSet, ) -> impl FutureMaybeSend, DataStoreError>> { async move { - if self.tx_inputs.account().id() != account_id { - return Err(DataStoreError::AccountNotFound(account_id)); + // Get asset witnessess from local or foreign account. + if self.tx_inputs.account().id() == account_id { + get_asset_witnesses_from_account(self.tx_inputs.account(), vault_keys) + } else { + let foreign_inputs = self + .tx_inputs + .read_foreign_account_inputs(account_id) + .map_err(|err| DataStoreError::Other { + error_msg: format!("failed to read foreign account inputs: {err}").into(), + source: Some(Box::new(err)), + })?; + get_asset_witnesses_from_account(foreign_inputs.account(), vault_keys) } - - if self.tx_inputs.account().vault().root() != vault_root { - return Err(DataStoreError::Other { - error_msg: "vault root mismatch".into(), - source: None, - }); - } - - Result::, _>::from_iter(vault_keys.into_iter().map(|vault_key| { - match self.tx_inputs.account().vault().open(vault_key) { - Ok(vault_proof) => { - AssetWitness::new(vault_proof.into()).map_err(|err| DataStoreError::Other { - error_msg: "failed to open vault asset tree".into(), - source: Some(err.into()), - }) - }, - Err(err) => Err(DataStoreError::Other { - error_msg: "failed to open vault".into(), - source: Some(err.into()), - }), - } - })) } } fn get_storage_map_witness( &self, account_id: AccountId, - _map_root: Word, - _map_key: Word, + map_root: Word, + map_key: Word, ) -> impl FutureMaybeSend> { async move { - if self.tx_inputs.account().id() != account_id { - return Err(DataStoreError::AccountNotFound(account_id)); - } + if self.tx_inputs.account().id() == account_id { + let storage_map_witness = + self.tx_inputs.account().storage().maps().find_map(|partial_map| { + if partial_map.root() == map_root { + partial_map.open(&map_key).ok() + } else { + None + } + }); + storage_map_witness + .ok_or_else(|| DataStoreError::Other { error_msg: "todo".into(), source: None }) + } else { + // Get foreign account inputs. + let foreign_inputs = self + .tx_inputs + .read_foreign_account_inputs(account_id) + .map_err(|err| DataStoreError::Other { + error_msg: format!("failed to read foreign account inputs: {err}").into(), + source: Some(Box::new(err)), + })?; + + // Search through the foreign account's partial storage maps. + let storage_map_witness = + foreign_inputs.account().storage().maps().find_map(|partial_map| { + if partial_map.root() == map_root { + partial_map.open(&map_key).ok() + } else { + None + } + }); - // For partial accounts, storage map witness is not available. - Err(DataStoreError::Other { - error_msg: "storage map witness not available with partial account state".into(), - source: None, - }) + storage_map_witness + .ok_or_else(|| DataStoreError::Other { error_msg: "todo".into(), source: None }) + } } } @@ -123,3 +155,26 @@ impl MastForestStore for TransactionInputsDataStore { self.mast_store.get(procedure_hash) } } + +// HELPERS +// ================================================================================================ + +fn get_asset_witnesses_from_account( + account: &PartialAccount, + vault_keys: BTreeSet, +) -> Result, DataStoreError> { + Result::, _>::from_iter(vault_keys.into_iter().map(|vault_key| { + match account.vault().open(vault_key) { + Ok(vault_proof) => { + AssetWitness::new(vault_proof.into()).map_err(|err| DataStoreError::Other { + error_msg: "failed to open vault asset tree".into(), + source: Some(err.into()), + }) + }, + Err(err) => Err(DataStoreError::Other { + error_msg: "failed to open vault".into(), + source: Some(err.into()), + }), + } + })) +} diff --git a/proto/proto/internal/store.proto b/proto/proto/internal/store.proto index 7fef64b13..7ab8b8b17 100644 --- a/proto/proto/internal/store.proto +++ b/proto/proto/internal/store.proto @@ -272,6 +272,9 @@ service NtxBuilder { // Returns the script for a note by its root. rpc GetNoteScriptByRoot(note.NoteRoot) returns (rpc.MaybeNoteScript) {} + + // Returns the latest state proof of the specified account. + rpc GetAccountProof(rpc.AccountProofRequest) returns (rpc.AccountProofResponse) {} } // GET NETWORK ACCOUNT DETAILS BY PREFIX