use anyhow::{Context, Result};
use miden_client::EMPTY_WORD;
use miden_client::account::{Account, AccountStorageMode, build_wallet_id};
use miden_client::asset::{Asset, FungibleAsset};
use miden_client::auth::RPO_FALCON_SCHEME_ID;
use miden_client::note::{NoteFile, NoteType};
use miden_client::rpc::{AcceptHeaderError, RpcError};
use miden_client::store::{InputNoteState, NoteFilter};
use miden_client::testing::common::*;
use miden_client::transaction::{InputNote, PaymentNoteDescription, TransactionRequestBuilder};
use rand::RngCore;
use crate::tests::config::ClientConfig;
pub async fn test_onchain_notes_flow(client_config: ClientConfig) -> Result<()> {
let (mut client_1, keystore_1) = client_config.clone().into_client().await?;
let (mut client_2, keystore_2) = ClientConfig::default()
.with_rpc_endpoint(client_config.rpc_endpoint())
.into_client()
.await?;
let (mut client_3, keystore_3) = ClientConfig::default()
.with_rpc_endpoint(client_config.rpc_endpoint())
.into_client()
.await?;
wait_for_node(&mut client_3).await;
let (faucet_account, _) = insert_new_fungible_faucet(
&mut client_1,
AccountStorageMode::Private,
&keystore_1,
RPO_FALCON_SCHEME_ID,
)
.await?;
let (basic_wallet_1, ..) = insert_new_wallet(
&mut client_2,
AccountStorageMode::Private,
&keystore_2,
RPO_FALCON_SCHEME_ID,
)
.await?;
let (basic_wallet_2, ..) = insert_new_wallet(
&mut client_3,
AccountStorageMode::Private,
&keystore_3,
RPO_FALCON_SCHEME_ID,
)
.await?;
client_1.sync_state().await?;
client_2.sync_state().await?;
let tx_request = TransactionRequestBuilder::new().build_mint_fungible_asset(
FungibleAsset::new(faucet_account.id(), MINT_AMOUNT)?,
basic_wallet_1.id(),
NoteType::Public,
client_1.rng(),
)?;
let note = tx_request
.expected_output_own_notes()
.pop()
.with_context(|| "no expected output notes found in onchain transaction from faucet")?
.clone();
execute_tx_and_sync(&mut client_1, faucet_account.id(), tx_request).await?;
client_2.sync_state().await?;
let received_note: InputNote = client_2
.get_input_note(note.id())
.await?
.with_context(|| format!("Note {} not found in client_2", note.id()))?
.try_into()?;
assert_eq!(received_note.note().commitment(), note.commitment());
let tx_id =
consume_notes(&mut client_2, basic_wallet_1.id(), &[received_note.note().clone()]).await;
wait_for_tx(&mut client_2, tx_id).await?;
assert_account_has_single_asset(
&client_2,
basic_wallet_1.id(),
faucet_account.id(),
MINT_AMOUNT,
)
.await;
let p2id_asset = FungibleAsset::new(faucet_account.id(), TRANSFER_AMOUNT)?;
let tx_request = TransactionRequestBuilder::new().build_pay_to_id(
PaymentNoteDescription::new(
vec![p2id_asset.into()],
basic_wallet_1.id(),
basic_wallet_2.id(),
),
NoteType::Public,
client_2.rng(),
)?;
execute_tx_and_sync(&mut client_2, basic_wallet_1.id(), tx_request).await?;
let tx_request = TransactionRequestBuilder::new().build_pay_to_id(
PaymentNoteDescription::new(
vec![p2id_asset.into()],
basic_wallet_1.id(),
basic_wallet_2.id(),
)
.with_reclaim_height(1.into()),
NoteType::Public,
client_2.rng(),
)?;
let note = tx_request
.expected_output_own_notes()
.pop()
.with_context(|| "no expected output notes found in onchain transaction from basic wallet")?
.clone();
execute_tx_and_sync(&mut client_2, basic_wallet_1.id(), tx_request).await?;
let tx_request = TransactionRequestBuilder::new().build_consume_notes(vec![note.clone()])?;
execute_tx_and_sync(&mut client_2, basic_wallet_1.id(), tx_request).await?;
client_3.sync_state().await?;
assert_eq!(client_3.get_input_notes(NoteFilter::Committed).await?.len(), 1);
assert_eq!(client_3.get_input_notes(NoteFilter::Consumed).await?.len(), 1);
let note = client_3
.get_input_notes(NoteFilter::Committed)
.await?
.first()
.with_context(|| "no committed input notes found")?
.clone()
.try_into()?;
let tx_id = consume_notes(&mut client_3, basic_wallet_2.id(), &[note]).await;
wait_for_tx(&mut client_3, tx_id).await?;
assert_account_has_single_asset(
&client_3,
basic_wallet_2.id(),
faucet_account.id(),
TRANSFER_AMOUNT,
)
.await;
Ok(())
}
pub async fn test_onchain_accounts(client_config: ClientConfig) -> Result<()> {
let (mut client_1, keystore_1) = client_config.clone().into_client().await?;
let (mut client_2, keystore_2) = ClientConfig::default()
.with_rpc_endpoint(client_config.rpc_endpoint())
.into_client()
.await?;
wait_for_node(&mut client_2).await;
let (faucet_account_header, secret_key) = insert_new_fungible_faucet(
&mut client_1,
AccountStorageMode::Public,
&keystore_1,
RPO_FALCON_SCHEME_ID,
)
.await?;
let (first_regular_account, ..) = insert_new_wallet(
&mut client_1,
AccountStorageMode::Private,
&keystore_1,
RPO_FALCON_SCHEME_ID,
)
.await?;
let (second_client_first_regular_account, ..) = insert_new_wallet(
&mut client_2,
AccountStorageMode::Private,
&keystore_2,
RPO_FALCON_SCHEME_ID,
)
.await?;
let target_account_id = first_regular_account.id();
let second_client_target_account_id = second_client_first_regular_account.id();
let faucet_account_id = faucet_account_header.id();
keystore_2.add_key(&secret_key)?;
client_2.add_account(&faucet_account_header, false).await?;
println!("First client consuming note");
client_1.sync_state().await?;
let (tx_id, note) =
mint_note(&mut client_1, target_account_id, faucet_account_id, NoteType::Private).await;
wait_for_tx(&mut client_1, tx_id).await?;
client_2.sync_state().await?;
let (client_1_faucet, _) = client_1
.get_account_header_by_id(faucet_account_header.id())
.await?
.context("failed to find faucet account in client 1 after sync")?;
let (client_2_faucet, _) = client_2
.get_account_header_by_id(faucet_account_header.id())
.await?
.context("failed to find faucet account in client 2 after sync")?;
assert_eq!(client_1_faucet.commitment(), client_2_faucet.commitment());
println!("Second client consuming note");
let (tx_id, second_client_note) = mint_note(
&mut client_2,
second_client_target_account_id,
faucet_account_id,
NoteType::Private,
)
.await;
wait_for_tx(&mut client_2, tx_id).await?;
client_1.sync_state().await?;
println!("About to consume");
let tx_id = consume_notes(&mut client_1, target_account_id, &[note]).await;
wait_for_tx(&mut client_1, tx_id).await?;
assert_account_has_single_asset(&client_1, target_account_id, faucet_account_id, MINT_AMOUNT)
.await;
let tx_id =
consume_notes(&mut client_2, second_client_target_account_id, &[second_client_note]).await;
wait_for_tx(&mut client_2, tx_id).await?;
assert_account_has_single_asset(
&client_2,
second_client_target_account_id,
faucet_account_id,
MINT_AMOUNT,
)
.await;
let (client_1_faucet, _) = client_1
.get_account_header_by_id(faucet_account_header.id())
.await?
.context("failed to find faucet account in client 1 after consume transactions")?;
let (client_2_faucet, _) = client_2
.get_account_header_by_id(faucet_account_header.id())
.await?
.context("failed to find faucet account in client 2 after consume transactions")?;
assert_eq!(client_1_faucet.commitment(), client_2_faucet.commitment());
let from_account_id = target_account_id;
let to_account_id = second_client_target_account_id;
let account_client_1: Account = client_1
.get_account(from_account_id)
.await?
.context("failed to find from account for balance check")?
.try_into()?;
let from_account_balance = account_client_1.vault().get_balance(faucet_account_id).unwrap_or(0);
let account_client_2: Account = client_2
.get_account(to_account_id)
.await?
.context("failed to find from account for balance check")?
.try_into()?;
let to_account_balance = account_client_2.vault().get_balance(faucet_account_id).unwrap_or(0);
let asset = FungibleAsset::new(faucet_account_id, TRANSFER_AMOUNT)?;
println!("Running P2ID tx...");
let tx_request = TransactionRequestBuilder::new().build_pay_to_id(
PaymentNoteDescription::new(vec![Asset::Fungible(asset)], from_account_id, to_account_id),
NoteType::Public,
client_1.rng(),
)?;
execute_tx_and_sync(&mut client_1, from_account_id, tx_request).await?;
println!("Syncing on second client...");
client_2.sync_state().await?;
let notes = client_2.get_input_notes(NoteFilter::Committed).await?;
client_1.import_notes(&[NoteFile::NoteId(notes[0].id())]).await?;
println!("Consuming note on second client...");
let tx_request = TransactionRequestBuilder::new()
.build_consume_notes(vec![notes[0].clone().try_into().unwrap()])?;
execute_tx_and_sync(&mut client_2, to_account_id, tx_request).await?;
println!("Syncing on first client...");
client_1.sync_state().await?;
let input_note = client_1
.get_input_note(notes[0].id())
.await?
.with_context(|| format!("input note {} not found", notes[0].id()))?;
assert!(matches!(input_note.state(), InputNoteState::ConsumedExternal { .. }));
let account_client_1: Account = client_1
.get_account(from_account_id)
.await?
.context("failed to find from account after transfer")?
.try_into()?;
let new_from_account_balance =
account_client_1.vault().get_balance(faucet_account_id).unwrap_or(0);
let account_client_2: Account = client_2
.get_account(to_account_id)
.await?
.context("failed to find from account after transfer")?
.try_into()?;
let new_to_account_balance =
account_client_2.vault().get_balance(faucet_account_id).unwrap_or(0);
assert_eq!(new_from_account_balance, from_account_balance - TRANSFER_AMOUNT);
assert_eq!(new_to_account_balance, to_account_balance + TRANSFER_AMOUNT);
Ok(())
}
pub async fn test_import_account_by_id(client_config: ClientConfig) -> Result<()> {
let (mut client_1, keystore_1) = client_config.clone().into_client().await?;
let (mut client_2, keystore_2) = ClientConfig::default()
.with_rpc_endpoint(client_config.rpc_endpoint())
.into_client()
.await?;
wait_for_node(&mut client_1).await;
let mut user_seed = [0u8; 32];
client_1.rng().fill_bytes(&mut user_seed);
let (faucet_account_header, _) = insert_new_fungible_faucet(
&mut client_1,
AccountStorageMode::Public,
&keystore_1,
RPO_FALCON_SCHEME_ID,
)
.await?;
let (first_regular_account, secret_key) = insert_new_wallet_with_seed(
&mut client_1,
AccountStorageMode::Public,
&keystore_1,
user_seed,
RPO_FALCON_SCHEME_ID,
)
.await?;
let target_account_id = first_regular_account.id();
let faucet_account_id = faucet_account_header.id();
let tx_id =
mint_and_consume(&mut client_1, target_account_id, faucet_account_id, NoteType::Public)
.await;
wait_for_tx(&mut client_1, tx_id).await?;
let (tx_id, note) =
mint_note(&mut client_1, target_account_id, faucet_account_id, NoteType::Public).await;
wait_for_tx(&mut client_1, tx_id).await?;
let built_wallet_id =
build_wallet_id(user_seed, &secret_key.public_key(), AccountStorageMode::Public, false)?;
assert_eq!(built_wallet_id, first_regular_account.id());
client_2.import_account_by_id(built_wallet_id).await?;
keystore_2.add_key(&secret_key)?;
let original_account =
client_1.get_account(first_regular_account.id()).await?.with_context(|| {
format!("Original account {} not found in client_1", first_regular_account.id())
})?;
let imported_account =
client_2.get_account(first_regular_account.id()).await?.with_context(|| {
format!("Imported account {} not found in client_2", first_regular_account.id())
})?;
assert_eq!(imported_account.commitment(), original_account.commitment());
println!("Second client consuming note");
client_2.sync_state().await?;
let tx_id = consume_notes(&mut client_2, target_account_id, &[note]).await;
wait_for_tx(&mut client_2, tx_id).await?;
assert_account_has_single_asset(
&client_2,
target_account_id,
faucet_account_id,
MINT_AMOUNT * 2,
)
.await;
Ok(())
}
pub async fn test_incorrect_genesis(client_config: ClientConfig) -> Result<()> {
let (builder, _) = client_config.into_client_builder().await?;
let mut client = builder.build().await?;
client.test_rpc_api().set_genesis_commitment(EMPTY_WORD).await?;
let result = client.test_rpc_api().get_block_header_by_number(None, false).await;
match result {
Err(RpcError::AcceptHeaderError(AcceptHeaderError::NoSupportedMediaRange)) => Ok(()),
Ok(_) => anyhow::bail!("grpc request was unexpectedly successful"),
_ => anyhow::bail!("expected accept header error"),
}
}