Skip to content

Commit

Permalink
feat: FPI for TransactionRequest and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
igamigo committed Oct 25, 2024
1 parent da97fab commit 110ec0d
Show file tree
Hide file tree
Showing 14 changed files with 290 additions and 113 deletions.
11 changes: 6 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@

## 0.6.0 (TBD)

* Implemented `GetAccountProof` endpoint (#556)
* Added FPI (Foreign Procedure Invocation) support for `TransactionRequest` (#560).
* Implemented `GetAccountProof` endpoint (#556).
* [BREAKING] Refactored `OutputNoteRecord` to use states and transitions for updates (#551).
* Added better error handling for WASM sync state
* Added WASM Input note tests + updated input note models (#554)
* Added better error handling for WASM sync state.
* Added WASM Input note tests + updated input note models (#554).
* Added new variants for the `NoteFilter` struct (#538).
* [BREAKING] Added note tags for future notes in `TransactionRequest` (#538).
* Added support for multiple input note inserts at once (#538).
* Expose full SyncSummary from WASM (#555)
* Fixed WASM + added additional WASM models (#548)
* Expose full SyncSummary from WASM (#555).
* Fixed WASM + added additional WASM models (#548).
* Added dedicated separate table for tracked tags (#535).
* [BREAKING] Added transaction prover component to `Client` (#550).
* [BREAKING] Added support for committed and discarded transactions (#531).
Expand Down
6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
resolver = "2"

members = [
"bin/miden-cli",
"crates/rust-client",
#"bin/miden-cli",
#"crates/rust-client",
"crates/web-client",
"tests"
]

default-members = ["crates/rust-client", "bin/miden-cli"]
#default-members = ["crates/rust-client", "bin/miden-cli"]

[workspace.package]
edition = "2021"
Expand Down
3 changes: 2 additions & 1 deletion bin/miden-cli/src/commands/new_transactions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,8 @@ async fn execute_transaction(
force: bool,
) -> Result<(), String> {
println!("Executing transaction...");
let transaction_execution_result = client.new_transaction(account_id, transaction_request)?;
let transaction_execution_result =
client.new_transaction(account_id, transaction_request).await?;

// Show delta and ask for confirmation
print_transaction_details(&transaction_execution_result)?;
Expand Down
3 changes: 3 additions & 0 deletions crates/rust-client/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ const TONIC_CLIENT_PROTO_OUT_DIR: &str = "src/rpc/tonic_client/generated";
const WEB_TONIC_CLIENT_PROTO_OUT_DIR: &str = "src/rpc/web_tonic_client/generated";

fn main() -> miette::Result<()> {
println!("cargo:rerun-if-changed=Cargo.toml");
println!("cargo:rerun-if-changed=Cargo.lock");

let out_dir = env::var("OUT_DIR").expect("OUT_DIR should be set");
let dest_path = PathBuf::from(out_dir);

Expand Down
10 changes: 9 additions & 1 deletion crates/rust-client/src/rpc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ impl NoteInclusionDetails {
// ACCOUNT PROOF
// ================================================================================================

/// Contains a block number, and a list of account proofs at that block.
pub type AccountProofs = (u32, Vec<AccountProof>);

/// Represents a proof of existence of an account's state at a specific block number.
Expand All @@ -154,13 +155,18 @@ impl AccountProof {
account_hash: Digest,
state_headers: Option<(AccountHeader, AccountStorageHeader, Option<AccountCode>)>,
) -> Result<Self, AccountProofError> {
if let Some((account_header, ..)) = &state_headers {
if let Some((account_header, _, code)) = &state_headers {
if account_header.hash() != account_hash {
return Err(AccountProofError::InconsistentAccountHash);
}
if account_id != account_header.id() {
return Err(AccountProofError::InconsistentAccountId);
}
if let Some(code) = code {
if code.commitment() != account_header.code_commitment() {
return Err(AccountProofError::InconsistentCodeCommitment);
}
}
}

Ok(Self {
Expand Down Expand Up @@ -213,13 +219,15 @@ impl AccountProof {
pub enum AccountProofError {
InconsistentAccountHash,
InconsistentAccountId,
InconsistentCodeCommitment,
}

impl fmt::Display for AccountProofError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AccountProofError::InconsistentAccountHash => write!(f,"The received account hash does not match the received account header's account hash"),
AccountProofError::InconsistentAccountId => write!(f,"The received account ID does not match the received account header's ID"),
AccountProofError::InconsistentCodeCommitment => write!(f,"The received code commitment does not match the received account header's code commitment"),
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions crates/rust-client/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@ async fn test_mint_transaction() {
)
.unwrap();

let transaction = client.new_transaction(faucet.id(), transaction_request).unwrap();
let transaction = client.new_transaction(faucet.id(), transaction_request).await.unwrap();

assert!(transaction.executed_transaction().account_delta().nonce().is_some());
}
Expand Down Expand Up @@ -426,7 +426,7 @@ async fn test_get_output_notes() {
//Before executing transaction, there are no output notes
assert!(client.get_output_notes(NoteFilter::All).unwrap().is_empty());

let transaction = client.new_transaction(faucet.id(), transaction_request).unwrap();
let transaction = client.new_transaction(faucet.id(), transaction_request).await.unwrap();
client.submit_transaction(transaction).await.unwrap();

// Check that there was an output note but it wasn't consumed
Expand Down
140 changes: 94 additions & 46 deletions crates/rust-client/src/transactions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ use alloc::{
};
use core::fmt::{self};

use miden_lib::transaction::TransactionKernel;
pub use miden_lib::transaction::TransactionKernel;
use miden_objects::{
accounts::{Account, AccountDelta, AccountId, AccountType},
assets::{Asset, NonFungibleAsset},
notes::{Note, NoteDetails, NoteExecutionMode, NoteId, NoteTag, NoteType},
notes::{Note, NoteDetails, NoteId, NoteTag},
transaction::{InputNotes, TransactionArgs},
AssetError, Digest, Felt, NoteError, Word,
vm::AdviceInputs,
AssetError, Digest, Felt, Word, ZERO,
};
pub use miden_tx::{LocalTransactionProver, ProvingOptions, TransactionProver};
use script_builder::{AccountCapabilities, AccountInterface, TransactionScriptBuilder};
Expand All @@ -24,6 +25,7 @@ use winter_maybe_async::*;
use super::{Client, FeltRng};
use crate::{
notes::NoteScreener,
rpc::RpcError,
store::{input_note_states::ExpectedNoteState, InputNoteRecord, NoteFilter, TransactionFilter},
ClientError,
};
Expand Down Expand Up @@ -226,8 +228,7 @@ impl<R: FeltRng> Client<R> {
/// a subset of executor's output notes
/// - Returns a [ClientError::TransactionExecutorError] if the execution fails
/// - Returns a [ClientError::TransactionRequestError] if the request is invalid
#[maybe_async]
pub fn new_transaction(
pub async fn new_transaction(
&mut self,
account_id: AccountId,
transaction_request: TransactionRequest,
Expand Down Expand Up @@ -296,7 +297,13 @@ impl<R: FeltRng> Client<R> {
},
};

let tx_args = transaction_request.into_transaction_args(tx_script);
// Inject foreign account data
let foreign_data_advice_inputs =
self.get_foreign_account_inputs(&transaction_request).await?;
let mut tx_args = transaction_request.into_transaction_args(tx_script);
let AdviceInputs { map, store, .. } = foreign_data_advice_inputs;
tx_args.extend_advice_map(map);
tx_args.extend_merkle_store(store.inner_nodes());

// Execute the transaction and get the witness
let executed_transaction = maybe_await!(self
Expand Down Expand Up @@ -555,6 +562,86 @@ impl<R: FeltRng> Client<R> {
interfaces: account_capabilities,
})
}

/// Fetches foreign public account data as needed and returns related advice inputs.
async fn get_foreign_account_inputs(
&mut self,
transaction_request: &TransactionRequest,
) -> Result<AdviceInputs, ClientError> {
if transaction_request.foreign_account_data().is_empty() {
return Ok(AdviceInputs::default());
}
// TODO: We want to cache account code for a specific account here, so that we can send
// latest-known commitments to the node and avoid some bandwidth pressure when possible
// For now, send an empty list of code commitments to get the latest code anyway.
let (_block_num, account_proofs) = self
.rpc_api
.get_account_proofs(transaction_request.foreign_account_data(), &[], true)
.await?;

let mut advice_inputs = AdviceInputs::default();

for account_proof in account_proofs.into_iter() {
let account_header = account_proof.account_header().ok_or_else(|| {
ClientError::RpcError(RpcError::ExpectedDataMissing("AccountHeader".to_string()))
})?;

let account_id = account_header.id();
let account_nonce = account_header.nonce();
let vault_root = account_header.vault_root();
let storage_root = account_header.storage_commitment();
let code_root = account_header.code_commitment();

let foreign_id_root = Digest::from([account_id.into(), ZERO, ZERO, ZERO]);
let foreign_id_and_nonce = [account_id.into(), ZERO, ZERO, account_nonce];

let storage_header = account_proof.storage_header().ok_or_else(|| {
ClientError::RpcError(RpcError::ExpectedDataMissing("StorageHeader".to_string()))
})?;

// Prepare storage slot data
let mut slots_data = Vec::new();
for (slot_type, value) in storage_header.slots() {
let mut elements = [ZERO; 8];
elements[0..4].copy_from_slice(value);
elements[4..8].copy_from_slice(&slot_type.as_word());
slots_data.extend_from_slice(&elements);
}

let account_code = account_proof.account_code().ok_or_else(|| {
ClientError::RpcError(RpcError::ExpectedDataMissing("AccountCode".to_string()))
})?;

// Extend the advice inputs with the new data
advice_inputs.extend_map([
// ACCOUNT_ID -> [ID_AND_NONCE, VAULT_ROOT, STORAGE_ROOT, CODE_ROOT]
(
foreign_id_root,
[
&foreign_id_and_nonce,
vault_root.as_elements(),
storage_root.as_elements(),
code_root.as_elements(),
]
.concat(),
),
// STORAGE_ROOT -> [STORAGE_SLOT_DATA]
(storage_root, slots_data),
// CODE_ROOT -> [ACCOUNT_CODE_DATA]
(code_root, account_code.as_elements()),
]);

// TODO: Remove this unwrap
advice_inputs.extend_merkle_store(
account_proof
.merkle_proof()
.inner_nodes(account_id.into(), account_header.hash())
.unwrap(),
);
}

Ok(advice_inputs)
}
}

// TESTING HELPERS
Expand Down Expand Up @@ -631,45 +718,6 @@ pub fn notes_from_output(output_notes: &OutputNotes) -> impl Iterator<Item = &No
})
}

/// Returns a note tag for a swap note with the specified parameters.
///
/// Use case ID for the returned tag is set to 0.
///
/// Tag payload is constructed by taking asset tags (8 bits of faucet ID) and concatenating them
/// together as offered_asset_tag + requested_asset tag.
///
/// Network execution hint for the returned tag is set to `Local`.
///
/// Based on miden-base's implementation (<https://github.com/0xPolygonMiden/miden-base/blob/9e4de88031b55bcc3524cb0ccfb269821d97fb29/miden-lib/src/notes/mod.rs#L153>)
///
/// TODO: we should make the function in base public and once that gets released use that one and
/// delete this implementation.
pub fn build_swap_tag(
note_type: NoteType,
offered_asset_faucet_id: AccountId,
requested_asset_faucet_id: AccountId,
) -> Result<NoteTag, NoteError> {
const SWAP_USE_CASE_ID: u16 = 0;

// get bits 4..12 from faucet IDs of both assets, these bits will form the tag payload; the
// reason we skip the 4 most significant bits is that these encode metadata of underlying
// faucets and are likely to be the same for many different faucets.

let offered_asset_id: u64 = offered_asset_faucet_id.into();
let offered_asset_tag = (offered_asset_id >> 52) as u8;

let requested_asset_id: u64 = requested_asset_faucet_id.into();
let requested_asset_tag = (requested_asset_id >> 52) as u8;

let payload = ((offered_asset_tag as u16) << 8) | (requested_asset_tag as u16);

let execution = NoteExecutionMode::Local;
match note_type {
NoteType::Public => NoteTag::for_public_use_case(SWAP_USE_CASE_ID, payload, execution),
_ => NoteTag::for_local_use_case(SWAP_USE_CASE_ID, payload),
}
}

#[cfg(test)]
mod test {
use miden_lib::transaction::TransactionKernel;
Expand Down Expand Up @@ -732,7 +780,7 @@ mod test {
)
.unwrap();

let tx_result = client.new_transaction(account.id(), tx_request).unwrap();
let tx_result = client.new_transaction(account.id(), tx_request).await.unwrap();
assert!(tx_result
.created_notes()
.get_note(0)
Expand Down
Loading

0 comments on commit 110ec0d

Please sign in to comment.