Skip to content
Closed
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 6 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand Down
165 changes: 128 additions & 37 deletions crates/ntx-builder/src/actor/execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down Expand Up @@ -339,9 +340,45 @@ impl DataStore for NtxDataStore {
fn get_foreign_account_inputs(
&self,
foreign_account_id: AccountId,
_ref_block: BlockNumber,
ref_block: BlockNumber,
) -> impl FutureMaybeSend<Result<AccountInputs, DataStoreError>> {
async move { Err(DataStoreError::AccountNotFound(foreign_account_id)) }
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 account =
Account::try_from(&account_details).map_err(|err| DataStoreError::Other {
error_msg: format!("failed to convert account details to account: {err}")
.into(),
source: Some(Box::new(err)),
})?;

// Return partial account and witness.
let partial_account = PartialAccount::from(&account);
Ok(AccountInputs::new(partial_account, account_proof.witness))
}
}

fn get_vault_asset_witnesses(
Expand All @@ -350,26 +387,41 @@ impl DataStore for NtxDataStore {
vault_root: Word,
vault_keys: BTreeSet<AssetVaultKey>,
) -> impl FutureMaybeSend<Result<Vec<AssetWitness>, 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::<Vec<_>, _>::from_iter(vault_keys.into_iter().map(|vault_key| {
AssetWitness::new(self.account.vault().open(vault_key).into()).map_err(|err| {
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, None).await.map_err(|err| {
DataStoreError::Other {
error_msg: "failed to open vault asset tree".into(),
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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple of comments on this approach:

First, when we make a request to the store, we should use the reference block of this data store (i.e., self.reference_header.block_num().

But also, getting the full account proof is very heavy compared to what we need here. Maybe that's OK for this PR, but we should follow it up with using dedicated endpoints on the store to get asset/storage witnesses. Let's create an issue for this.

}
}
}

Expand All @@ -379,28 +431,52 @@ impl DataStore for NtxDataStore {
map_root: Word,
map_key: Word,
) -> impl FutureMaybeSend<Result<StorageMapWitness, DataStoreError>> {
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, None).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,
})
}
}

Expand Down Expand Up @@ -445,3 +521,18 @@ impl MastForestStore for NtxDataStore {
self.mast_store.get(procedure_hash)
}
}

// HELPERS
// ================================================================================================

fn get_asset_witnesses(
vault_keys: BTreeSet<AssetVaultKey>,
vault: &AssetVault,
) -> Result<Vec<AssetWitness>, DataStoreError> {
Result::<Vec<_>, _>::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)),
})
}))
}
24 changes: 23 additions & 1 deletion crates/ntx-builder/src/store.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
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;
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};
Expand Down Expand Up @@ -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<u32>,
) -> Result<AccountProofResponse, StoreError> {
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
Expand Down
25 changes: 25 additions & 0 deletions crates/proto/src/domain/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ use miden_node_utils::formatting::format_opt;
use miden_protocol::Word;
use miden_protocol::account::{
Account,
AccountCode,
AccountHeader,
AccountId,
AccountStorage,
AccountStorageHeader,
StorageMap,
StorageSlotHeader,
Expand Down Expand Up @@ -519,6 +521,29 @@ pub struct AccountDetails {
pub storage_details: AccountStorageDetails,
}

impl TryFrom<&AccountDetails> for Account {
type Error = ConversionError;

fn try_from(account_details: &AccountDetails) -> Result<Self, Self::Error> {
let asset_vault = AssetVault::new(&account_details.vault_details.assets)?;
let account_storage = AccountStorage::new(Vec::new())?; // TODO(currentpr): how do we get account storage?
let account_code = account_details
.account_code
.as_ref()
.ok_or(ConversionError::AccountCodeMissing)?;
let account_code = AccountCode::from_bytes(account_code)?;
let account = Account::new(
account_details.account_header.id(),
asset_vault,
account_storage,
account_code,
account_details.account_header.nonce(),
None, // TODO(currentpr): add seed?
)?;
Ok(account)
}
}

/// Represents the response to an account proof request.
pub struct AccountProofResponse {
pub block_num: BlockNumber,
Expand Down
Loading
Loading