use std::sync::Arc;
use std::time::Duration;
use anyhow::{Context, Result};
use assert_matches::assert_matches;
use miden_client::account::component::{AccountComponent, AccountComponentMetadata};
use miden_client::account::{
Account,
AccountBuilder,
AccountId,
AccountStorageMode,
AccountType,
StorageMap,
StorageMapKey,
StorageSlot,
StorageSlotName,
};
use miden_client::assembly::CodeBuilder;
use miden_client::asset::{Asset, FungibleAsset};
use miden_client::auth::{AuthSchemeId, AuthSecretKey, AuthSingleSig, RPO_FALCON_SCHEME_ID};
use miden_client::builder::ClientBuilder;
use miden_client::keystore::FilesystemKeyStore;
use miden_client::note::{NoteFile, NoteType};
use miden_client::rpc::AccountStateAt;
use miden_client::rpc::domain::account::{
AccountStorageRequirements,
FetchedAccount,
StorageMapEntries,
};
use miden_client::store::{
InputNoteRecord,
InputNoteState,
NoteFilter,
OutputNoteState,
TransactionFilter,
};
use miden_client::testing::common::*;
use miden_client::transaction::{
DiscardCause,
PaymentNoteDescription,
ProvenTransaction,
TransactionInputs,
TransactionProver,
TransactionProverError,
TransactionRequestBuilder,
TransactionStatus,
};
use miden_client::{ClientError, EMPTY_WORD, Felt, Word};
use miden_client_sqlite_store::ClientBuilderSqliteExt;
use tracing::info;
use crate::tests::config::ClientConfig;
pub async fn test_client_builder_initializes_client_with_endpoint(
client_config: ClientConfig,
) -> Result<()> {
let (endpoint, _, store_config, auth_path) = client_config.as_parts();
let mut client = ClientBuilder::<FilesystemKeyStore>::new()
.grpc_client(&endpoint, Some(10_000))
.filesystem_keystore(auth_path)?
.sqlite_store(store_config)
.in_debug_mode(miden_client::DebugMode::Enabled)
.build()
.await?;
assert!(client.in_debug_mode());
let sync_summary = client.sync_state().await?;
assert!(sync_summary.block_num.as_u32() > 0);
Ok(())
}
pub async fn test_multiple_tx_on_same_block(client_config: ClientConfig) -> Result<()> {
let (mut client, authenticator) = client_config.into_client().await?;
wait_for_node(&mut client).await;
let (first_regular_account, second_regular_account, faucet_account_header) =
setup_two_wallets_and_faucet(
&mut client,
AccountStorageMode::Private,
&authenticator,
RPO_FALCON_SCHEME_ID,
)
.await?;
let from_account_id = first_regular_account.id();
let to_account_id = second_regular_account.id();
let faucet_account_id = faucet_account_header.id();
let tx_id =
mint_and_consume(&mut client, from_account_id, faucet_account_id, NoteType::Private).await;
wait_for_tx(&mut client, tx_id).await?;
let asset = FungibleAsset::new(faucet_account_id, TRANSFER_AMOUNT).unwrap();
let tx_request_1 = TransactionRequestBuilder::new()
.build_pay_to_id(
PaymentNoteDescription::new(
vec![Asset::Fungible(asset)],
from_account_id,
to_account_id,
),
NoteType::Private,
client.rng(),
)
.unwrap();
let tx_request_2 = TransactionRequestBuilder::new()
.build_pay_to_id(
PaymentNoteDescription::new(
vec![Asset::Fungible(asset)],
from_account_id,
to_account_id,
),
NoteType::Private,
client.rng(),
)
.unwrap();
info!(from = %from_account_id, to = %to_account_id, "Running P2ID transaction");
let transaction_result_1 =
client.execute_transaction(from_account_id, tx_request_1).await.unwrap();
let transaction_id_1 = transaction_result_1.id();
let proven_transaction_1 = client.prove_transaction(&transaction_result_1).await.unwrap();
let current_height = client.get_sync_height().await?;
client.apply_transaction(&transaction_result_1, current_height).await?;
let transaction_result_2 =
client.execute_transaction(from_account_id, tx_request_2).await.unwrap();
let transaction_id_2 = transaction_result_2.id();
let proven_transaction_2 = client.prove_transaction(&transaction_result_2).await.unwrap();
client
.submit_proven_transaction(proven_transaction_1, &transaction_result_1)
.await?;
let submission_height_2 = client
.submit_proven_transaction(proven_transaction_2, &transaction_result_2)
.await
.unwrap();
client
.apply_transaction(&transaction_result_2, submission_height_2)
.await
.unwrap();
client.sync_state().await.unwrap();
wait_for_blocks(&mut client, 1).await;
wait_for_tx(&mut client, transaction_id_1).await?;
wait_for_tx(&mut client, transaction_id_2).await?;
let transactions = client
.get_transactions(TransactionFilter::All)
.await
.unwrap()
.into_iter()
.filter(|tx| tx.id == transaction_id_1 || tx.id == transaction_id_2)
.collect::<Vec<_>>();
assert_eq!(transactions.len(), 2);
assert!(matches!(transactions[0].status, TransactionStatus::Committed { .. }));
assert_eq!(transactions[0].status, transactions[1].status);
let note_id = transactions[0].details.output_notes.iter().next().unwrap().id();
let note = client.get_output_note(note_id).await.unwrap().unwrap();
assert!(matches!(note.state(), OutputNoteState::CommittedFull { .. }));
let sender_balance = client
.account_reader(from_account_id)
.get_balance(faucet_account_id)
.await
.context("failed to find sender account after transactions")?;
assert_eq!(sender_balance, MINT_AMOUNT - (TRANSFER_AMOUNT * 2));
Ok(())
}
pub async fn test_import_expected_notes(client_config: ClientConfig) -> Result<()> {
let (mut client_1, authenticator_1) = client_config.clone().into_client().await?;
let (first_basic_account, faucet_account) = setup_wallet_and_faucet(
&mut client_1,
AccountStorageMode::Private,
&authenticator_1,
RPO_FALCON_SCHEME_ID,
)
.await?;
let (mut client_2, authenticator_2) = client_config.into_client().await?;
let (client_2_account, _) = insert_new_wallet(
&mut client_2,
AccountStorageMode::Private,
&authenticator_2,
RPO_FALCON_SCHEME_ID,
)
.await?;
wait_for_node(&mut client_2).await;
let tx_request = TransactionRequestBuilder::new()
.build_mint_fungible_asset(
FungibleAsset::new(faucet_account.id(), MINT_AMOUNT).unwrap(),
client_2_account.id(),
NoteType::Public,
client_2.rng(),
)
.unwrap();
let note: InputNoteRecord =
tx_request.expected_output_own_notes().pop().unwrap().clone().into();
client_2.sync_state().await.unwrap();
assert_eq!(
client_2
.import_notes(&[NoteFile::NoteId(note.id())])
.await
.unwrap_err()
.to_string(),
"note import error: No notes fetched from node".to_string()
);
execute_tx_and_sync(&mut client_1, faucet_account.id(), tx_request).await?;
wait_for_blocks(&mut client_1, 3).await;
let new_sync_data = client_2.sync_state().await.unwrap();
client_2.add_note_tag(note.metadata().unwrap().tag()).await.unwrap();
client_2.import_notes(&[NoteFile::NoteId(note.clone().id())]).await.unwrap();
client_2.sync_state().await.unwrap();
let input_note = client_2.get_input_note(note.id()).await.unwrap().unwrap();
assert!(input_note.inclusion_proof().is_some(), "Expected inclusion proof to be present");
assert!(
new_sync_data.block_num > input_note.inclusion_proof().unwrap().location().block_num() + 1
);
let tx_id =
consume_notes(&mut client_2, client_2_account.id(), &[input_note.try_into().unwrap()])
.await;
wait_for_tx(&mut client_2, tx_id).await?;
let tx_request = TransactionRequestBuilder::new()
.build_mint_fungible_asset(
FungibleAsset::new(faucet_account.id(), MINT_AMOUNT).unwrap(),
first_basic_account.id(),
NoteType::Private,
client_2.rng(),
)
.unwrap();
let note: InputNoteRecord =
tx_request.expected_output_own_notes().pop().unwrap().clone().into();
client_2.add_note_tag(note.metadata().unwrap().tag()).await.unwrap();
client_2
.import_notes(&[NoteFile::NoteDetails {
details: note.clone().into(),
after_block_num: client_1.get_sync_height().await.unwrap(),
tag: Some(note.metadata().unwrap().tag()),
}])
.await
.unwrap();
let input_note = client_2.get_input_note(note.id()).await.unwrap().unwrap();
assert!(matches!(input_note.state(), InputNoteState::Expected { .. }));
execute_tx_and_sync(&mut client_1, faucet_account.id(), tx_request).await?;
client_2.sync_state().await.unwrap();
let input_note = client_2.get_input_note(note.id()).await.unwrap().unwrap();
assert!(input_note.inclusion_proof().is_some(), "Expected inclusion proof to be present");
let tx_id =
consume_notes(&mut client_1, first_basic_account.id(), &[input_note.try_into().unwrap()])
.await;
wait_for_tx(&mut client_1, tx_id).await?;
Ok(())
}
pub async fn test_import_expected_note_uncommitted(client_config: ClientConfig) -> Result<()> {
let (mut client_1, authenticator) = client_config.clone().into_client().await?;
let faucet_account = insert_new_fungible_faucet(
&mut client_1,
AccountStorageMode::Private,
&authenticator,
RPO_FALCON_SCHEME_ID,
)
.await
.unwrap()
.0;
let (mut client_2, _) = ClientConfig::default()
.with_rpc_endpoint(client_config.rpc_endpoint())
.into_client()
.await?;
let (client_2_account, _) = insert_new_wallet(
&mut client_2,
AccountStorageMode::Private,
&authenticator,
RPO_FALCON_SCHEME_ID,
)
.await?;
wait_for_node(&mut client_2).await;
let tx_request = TransactionRequestBuilder::new().build_mint_fungible_asset(
FungibleAsset::new(faucet_account.id(), MINT_AMOUNT).unwrap(),
client_2_account.id(),
NoteType::Public,
client_1.rng(),
)?;
let note: InputNoteRecord =
tx_request.expected_output_own_notes().pop().unwrap().clone().into();
client_2.sync_state().await.unwrap();
let imported_note_id = client_2
.import_notes(&[NoteFile::NoteDetails {
details: note.into(),
after_block_num: 0.into(),
tag: None,
}])
.await?[0];
let imported_note = client_2.get_input_note(imported_note_id).await.unwrap().unwrap();
assert!(matches!(imported_note.state(), InputNoteState::Expected { .. }));
Ok(())
}
pub async fn test_import_expected_notes_from_the_past_as_committed(
client_config: ClientConfig,
) -> Result<()> {
let (mut client_1, authenticator_1) = client_config.clone().into_client().await?;
let (first_basic_account, faucet_account) = setup_wallet_and_faucet(
&mut client_1,
AccountStorageMode::Private,
&authenticator_1,
RPO_FALCON_SCHEME_ID,
)
.await?;
let (mut client_2, _) = ClientConfig::default()
.with_rpc_endpoint(client_config.rpc_endpoint())
.into_client()
.await?;
wait_for_node(&mut client_2).await;
let tx_request = TransactionRequestBuilder::new().build_mint_fungible_asset(
FungibleAsset::new(faucet_account.id(), MINT_AMOUNT).unwrap(),
first_basic_account.id(),
NoteType::Public,
client_1.rng(),
)?;
let note: InputNoteRecord =
tx_request.expected_output_own_notes().pop().unwrap().clone().into();
let block_height_before = client_1.get_sync_height().await.unwrap();
execute_tx_and_sync(&mut client_1, faucet_account.id(), tx_request).await?;
let note_id = client_2
.import_notes(&[NoteFile::NoteDetails {
details: note.clone().into(),
after_block_num: block_height_before,
tag: Some(note.metadata().unwrap().tag()),
}])
.await?[0];
let imported_note = client_2.get_input_note(note_id).await.unwrap().unwrap();
assert!(matches!(imported_note.state(), InputNoteState::Expected { .. }));
client_2.sync_state().await.unwrap();
assert!(
client_2
.import_notes(&[NoteFile::NoteDetails {
details: note.clone().into(),
after_block_num: block_height_before,
tag: Some(note.metadata().unwrap().tag()),
}])
.await?
.is_empty()
);
let imported_note = client_2.get_input_note(note_id).await.unwrap().unwrap();
let client_1_note = client_1.get_input_note(note_id).await.unwrap().unwrap();
assert_eq!(imported_note.state(), client_1_note.state());
assert!(matches!(imported_note.state(), InputNoteState::Committed { .. }));
Ok(())
}
pub async fn test_get_account_update(client_config: ClientConfig) -> Result<()> {
let (mut client, authenticator) = client_config.clone().into_client().await?;
let (basic_wallet_1, faucet_account) = setup_wallet_and_faucet(
&mut client,
AccountStorageMode::Private,
&authenticator,
RPO_FALCON_SCHEME_ID,
)
.await?;
wait_for_node(&mut client).await;
let (basic_wallet_2, ..) = insert_new_wallet(
&mut client,
AccountStorageMode::Public,
&authenticator,
RPO_FALCON_SCHEME_ID,
)
.await?;
let tx_id_1 =
mint_and_consume(&mut client, basic_wallet_1.id(), faucet_account.id(), NoteType::Private)
.await;
wait_for_tx(&mut client, tx_id_1).await?;
let tx_id_2 =
mint_and_consume(&mut client, basic_wallet_2.id(), faucet_account.id(), NoteType::Private)
.await;
wait_for_tx(&mut client, tx_id_2).await?;
let rpc_api = client.test_rpc_api();
let details1 = rpc_api.get_account_details(basic_wallet_1.id()).await.unwrap();
let details2 = rpc_api.get_account_details(basic_wallet_2.id()).await.unwrap();
assert!(matches!(details1, FetchedAccount::Private(_, _)));
assert_matches!(details2, FetchedAccount::Public(account, _) if account.vault().get_balance(faucet_account.id()).unwrap() == MINT_AMOUNT);
Ok(())
}
pub async fn test_sync_detail_values(client_config: ClientConfig) -> Result<()> {
let (mut client1, authenticator_1) = client_config.clone().into_client().await?;
let (mut client2, authenticator_2) = ClientConfig::default()
.with_rpc_endpoint(client_config.rpc_endpoint())
.into_client()
.await?;
wait_for_node(&mut client1).await;
wait_for_node(&mut client2).await;
let (first_regular_account, faucet_account_header) = setup_wallet_and_faucet(
&mut client1,
AccountStorageMode::Private,
&authenticator_1,
RPO_FALCON_SCHEME_ID,
)
.await?;
let (second_regular_account, ..) = insert_new_wallet(
&mut client2,
AccountStorageMode::Private,
&authenticator_2,
RPO_FALCON_SCHEME_ID,
)
.await?;
let from_account_id = first_regular_account.id();
let to_account_id = second_regular_account.id();
let faucet_account_id = faucet_account_header.id();
let tx_id =
mint_and_consume(&mut client1, from_account_id, faucet_account_id, NoteType::Private).await;
wait_for_tx(&mut client1, tx_id).await?;
let new_details = client2.sync_state().await.unwrap();
assert!(new_details.is_empty());
let asset = FungibleAsset::new(faucet_account_id, TRANSFER_AMOUNT).unwrap();
let tx_request = TransactionRequestBuilder::new().build_pay_to_id(
PaymentNoteDescription::new(vec![Asset::Fungible(asset)], from_account_id, to_account_id)
.with_reclaim_height(new_details.block_num + 5),
NoteType::Public,
client1.rng(),
)?;
let note = tx_request.expected_output_own_notes().pop().unwrap();
execute_tx_and_sync(&mut client1, from_account_id, tx_request).await?;
let new_details = client2.sync_state().await.unwrap();
assert_eq!(new_details.new_public_notes.len(), 1);
assert_eq!(new_details.committed_notes.len(), 0);
assert_eq!(new_details.consumed_notes.len(), 0);
assert_eq!(new_details.updated_accounts.len(), 0);
let tx_request = TransactionRequestBuilder::new().build_consume_notes(vec![note]).unwrap();
execute_tx_and_sync(&mut client2, to_account_id, tx_request).await?;
let new_details = client1.sync_state().await.unwrap();
assert_eq!(new_details.committed_notes.len(), 0);
assert_eq!(new_details.consumed_notes.len(), 1);
Ok(())
}
pub async fn test_multiple_transactions_can_be_committed_in_different_blocks_without_sync(
client_config: ClientConfig,
) -> Result<()> {
let (mut client, authenticator) = client_config.into_client().await?;
let (first_regular_account, faucet_account_header) = setup_wallet_and_faucet(
&mut client,
AccountStorageMode::Private,
&authenticator,
RPO_FALCON_SCHEME_ID,
)
.await?;
let from_account_id = first_regular_account.id();
let faucet_account_id = faucet_account_header.id();
let (first_note_id, first_note_tx_id) = {
let fungible_asset = FungibleAsset::new(faucet_account_id, MINT_AMOUNT).unwrap();
info!(faucet_id = %faucet_account_id, target = %from_account_id, "Minting first asset");
let tx_request = TransactionRequestBuilder::new().build_mint_fungible_asset(
fungible_asset,
from_account_id,
NoteType::Private,
client.rng(),
)?;
info!("Executing first mint transaction");
let transaction_id =
client.submit_new_transaction(faucet_account_id, tx_request.clone()).await?;
let note_id = tx_request.expected_output_own_notes().pop().unwrap().id();
(note_id, transaction_id)
};
let (second_note_id, second_note_tx_id) = {
let fungible_asset = FungibleAsset::new(faucet_account_id, MINT_AMOUNT).unwrap();
info!(faucet_id = %faucet_account_id, target = %from_account_id, "Minting second asset");
let tx_request = TransactionRequestBuilder::new().build_mint_fungible_asset(
fungible_asset,
from_account_id,
NoteType::Private,
client.rng(),
)?;
info!("Executing second mint transaction");
let transaction_result =
client.execute_transaction(faucet_account_id, tx_request.clone()).await.unwrap();
let transaction_id = transaction_result.id();
info!(tx_id = %transaction_id, "Sending second transaction to node");
let note_id = tx_request.expected_output_own_notes().pop().unwrap().id();
while client
.test_rpc_api()
.get_notes_by_id(&[first_note_id])
.await
.unwrap()
.is_empty()
{
std::thread::sleep(Duration::from_secs(3));
}
let proven_transaction = client.prove_transaction(&transaction_result).await.unwrap();
let submission_height = client
.submit_proven_transaction(proven_transaction, &transaction_result)
.await
.unwrap();
client.apply_transaction(&transaction_result, submission_height).await.unwrap();
(note_id, transaction_id)
};
let (third_note_id, third_note_tx_id) = {
let fungible_asset = FungibleAsset::new(faucet_account_id, MINT_AMOUNT).unwrap();
info!(faucet_id = %faucet_account_id, target = %from_account_id, "Minting third asset");
let tx_request = TransactionRequestBuilder::new().build_mint_fungible_asset(
fungible_asset,
from_account_id,
NoteType::Private,
client.rng(),
)?;
info!("Executing third mint transaction");
let transaction_result =
client.execute_transaction(faucet_account_id, tx_request.clone()).await.unwrap();
let transaction_id = transaction_result.id();
info!(tx_id = %transaction_id, "Sending third transaction to node");
let note_id = tx_request.expected_output_own_notes().pop().unwrap().id();
while client
.test_rpc_api()
.get_notes_by_id(&[second_note_id])
.await
.unwrap()
.is_empty()
{
std::thread::sleep(Duration::from_secs(3));
}
let proven_transaction = client.prove_transaction(&transaction_result).await.unwrap();
let submission_height = client
.submit_proven_transaction(proven_transaction, &transaction_result)
.await
.unwrap();
client.apply_transaction(&transaction_result, submission_height).await.unwrap();
(note_id, transaction_id)
};
while client
.test_rpc_api()
.get_notes_by_id(&[third_note_id])
.await
.unwrap()
.is_empty()
{
std::thread::sleep(Duration::from_secs(3));
}
client.sync_state().await.unwrap();
let all_transactions = client.get_transactions(TransactionFilter::All).await.unwrap();
let first_tx = all_transactions.iter().find(|tx| tx.id == first_note_tx_id).unwrap();
let second_tx = all_transactions.iter().find(|tx| tx.id == second_note_tx_id).unwrap();
let third_tx = all_transactions.iter().find(|tx| tx.id == third_note_tx_id).unwrap();
match (first_tx.status.clone(), second_tx.status.clone(), third_tx.status.clone()) {
(
TransactionStatus::Committed { block_number: first_tx_commit_height, .. },
TransactionStatus::Committed {
block_number: second_tx_commit_height, ..
},
TransactionStatus::Committed { block_number: third_tx_commit_height, .. },
) => {
assert!(first_tx_commit_height < second_tx_commit_height);
assert!(second_tx_commit_height < third_tx_commit_height);
},
_ => {
panic!("All three TXs should be committed in different blocks")
},
}
Ok(())
}
pub async fn test_consume_multiple_expected_notes(client_config: ClientConfig) -> Result<()> {
let (mut client, authenticator_1) = client_config.clone().into_client().await?;
let (mut unauth_client, authenticator_2) = ClientConfig::default()
.with_rpc_endpoint(client_config.rpc_endpoint())
.into_client()
.await?;
wait_for_node(&mut client).await;
let (target_basic_account_1, faucet_account_header) = setup_wallet_and_faucet(
&mut client,
AccountStorageMode::Private,
&authenticator_1,
RPO_FALCON_SCHEME_ID,
)
.await?;
let (target_basic_account_2, ..) = insert_new_wallet(
&mut unauth_client,
AccountStorageMode::Private,
&authenticator_2,
RPO_FALCON_SCHEME_ID,
)
.await?;
unauth_client.sync_state().await.unwrap();
let faucet_account_id = faucet_account_header.id();
let to_account_ids = [target_basic_account_1.id(), target_basic_account_2.id()];
let fungible_asset = FungibleAsset::new(faucet_account_id, TRANSFER_AMOUNT).unwrap();
let mint_tx_request = mint_multiple_fungible_asset(
fungible_asset,
&[to_account_ids[0], to_account_ids[0], to_account_ids[1], to_account_ids[1]],
NoteType::Private,
client.rng(),
);
let all_expected_notes = mint_tx_request.expected_output_own_notes();
execute_tx_and_sync(&mut client, faucet_account_id, mint_tx_request).await?;
unauth_client.sync_state().await.unwrap();
let expected_notes = all_expected_notes.into_iter();
let client_notes: Vec<_> = client.get_input_notes(NoteFilter::All).await.unwrap();
let client_notes_ids: Vec<_> = client_notes.iter().map(|note| note.id()).collect();
let (client_owned_notes, unauth_owned_notes): (Vec<_>, Vec<_>) =
expected_notes.partition(|note| client_notes_ids.contains(¬e.id()));
let tx_request_1 = TransactionRequestBuilder::new()
.input_notes(client_owned_notes.iter().map(|note| (note.clone(), None)))
.build()?;
let tx_request_2 = TransactionRequestBuilder::new()
.input_notes(unauth_owned_notes.iter().map(|note| ((*note).clone(), None)))
.build()?;
let tx_id_1 = client.submit_new_transaction(to_account_ids[0], tx_request_1).await.unwrap();
let tx_id_2 = unauth_client
.submit_new_transaction(to_account_ids[1], tx_request_2)
.await
.unwrap();
assert!(!client.get_input_notes(NoteFilter::Processing).await.unwrap().is_empty());
assert!(!unauth_client.get_input_notes(NoteFilter::Processing).await.unwrap().is_empty());
wait_for_tx(&mut client, tx_id_1).await?;
wait_for_tx(&mut unauth_client, tx_id_2).await?;
assert!(client.get_input_notes(NoteFilter::Expected).await.unwrap().is_empty());
assert!(unauth_client.get_input_notes(NoteFilter::Expected).await.unwrap().is_empty());
assert!(
!client.get_input_notes(NoteFilter::Consumed).await.unwrap().is_empty(),
"Authenticated notes are consumed"
);
assert!(
!unauth_client.get_input_notes(NoteFilter::Consumed).await.unwrap().is_empty(),
"Unauthenticated notes are consumed"
);
for (client, account_id) in [(client, to_account_ids[0]), (unauth_client, to_account_ids[1])] {
assert_account_has_single_asset(
&client,
account_id,
faucet_account_id,
TRANSFER_AMOUNT * 2,
)
.await;
}
Ok(())
}
pub async fn test_import_consumed_note_with_proof(client_config: ClientConfig) -> Result<()> {
let (mut client_1, authenticator_1) = client_config.clone().into_client().await?;
let (first_regular_account, faucet_account_header) = setup_wallet_and_faucet(
&mut client_1,
AccountStorageMode::Private,
&authenticator_1,
RPO_FALCON_SCHEME_ID,
)
.await?;
let (mut client_2, authenticator_2) = ClientConfig::default()
.with_rpc_endpoint(client_config.rpc_endpoint())
.into_client()
.await?;
let (client_2_account, _) = insert_new_wallet(
&mut client_2,
AccountStorageMode::Private,
&authenticator_2,
RPO_FALCON_SCHEME_ID,
)
.await?;
wait_for_node(&mut client_2).await;
let from_account_id = first_regular_account.id();
let to_account_id = client_2_account.id();
let faucet_account_id = faucet_account_header.id();
let tx_id =
mint_and_consume(&mut client_1, from_account_id, faucet_account_id, NoteType::Private)
.await;
wait_for_tx(&mut client_1, tx_id).await?;
let current_block_num = client_1.get_sync_height().await.unwrap();
let asset = FungibleAsset::new(faucet_account_id, TRANSFER_AMOUNT).unwrap();
info!(from = %from_account_id, to = %to_account_id, "Running P2IDE transaction");
let tx_request = TransactionRequestBuilder::new().build_pay_to_id(
PaymentNoteDescription::new(vec![Asset::Fungible(asset)], from_account_id, to_account_id)
.with_reclaim_height(current_block_num),
NoteType::Private,
client_1.rng(),
)?;
execute_tx_and_sync(&mut client_1, from_account_id, tx_request).await?;
let note = client_1
.get_input_notes(NoteFilter::Committed)
.await
.unwrap()
.first()
.unwrap()
.clone();
info!(note_id = %note.id(), account_id = %from_account_id, "Consuming note");
let tx_request = TransactionRequestBuilder::new()
.build_consume_notes(vec![note.clone().try_into().unwrap()])
.unwrap();
execute_tx_and_sync(&mut client_1, from_account_id, tx_request).await?;
client_2
.import_notes(&[NoteFile::NoteWithProof(
note.clone().try_into().unwrap(),
note.inclusion_proof().unwrap().clone(),
)])
.await?;
let consumed_note = client_2.get_input_note(note.id()).await.unwrap().unwrap();
assert!(matches!(consumed_note.state(), InputNoteState::ConsumedExternal { .. }));
Ok(())
}
pub async fn test_import_consumed_note_with_id(client_config: ClientConfig) -> Result<()> {
let (mut client_1, authenticator) = client_config.clone().into_client().await?;
let (first_regular_account, second_regular_account, faucet_account_header) =
setup_two_wallets_and_faucet(
&mut client_1,
AccountStorageMode::Private,
&authenticator,
RPO_FALCON_SCHEME_ID,
)
.await?;
let (mut client_2, _) = ClientConfig::default()
.with_rpc_endpoint(client_config.rpc_endpoint())
.into_client()
.await?;
wait_for_node(&mut client_2).await;
let from_account_id = first_regular_account.id();
let to_account_id = second_regular_account.id();
let faucet_account_id = faucet_account_header.id();
let tx_id =
mint_and_consume(&mut client_1, from_account_id, faucet_account_id, NoteType::Private)
.await;
wait_for_tx(&mut client_1, tx_id).await?;
let current_block_num = client_1.get_sync_height().await.unwrap();
let asset = FungibleAsset::new(faucet_account_id, TRANSFER_AMOUNT).unwrap();
info!(from = %from_account_id, to = %to_account_id, "Running P2IDE transaction (public)");
let tx_request = TransactionRequestBuilder::new().build_pay_to_id(
PaymentNoteDescription::new(vec![Asset::Fungible(asset)], from_account_id, to_account_id)
.with_reclaim_height(current_block_num),
NoteType::Public,
client_1.rng(),
)?;
execute_tx_and_sync(&mut client_1, from_account_id, tx_request).await?;
let note = client_1
.get_input_notes(NoteFilter::Committed)
.await
.unwrap()
.first()
.unwrap()
.clone();
info!(note_id = %note.id(), account_id = %from_account_id, "Consuming note");
let tx_request = TransactionRequestBuilder::new()
.build_consume_notes(vec![note.clone().try_into().unwrap()])
.unwrap();
execute_tx_and_sync(&mut client_1, from_account_id, tx_request).await?;
client_2.sync_state().await.unwrap();
client_2.import_notes(&[NoteFile::NoteId(note.id())]).await.unwrap();
let consumed_note = client_2.get_input_note(note.id()).await.unwrap().unwrap();
assert!(matches!(consumed_note.state(), InputNoteState::ConsumedExternal { .. }));
Ok(())
}
pub async fn test_import_note_with_proof(client_config: ClientConfig) -> Result<()> {
let (mut client_1, authenticator) = client_config.clone().into_client().await?;
let (first_regular_account, second_regular_account, faucet_account_header) =
setup_two_wallets_and_faucet(
&mut client_1,
AccountStorageMode::Private,
&authenticator,
RPO_FALCON_SCHEME_ID,
)
.await?;
let (mut client_2, _) = ClientConfig::default()
.with_rpc_endpoint(client_config.rpc_endpoint())
.into_client()
.await?;
wait_for_node(&mut client_2).await;
let from_account_id = first_regular_account.id();
let to_account_id = second_regular_account.id();
let faucet_account_id = faucet_account_header.id();
let tx_id =
mint_and_consume(&mut client_1, from_account_id, faucet_account_id, NoteType::Private)
.await;
wait_for_tx(&mut client_1, tx_id).await?;
let current_block_num = client_1.get_sync_height().await.unwrap();
let asset = FungibleAsset::new(faucet_account_id, TRANSFER_AMOUNT).unwrap();
info!(from = %from_account_id, to = %to_account_id, "Running P2IDE transaction (with proof)");
let tx_request = TransactionRequestBuilder::new().build_pay_to_id(
PaymentNoteDescription::new(vec![Asset::Fungible(asset)], from_account_id, to_account_id)
.with_reclaim_height(current_block_num),
NoteType::Private,
client_1.rng(),
)?;
execute_tx_and_sync(&mut client_1, from_account_id, tx_request).await?;
let note = client_1
.get_input_notes(NoteFilter::Committed)
.await
.unwrap()
.first()
.unwrap()
.clone();
client_2
.import_notes(&[NoteFile::NoteWithProof(
note.clone().try_into().unwrap(),
note.inclusion_proof().unwrap().clone(),
)])
.await?;
let imported_note = client_2.get_input_note(note.id()).await.unwrap().unwrap();
assert!(matches!(imported_note.state(), InputNoteState::Unverified { .. }));
client_2.sync_state().await.unwrap();
let imported_note = client_2.get_input_note(note.id()).await.unwrap().unwrap();
assert!(matches!(imported_note.state(), InputNoteState::Committed { .. }));
Ok(())
}
pub async fn test_discarded_transaction(client_config: ClientConfig) -> Result<()> {
let (mut client_1, authenticator_1) = client_config.clone().into_client().await?;
let (first_regular_account, faucet_account_header) = setup_wallet_and_faucet(
&mut client_1,
AccountStorageMode::Private,
&authenticator_1,
RPO_FALCON_SCHEME_ID,
)
.await?;
let (mut client_2, authenticator_2) = ClientConfig::default()
.with_rpc_endpoint(client_config.rpc_endpoint())
.into_client()
.await?;
let (second_regular_account, ..) = insert_new_wallet(
&mut client_2,
AccountStorageMode::Private,
&authenticator_2,
RPO_FALCON_SCHEME_ID,
)
.await?;
wait_for_node(&mut client_2).await;
let from_account_id = first_regular_account.id();
let to_account_id = second_regular_account.id();
let faucet_account_id = faucet_account_header.id();
let tx_id =
mint_and_consume(&mut client_1, from_account_id, faucet_account_id, NoteType::Private)
.await;
wait_for_tx(&mut client_1, tx_id).await?;
let current_block_num = client_1.get_sync_height().await.unwrap();
let asset = FungibleAsset::new(faucet_account_id, TRANSFER_AMOUNT).unwrap();
info!(from = %from_account_id, to = %to_account_id, "Running P2IDE transaction (discarded)");
let tx_request = TransactionRequestBuilder::new().build_pay_to_id(
PaymentNoteDescription::new(vec![Asset::Fungible(asset)], from_account_id, to_account_id)
.with_reclaim_height(current_block_num),
NoteType::Public,
client_1.rng(),
)?;
execute_tx_and_sync(&mut client_1, from_account_id, tx_request).await?;
client_2.sync_state().await.unwrap();
let note = client_1
.get_input_notes(NoteFilter::Committed)
.await
.unwrap()
.first()
.unwrap()
.clone();
info!(note_id = %note.id(), account_id = %from_account_id, "Consuming note (without submitting)");
let tx_request = TransactionRequestBuilder::new()
.build_consume_notes(vec![note.clone().try_into().unwrap()])
.unwrap();
let transaction_result =
client_1.execute_transaction(from_account_id, tx_request.clone()).await.unwrap();
let tx_id = transaction_result.id();
let account_hash_before_tx =
client_1.account_reader(from_account_id).commitment().await.unwrap();
let submission_height = client_1.get_sync_height().await.unwrap();
client_1
.apply_transaction(&transaction_result, submission_height)
.await
.unwrap();
let account_hash_after_tx =
client_1.account_reader(from_account_id).commitment().await.unwrap();
assert_ne!(
account_hash_before_tx, account_hash_after_tx,
"Account hash should change after applying the transaction"
);
let note_record = client_1.get_input_note(note.id()).await.unwrap().unwrap();
assert!(matches!(note_record.state(), InputNoteState::ProcessingAuthenticated(_)));
execute_tx_and_sync(&mut client_2, to_account_id, tx_request).await?;
let note_record = client_2.get_input_note(note.id()).await.unwrap().unwrap();
assert!(matches!(note_record.state(), InputNoteState::ConsumedAuthenticatedLocal(_)));
client_1.sync_state().await.unwrap();
let note_record = client_1.get_input_note(note.id()).await.unwrap().unwrap();
assert!(matches!(note_record.state(), InputNoteState::ConsumedExternal(_)));
let tx_record = client_1
.get_transactions(TransactionFilter::All)
.await
.unwrap()
.into_iter()
.find(|tx| tx.id == tx_id)
.with_context(|| {
format!("Transaction with id {tx_id} not found in discarded transactions")
})?;
assert!(matches!(
tx_record.status,
TransactionStatus::Discarded(DiscardCause::InputConsumed)
));
let account_hash_after_sync =
client_1.account_reader(from_account_id).commitment().await.unwrap();
assert_ne!(
account_hash_after_sync, account_hash_after_tx,
"Account hash should change after transaction was discarded"
);
assert_eq!(
account_hash_after_sync, account_hash_before_tx,
"Account hash should be rolled back to the value before the transaction"
);
Ok(())
}
struct AlwaysFailingProver;
impl AlwaysFailingProver {
pub fn new() -> Self {
Self
}
}
#[async_trait::async_trait]
impl TransactionProver for AlwaysFailingProver {
async fn prove(
&self,
_inputs: TransactionInputs,
) -> Result<ProvenTransaction, TransactionProverError> {
return Err(TransactionProverError::other("This prover always fails"));
}
}
pub async fn test_custom_transaction_prover_error_caught(
client_config: ClientConfig,
) -> Result<()> {
let (mut client, authenticator) = client_config.into_client().await?;
let (first_regular_account, faucet_account_header) = setup_wallet_and_faucet(
&mut client,
AccountStorageMode::Private,
&authenticator,
RPO_FALCON_SCHEME_ID,
)
.await?;
let from_account_id = first_regular_account.id();
let faucet_account_id = faucet_account_header.id();
let fungible_asset = FungibleAsset::new(faucet_account_id, MINT_AMOUNT).unwrap();
let tx_request = TransactionRequestBuilder::new().build_mint_fungible_asset(
fungible_asset,
from_account_id,
NoteType::Private,
client.rng(),
)?;
let transaction_result =
client.execute_transaction(faucet_account_id, tx_request.clone()).await.unwrap();
let result = client
.prove_transaction_with(&transaction_result, Arc::new(AlwaysFailingProver::new()))
.await;
let Err(ClientError::TransactionProvingError(TransactionProverError::Other {
error_msg, ..
})) = result
else {
panic!("expected different prover error");
};
assert_eq!(error_msg.as_ref(), "This prover always fails");
Ok(())
}
pub async fn test_locked_account(client_config: ClientConfig) -> Result<()> {
let (mut client_1, authenticator) = client_config.clone().into_client().await?;
let (faucet_account, _) = insert_new_fungible_faucet(
&mut client_1,
AccountStorageMode::Private,
&authenticator,
RPO_FALCON_SCHEME_ID,
)
.await?;
let (private_account, _) = insert_new_wallet(
&mut client_1,
AccountStorageMode::Private,
&authenticator,
RPO_FALCON_SCHEME_ID,
)
.await?;
let from_account_id = private_account.id();
let faucet_account_id = faucet_account.id();
wait_for_node(&mut client_1).await;
let tx_id =
mint_and_consume(&mut client_1, from_account_id, faucet_account_id, NoteType::Private)
.await;
wait_for_tx(&mut client_1, tx_id).await?;
let private_account: Account =
client_1.get_account(from_account_id).await?.context("Account not found")?;
let original_seed = private_account.seed();
let (mut client_2, _) = ClientConfig::default()
.with_rpc_endpoint(client_config.rpc_endpoint())
.into_client()
.await?;
client_2.add_account(&private_account, false).await.unwrap();
wait_for_node(&mut client_2).await;
assert!(!client_2.account_reader(from_account_id).status().await.unwrap().is_locked());
let tx_id =
mint_and_consume(&mut client_1, from_account_id, faucet_account_id, NoteType::Private)
.await;
wait_for_tx(&mut client_1, tx_id).await?;
let summary = client_2.sync_state().await.unwrap();
assert!(summary.locked_accounts.contains(&from_account_id));
let status = client_2.account_reader(from_account_id).status().await.unwrap();
assert!(status.is_locked());
assert_eq!(status.seed(), original_seed.as_ref());
let updated_private_account: Account =
client_1.get_account(from_account_id).await?.context("Account not found")?;
client_2.add_account(&updated_private_account, true).await.unwrap();
client_2.sync_state().await.unwrap();
assert!(!client_2.account_reader(from_account_id).status().await.unwrap().is_locked());
Ok(())
}
pub async fn test_expired_transaction_fails(client_config: ClientConfig) -> Result<()> {
let (mut client, authenticator) = client_config.into_client().await?;
let (faucet_account, _) = insert_new_fungible_faucet(
&mut client,
AccountStorageMode::Private,
&authenticator,
RPO_FALCON_SCHEME_ID,
)
.await?;
let (private_account, ..) = insert_new_wallet(
&mut client,
AccountStorageMode::Private,
&authenticator,
RPO_FALCON_SCHEME_ID,
)
.await?;
let from_account_id = private_account.id();
let faucet_account_id = faucet_account.id();
wait_for_node(&mut client).await;
let expiration_delta = 2;
let fungible_asset = FungibleAsset::new(faucet_account_id, MINT_AMOUNT).unwrap();
info!(faucet_id = %faucet_account_id, target = %from_account_id, expiration_delta, "Minting asset with expiration");
let tx_request = TransactionRequestBuilder::new()
.expiration_delta(expiration_delta)
.build_mint_fungible_asset(
fungible_asset,
from_account_id,
NoteType::Public,
client.rng(),
)?;
info!("Executing transaction");
let transaction_result =
client.execute_transaction(faucet_account_id, tx_request).await.unwrap();
info!(tx_id = %transaction_result.id(), "Transaction executed, waiting for expiration");
wait_for_blocks(&mut client, (expiration_delta + 1).into()).await;
info!("Sending expired transaction to node (expecting failure)");
let proven_transaction = client.prove_transaction(&transaction_result).await.unwrap();
let submitted_tx_result =
match client.submit_proven_transaction(proven_transaction, &transaction_result).await {
Ok(submission_height) => {
client.apply_transaction(&transaction_result, submission_height).await
},
Err(err) => Err(err),
};
assert!(submitted_tx_result.is_err());
Ok(())
}
pub async fn test_unused_rpc_api(client_config: ClientConfig) -> Result<()> {
let (mut client, keystore) = client_config.into_client().await?;
let (first_basic_account, faucet_account) = setup_wallet_and_faucet(
&mut client,
AccountStorageMode::Public,
&keystore,
RPO_FALCON_SCHEME_ID,
)
.await?;
wait_for_node(&mut client).await;
client.sync_state().await.unwrap();
let first_block_num = client.get_sync_height().await.unwrap();
let (block_header, _) = client
.test_rpc_api()
.get_block_header_by_number(Some(first_block_num), false)
.await?;
let block = client.test_rpc_api().get_block_by_number(first_block_num).await.unwrap();
assert_eq!(&block_header, block.header());
let (tx_id, note) =
mint_note(&mut client, first_basic_account.id(), faucet_account.id(), NoteType::Public)
.await;
wait_for_tx(&mut client, tx_id).await?;
let tx_id =
consume_notes(&mut client, first_basic_account.id(), std::slice::from_ref(¬e)).await;
wait_for_tx(&mut client, tx_id).await?;
let custom_code = r#"
use miden::protocol::native_account
use miden::core::word
const MAP_SLOT = word("miden::testing::client::map")
pub proc update_map
push.1.2.3.4
# => [VALUE]
push.0.0.0.0
# => [KEY, VALUE]
push.MAP_SLOT[0..2]
exec.native_account::set_map_item
dropw dropw dropw dropw
end
"#;
let mut storage_map = StorageMap::new();
storage_map.insert(
StorageMapKey::new([Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)].into()),
[Felt::new(1), Felt::new(0), Felt::new(0), Felt::new(0)].into(),
)?;
let map_slot_name =
StorageSlotName::new("miden::testing::client::map").expect("slot name should be valid");
let storage_slots = vec![StorageSlot::with_map(map_slot_name, storage_map)];
let (account_with_map_item, _) = insert_account_with_custom_component(
&mut client,
custom_code,
storage_slots,
AccountStorageMode::Public,
&keystore,
)
.await?;
client.sync_state().await.unwrap();
let tx_script = CodeBuilder::new()
.with_linked_module("custom_library::set_map_item_library", custom_code)?
.compile_tx_script(
"
use custom_library::set_map_item_library
begin
call.set_map_item_library::update_map
end
",
)?;
let tx_request = TransactionRequestBuilder::new().custom_script(tx_script).build()?;
execute_tx_and_sync(&mut client, account_with_map_item.id(), tx_request.clone()).await?;
let faucet = insert_new_fungible_faucet(
&mut client,
AccountStorageMode::Private,
&keystore,
RPO_FALCON_SCHEME_ID,
)
.await?
.0;
let fungible_asset = FungibleAsset::new(faucet.id(), MINT_AMOUNT)?;
let tx_request = TransactionRequestBuilder::new().build_mint_fungible_asset(
fungible_asset,
first_basic_account.id(),
NoteType::Public,
client.rng(),
)?;
let note = tx_request.expected_output_own_notes().pop().unwrap();
execute_tx_and_sync(&mut client, fungible_asset.faucet_id(), tx_request.clone()).await?;
let tx_request = TransactionRequestBuilder::new().build_consume_notes(vec![note.clone()])?;
execute_tx_and_sync(&mut client, first_basic_account.id(), tx_request).await?;
let nullifier = note.nullifier();
let node_nullifier = client
.test_rpc_api()
.sync_nullifiers(&[nullifier.prefix()], 0.into(), None)
.await
.unwrap()
.pop()
.with_context(|| "no nullifier found in sync_nullifiers response")?;
let node_nullifier_proof = client
.test_rpc_api()
.check_nullifiers(&[nullifier])
.await
.unwrap()
.pop()
.with_context(|| "no nullifier proof returned from check_nullifiers RPC API")?;
let retrieved_note_script = client
.test_rpc_api()
.get_note_script_by_root(note.script().root())
.await
.unwrap();
let sync_storage_maps = client
.test_rpc_api()
.sync_storage_maps(0.into(), None, account_with_map_item.id())
.await
.unwrap();
let account_vault_info = client
.test_rpc_api()
.sync_account_vault(0.into(), None, first_basic_account.id())
.await
.unwrap();
let transactions_info = client
.test_rpc_api()
.sync_transactions(0.into(), None, vec![first_basic_account.id()])
.await
.unwrap();
assert_eq!(node_nullifier.nullifier, nullifier);
assert_eq!(node_nullifier_proof.leaf().entries().first().unwrap().0, nullifier.as_word());
assert_eq!(note.script().root(), retrieved_note_script.root());
assert!(!sync_storage_maps.updates.is_empty());
assert!(!account_vault_info.updates.is_empty());
assert!(!transactions_info.transaction_records.is_empty());
Ok(())
}
pub async fn test_ignore_invalid_notes(client_config: ClientConfig) -> Result<()> {
let (mut client, authenticator) = client_config.into_client().await?;
let (regular_account, second_regular_account, faucet_account_header) =
setup_two_wallets_and_faucet(
&mut client,
AccountStorageMode::Private,
&authenticator,
RPO_FALCON_SCHEME_ID,
)
.await?;
let account_id = regular_account.id();
let second_account_id = second_regular_account.id();
let faucet_account_id = faucet_account_header.id();
let (tx_id_1, note_1) =
mint_note(&mut client, account_id, faucet_account_id, NoteType::Private).await;
wait_for_tx(&mut client, tx_id_1).await?;
let (tx_id_2, note_2) =
mint_note(&mut client, account_id, faucet_account_id, NoteType::Private).await;
wait_for_tx(&mut client, tx_id_2).await?;
let (tx_id_3, note_3) =
mint_note(&mut client, second_account_id, faucet_account_id, NoteType::Private).await;
wait_for_tx(&mut client, tx_id_3).await?;
let (tx_id_4, note_4) =
mint_note(&mut client, second_account_id, faucet_account_id, NoteType::Private).await;
wait_for_tx(&mut client, tx_id_4).await?;
let tx_request = TransactionRequestBuilder::new()
.ignore_invalid_input_notes()
.build_consume_notes(vec![
note_1.clone(),
note_3.clone(),
note_2.clone(),
note_4.clone(),
])?;
execute_tx_and_sync(&mut client, account_id, tx_request).await?;
let consumed_notes = client.get_input_notes(NoteFilter::Consumed).await.unwrap();
assert_eq!(consumed_notes.len(), 2);
assert!(consumed_notes.iter().any(|note| note.id() == note_1.id()));
assert!(consumed_notes.iter().any(|note| note.id() == note_2.id()));
Ok(())
}
pub async fn test_output_only_note(client_config: ClientConfig) -> Result<()> {
let (mut client, authenticator) = client_config.into_client().await?;
let faucet = insert_new_fungible_faucet(
&mut client,
AccountStorageMode::Private,
&authenticator,
RPO_FALCON_SCHEME_ID,
)
.await
.unwrap()
.0;
let fungible_asset = FungibleAsset::new(faucet.id(), MINT_AMOUNT).unwrap();
let tx_request = TransactionRequestBuilder::new().build_mint_fungible_asset(
fungible_asset,
AccountId::try_from(ACCOUNT_ID_REGULAR).unwrap(),
NoteType::Public,
client.rng(),
)?;
let note_id = tx_request.expected_output_own_notes().pop().unwrap().id();
execute_tx_and_sync(&mut client, fungible_asset.faucet_id(), tx_request.clone()).await?;
let input_note = client.get_input_note(note_id).await.unwrap();
assert!(input_note.is_none());
let output_note = client.get_output_note(note_id).await.unwrap();
assert!(output_note.is_some());
Ok(())
}
pub async fn test_get_account_storage_map_key_filtering(client_config: ClientConfig) -> Result<()> {
let (mut client, keystore) = client_config.into_client().await?;
wait_for_node(&mut client).await;
let map_slot_name =
StorageSlotName::new("miden::testing::client::map").expect("valid slot name");
let map_key_1 =
StorageMapKey::new([Felt::new(15), Felt::new(15), Felt::new(15), Felt::new(15)].into());
let map_value_1 = Word::from([Felt::new(9), Felt::new(12), Felt::new(18), Felt::new(30)]);
let map_key_2 =
StorageMapKey::new([Felt::new(20), Felt::new(20), Felt::new(20), Felt::new(20)].into());
let map_value_2 = Word::from([Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]);
let mut storage_map = StorageMap::new();
storage_map.insert(map_key_1, map_value_1)?;
storage_map.insert(map_key_2, map_value_2)?;
let map_slot = StorageSlot::with_map(map_slot_name.clone(), storage_map);
let component_code = CodeBuilder::default()
.compile_component_code(
"miden::testing::map_key_filtering",
"pub proc dummy\n push.1\n end".to_string(),
)
.context("failed to compile component code")?;
let component = AccountComponent::new(
component_code,
vec![map_slot],
AccountComponentMetadata::new("miden::testing::map_key_filtering", AccountType::all()),
)
.map_err(|err| anyhow::anyhow!(err))?;
let key_pair = AuthSecretKey::new_falcon512_poseidon2();
let auth_component: AccountComponent =
AuthSingleSig::new(key_pair.public_key().to_commitment(), AuthSchemeId::Falcon512Poseidon2)
.into();
let account = AccountBuilder::new(Default::default())
.with_component(component)
.with_auth_component(auth_component)
.storage_mode(AccountStorageMode::Public)
.build()
.context("failed to build account")?;
let account_id = account.id();
keystore.add_key(&key_pair, account_id).await.context("failed to add key")?;
client.add_account(&account, false).await?;
let tx_id = client
.submit_new_transaction(account_id, TransactionRequestBuilder::new().build()?)
.await?;
wait_for_tx(&mut client, tx_id).await?;
let rpc = client.test_rpc_api();
let requirements_all = AccountStorageRequirements::new([(map_slot_name.clone(), [].iter())]);
let (_, proof_all) = rpc
.get_account_proof(account_id, requirements_all, AccountStateAt::ChainTip, None, None)
.await?;
let map_all = proof_all
.find_map_details(&map_slot_name)
.context("expected storage map details")?;
assert!(
matches!(map_all.entries, StorageMapEntries::AllEntries(ref e) if e.len() == 2),
"expected AllEntries with 2 entries, got {:?}",
map_all.entries,
);
let requirements_one = AccountStorageRequirements::new([(map_slot_name.clone(), [&map_key_1])]);
let (_, proof_one) = rpc
.get_account_proof(account_id, requirements_one, AccountStateAt::ChainTip, None, None)
.await?;
let map_one = proof_one
.find_map_details(&map_slot_name)
.context("expected storage map details")?;
match &map_one.entries {
StorageMapEntries::EntriesWithProofs(proofs) => {
assert_eq!(proofs.len(), 1, "expected 1 proof");
let hashed_key = map_key_1.hash().as_word();
let value = proofs[0].get(&hashed_key);
assert!(value.is_some(), "proof should contain the requested key");
assert_eq!(value.unwrap(), map_value_1, "value should match the requested key's value");
},
other => anyhow::bail!("expected EntriesWithProofs, got {:?}", other),
}
Ok(())
}
pub async fn test_get_account_proof_returns_vault_details(
client_config: ClientConfig,
) -> Result<()> {
let (mut client, keystore) = client_config.into_client().await?;
wait_for_node(&mut client).await;
let (wallet, faucet) = setup_wallet_and_faucet(
&mut client,
AccountStorageMode::Public,
&keystore,
RPO_FALCON_SCHEME_ID,
)
.await?;
let tx_id = mint_and_consume(&mut client, wallet.id(), faucet.id(), NoteType::Public).await;
wait_for_tx(&mut client, tx_id).await?;
let rpc = client.test_rpc_api();
let (_, proof) = rpc
.get_account_proof(
wallet.id(),
AccountStorageRequirements::default(),
AccountStateAt::ChainTip,
None,
Some(EMPTY_WORD),
)
.await?;
let (_, details) = proof.into_parts();
let details = details.context("expected account details for public account")?;
let vault_root = details.header.vault_root();
assert_eq!(
details.vault_details.assets,
vec![Asset::Fungible(FungibleAsset::new(faucet.id(), MINT_AMOUNT).unwrap())],
"expected exactly 1 asset (the minted fungible token)"
);
let (_, proof) = rpc
.get_account_proof(
wallet.id(),
AccountStorageRequirements::default(),
AccountStateAt::ChainTip,
None,
Some(vault_root),
)
.await?;
let (_, details) = proof.into_parts();
let details = details.context("expected account details for public account")?;
assert!(
details.vault_details.assets.is_empty(),
"expected empty assets when vault commitment matches"
);
let (_, proof) = rpc
.get_account_proof(
wallet.id(),
AccountStorageRequirements::default(),
AccountStateAt::ChainTip,
None,
None,
)
.await?;
let (_, details) = proof.into_parts();
let details = details.context("expected account details for public account")?;
assert!(
details.vault_details.assets.is_empty(),
"expected empty assets when vault commitment is not requested"
);
Ok(())
}
pub async fn test_prune_account_history(client_config: ClientConfig) -> Result<()> {
let (mut client, authenticator) = client_config.into_client().await?;
wait_for_node(&mut client).await;
let (basic_account, faucet_account) = setup_wallet_and_faucet(
&mut client,
AccountStorageMode::Private,
&authenticator,
RPO_FALCON_SCHEME_ID,
)
.await?;
let faucet_id = faucet_account.id();
let wallet_id = basic_account.id();
let (tx_id_1, _) = mint_note(&mut client, wallet_id, faucet_id, NoteType::Public).await;
wait_for_tx(&mut client, tx_id_1).await?;
let (tx_id_2, _) = mint_note(&mut client, wallet_id, faucet_id, NoteType::Public).await;
wait_for_tx(&mut client, tx_id_2).await?;
let faucet_before = client.get_account(faucet_id).await?.unwrap();
let deleted = client.prune_account_history(faucet_id, Felt::new(1)).await?;
assert!(deleted > 0, "Should have pruned old committed states");
let faucet_after = client.get_account(faucet_id).await?.unwrap();
assert_eq!(
faucet_before.to_commitment(),
faucet_after.to_commitment(),
"Account state should be identical after pruning"
);
assert!(client.get_account(wallet_id).await?.is_some());
assert!(client.get_account(faucet_id).await?.is_some());
info!(deleted_single = deleted, "Prune account history test completed");
Ok(())
}