use anyhow::{Context, Result};
use miden_client::account::AccountType;
use miden_client::address::{Address, AddressInterface, RoutingParameters};
use miden_client::asset::FungibleAsset;
use miden_client::note::NoteType;
use miden_client::store::{InputNoteState, NoteFilter};
use miden_client::transaction::TransactionRequestBuilder;
use crate::ClientConfig;
pub async fn test_transport_note_inclusion_proof_and_consumption(
client_config: ClientConfig,
) -> Result<()> {
if client_config.note_transport_endpoint.is_none() {
eprintln!(
"Skipping note transport test (set TEST_MIDEN_NOTE_TRANSPORT_URL or use \
--note-transport-url to enable)"
);
return Ok(());
}
let sender_config = client_config.clone();
let recipient_config = client_config;
let mut sender =
sender_config.into_unsynced_client().await.context("failed to build sender")?;
let mut recipient = recipient_config
.into_unsynced_client()
.await
.context("failed to build recipient")?;
sender.wait_for_node().await;
let faucet_account = sender
.insert_faucet(AccountType::Private)
.await
.context("failed to insert faucet")?;
let recipient_account = recipient
.insert_wallet(AccountType::Private)
.await
.context("failed to insert wallet")?;
let recipient_address = Address::new(recipient_account.id())
.with_routing_parameters(RoutingParameters::new(AddressInterface::BasicWallet));
recipient.sync_state().await.context("recipient initial sync")?;
let fungible_asset = FungibleAsset::new(faucet_account.id(), 100).context("asset")?;
let tx_request = TransactionRequestBuilder::new()
.build_mint_fungible_asset(
fungible_asset,
recipient_account.id(),
NoteType::Private,
sender.rng(),
)
.context("build mint tx")?;
let note = tx_request
.expected_output_own_notes()
.last()
.cloned()
.context("expected output note missing")?;
sender
.execute_tx_and_sync(faucet_account.id(), tx_request)
.await
.context("mint tx failed")?;
let inclusion_proof = sender
.get_output_note(note.id())
.await?
.and_then(|record| record.inclusion_proof().cloned())
.context("minted note should carry an inclusion proof after commit")?;
sender
.send_private_note_with_proof(note.clone(), &recipient_address, inclusion_proof)
.await
.context("send_private_note_with_proof failed")?;
recipient.sync_state().await.context("recipient sync")?;
let notes = recipient.get_input_notes(NoteFilter::All).await?;
let received = notes
.iter()
.find(|n| n.id() == Some(note.id()))
.context("minted note not received by recipient")?;
assert!(
matches!(received.state(), InputNoteState::Committed(..)),
"note should be committed, got: {:?}",
received.state()
);
assert!(received.inclusion_proof().is_some(), "note should have inclusion proof");
let consumable = recipient.get_consumable_notes(Some(recipient_account.id())).await?;
assert!(
consumable.iter().any(|(n, _)| n.id() == Some(note.id())),
"minted note should be consumable",
);
let tx_id = recipient.consume_notes(recipient_account.id(), &[note]).await?;
recipient.wait_for_tx(tx_id).await?;
recipient
.assert_account_has_single_asset(recipient_account.id(), faucet_account.id(), 100)
.await;
Ok(())
}
pub async fn test_transport_multiple_notes_different_blocks(
client_config: ClientConfig,
) -> Result<()> {
if client_config.note_transport_endpoint.is_none() {
eprintln!(
"Skipping note transport test (set TEST_MIDEN_NOTE_TRANSPORT_URL or use \
--note-transport-url to enable)"
);
return Ok(());
}
let sender_config = client_config.clone();
let recipient_config = client_config;
let mut sender =
sender_config.into_unsynced_client().await.context("failed to build sender")?;
let mut recipient = recipient_config
.into_unsynced_client()
.await
.context("failed to build recipient")?;
sender.wait_for_node().await;
let faucet_account = sender
.insert_faucet(AccountType::Private)
.await
.context("failed to insert faucet")?;
let recipient_account = recipient
.insert_wallet(AccountType::Private)
.await
.context("failed to insert wallet")?;
let recipient_address = Address::new(recipient_account.id())
.with_routing_parameters(RoutingParameters::new(AddressInterface::BasicWallet));
recipient.sync_state().await.context("recipient initial sync")?;
let amounts = [10u64, 20, 30];
let mut minted_notes = Vec::new();
for amount in amounts {
let fungible_asset =
FungibleAsset::new(faucet_account.id(), amount).context("failed to create asset")?;
let tx_request = TransactionRequestBuilder::new()
.build_mint_fungible_asset(
fungible_asset,
recipient_account.id(),
NoteType::Private,
sender.rng(),
)
.context("build mint tx")?;
let note = tx_request
.expected_output_own_notes()
.last()
.cloned()
.context("expected output note missing")?;
sender
.execute_tx_and_sync(faucet_account.id(), tx_request)
.await
.context("mint tx failed")?;
minted_notes.push(note);
}
for note in &minted_notes {
let inclusion_proof = sender
.get_output_note(note.id())
.await?
.and_then(|record| record.inclusion_proof().cloned())
.context("minted note should carry an inclusion proof after commit")?;
sender
.send_private_note_with_proof(note.clone(), &recipient_address, inclusion_proof)
.await
.context("send_private_note_with_proof failed")?;
}
recipient.sync_state().await.context("recipient sync")?;
let input_notes = recipient.get_input_notes(NoteFilter::All).await?;
let minted_ids: std::collections::HashSet<_> = minted_notes.iter().map(|n| n.id()).collect();
let received: Vec<_> = input_notes
.iter()
.filter(|n| n.id().is_some_and(|id| minted_ids.contains(&id)))
.collect();
assert_eq!(
received.len(),
minted_ids.len(),
"all minted notes should be received, got {}/{}",
received.len(),
minted_ids.len(),
);
for input_note in &received {
assert!(
matches!(input_note.state(), InputNoteState::Committed(..)),
"note should be committed, got: {:?}",
input_note.state()
);
assert!(input_note.inclusion_proof().is_some(), "note should have inclusion proof");
}
let mut block_nums: Vec<_> = received
.iter()
.filter_map(|n| n.inclusion_proof().map(|p| p.location().block_num()))
.collect();
block_nums.sort();
block_nums.dedup();
assert!(
block_nums.len() >= 2,
"expected at least 2 different commit blocks, got {}",
block_nums.len()
);
let consumable = recipient.get_consumable_notes(Some(recipient_account.id())).await?;
let consumable_minted = consumable
.iter()
.filter(|(n, _)| n.id().is_some_and(|id| minted_ids.contains(&id)))
.count();
assert_eq!(
consumable_minted,
minted_ids.len(),
"all minted notes should be consumable, got {}/{}",
consumable_minted,
minted_ids.len(),
);
let tx_id = recipient.consume_notes(recipient_account.id(), &minted_notes).await?;
recipient.wait_for_tx(tx_id).await?;
recipient
.assert_account_has_single_asset(recipient_account.id(), faucet_account.id(), 60)
.await;
Ok(())
}