use std::sync::Arc;
use miden_client::note::{
InputNoteReader,
NoteAssets,
NoteMetadata,
NoteRecipient,
NoteStorage,
NoteTag,
NoteType,
};
use miden_client::store::input_note_states::{
ConsumedExternalNoteState,
ConsumedUnauthenticatedLocalNoteState,
ExpectedNoteState,
NoteSubmissionData,
};
use miden_client::store::{InputNoteRecord, NoteFilter, Store};
use miden_client::{Felt, ZERO};
use miden_protocol::Word;
use miden_protocol::account::AccountId;
use miden_protocol::block::BlockNumber;
use miden_protocol::note::NoteDetails;
use miden_protocol::testing::account_id::{
ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE,
};
use miden_protocol::transaction::TransactionId;
use miden_standards::note::StandardNote;
use crate::tests::create_test_store;
fn create_consumed_external_input_note(
index: u32,
block_height: u32,
consumer_account: Option<AccountId>,
) -> InputNoteRecord {
let serial_number: Word = [Felt::new(u64::from(index) + 2000), ZERO, ZERO, ZERO].into();
let assets = NoteAssets::new(vec![]).unwrap();
let recipient = NoteRecipient::new(
serial_number,
StandardNote::SWAP.script(),
NoteStorage::new(vec![]).unwrap(),
);
let details = NoteDetails::new(assets, recipient);
let state = ConsumedExternalNoteState {
nullifier_block_height: BlockNumber::from(block_height),
consumer_account,
consumed_tx_order: None,
};
InputNoteRecord::new(details, Some(0), state.into())
}
fn create_expected_input_note(index: u32) -> InputNoteRecord {
let serial_number: Word = [Felt::new(u64::from(index) + 3000), ZERO, ZERO, ZERO].into();
let assets = NoteAssets::new(vec![]).unwrap();
let recipient = NoteRecipient::new(
serial_number,
StandardNote::SWAP.script(),
NoteStorage::new(vec![]).unwrap(),
);
let details = NoteDetails::new(assets, recipient);
let state = ExpectedNoteState {
metadata: None,
after_block_num: BlockNumber::from(0u32),
tag: None,
};
InputNoteRecord::new(details, Some(0), state.into())
}
fn create_consumed_input_note_with_consumer(
consumer: AccountId,
index: u32,
block_height: u32,
consumed_tx_order: u32,
) -> InputNoteRecord {
let serial_number: Word = [Felt::new(u64::from(index) + 5000), ZERO, ZERO, ZERO].into();
let assets = NoteAssets::new(vec![]).unwrap();
let recipient = NoteRecipient::new(
serial_number,
StandardNote::SWAP.script(),
NoteStorage::new(vec![]).unwrap(),
);
let details = NoteDetails::new(assets, recipient);
let metadata = NoteMetadata::new(consumer, NoteType::Public).with_tag(NoteTag::from(index));
let state = ConsumedUnauthenticatedLocalNoteState {
metadata,
nullifier_block_height: BlockNumber::from(block_height),
submission_data: NoteSubmissionData {
submitted_at: Some(0),
consumer_account: consumer,
consumer_transaction: TransactionId::from_raw(Word::default()),
},
consumed_tx_order: Some(consumed_tx_order),
};
InputNoteRecord::new(details, Some(0), state.into())
}
#[tokio::test]
async fn input_note_reader_returns_none_on_empty_store() {
let store = create_test_store().await;
let store: Arc<dyn Store> = Arc::new(store);
let consumer = AccountId::try_from(ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE).unwrap();
let mut reader = InputNoteReader::new(store, consumer);
let result = reader.next().await.unwrap();
assert!(result.is_none());
}
#[tokio::test]
async fn input_note_reader_iterates_all_consumed_notes() {
let store = create_test_store().await;
let consumer = AccountId::try_from(ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE).unwrap();
let notes: Vec<_> = (0..3u32)
.map(|i| create_consumed_input_note_with_consumer(consumer, i, 1, 0))
.collect();
store.upsert_input_notes(¬es).await.unwrap();
let store: Arc<dyn Store> = Arc::new(store);
let mut reader = InputNoteReader::new(store, consumer);
let mut collected = Vec::new();
while let Some(note) = reader.next().await.unwrap() {
collected.push(note);
}
assert_eq!(collected.len(), 3);
}
#[tokio::test]
async fn input_note_reader_skips_non_consumed_notes() {
let store = create_test_store().await;
let consumer = AccountId::try_from(ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE).unwrap();
let consumed1 = create_consumed_input_note_with_consumer(consumer, 0, 1, 0);
let expected = create_expected_input_note(1);
let consumed2 = create_consumed_input_note_with_consumer(consumer, 2, 1, 1);
store.upsert_input_notes(&[consumed1, expected, consumed2]).await.unwrap();
let store: Arc<dyn Store> = Arc::new(store);
let mut reader = InputNoteReader::new(store, consumer);
let mut collected = Vec::new();
while let Some(note) = reader.next().await.unwrap() {
collected.push(note);
}
assert_eq!(collected.len(), 2);
}
#[tokio::test]
async fn input_note_reader_filters_by_consumer() {
let store = create_test_store().await;
let consumer_a =
AccountId::try_from(ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE).unwrap();
let consumer_b = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap();
let note_a1 = create_consumed_input_note_with_consumer(consumer_a, 10, 1, 0);
let note_b = create_consumed_input_note_with_consumer(consumer_b, 11, 1, 0);
let note_a2 = create_consumed_input_note_with_consumer(consumer_a, 12, 1, 1);
store.upsert_input_notes(&[note_a1, note_b, note_a2]).await.unwrap();
let store: Arc<dyn Store> = Arc::new(store);
let mut reader = InputNoteReader::new(store, consumer_a);
let mut collected = Vec::new();
while let Some(note) = reader.next().await.unwrap() {
collected.push(note);
}
assert_eq!(collected.len(), 2);
for note in &collected {
assert_eq!(note.consumer_account(), Some(consumer_a));
}
}
#[tokio::test]
async fn input_note_reader_excludes_notes_without_tx_order_when_consumer_is_set() {
let store = create_test_store().await;
let consumer = AccountId::try_from(ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE).unwrap();
let note_with_order = create_consumed_input_note_with_consumer(consumer, 30, 1, 0);
let mut note_without_order = create_consumed_input_note_with_consumer(consumer, 31, 1, 0);
note_without_order.set_consumed_tx_order(None);
store
.upsert_input_notes(&[note_with_order.clone(), note_without_order])
.await
.unwrap();
let store: Arc<dyn Store> = Arc::new(store);
let mut reader = InputNoteReader::new(store, consumer);
let mut collected = Vec::new();
while let Some(note) = reader.next().await.unwrap() {
collected.push(note);
}
assert_eq!(collected.len(), 1);
assert_eq!(collected[0].id(), note_with_order.id());
}
#[tokio::test]
async fn input_note_reader_filters_by_block_range() {
let store = create_test_store().await;
let consumer = AccountId::try_from(ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE).unwrap();
let note_b1 = create_consumed_input_note_with_consumer(consumer, 0, 1, 0);
let note_b3 = create_consumed_input_note_with_consumer(consumer, 1, 3, 0);
let note_b5 = create_consumed_input_note_with_consumer(consumer, 2, 5, 0);
let note_b7 = create_consumed_input_note_with_consumer(consumer, 3, 7, 0);
store
.upsert_input_notes(&[note_b1, note_b3.clone(), note_b5.clone(), note_b7])
.await
.unwrap();
let store: Arc<dyn Store> = Arc::new(store);
let mut reader = InputNoteReader::new(store, consumer)
.in_block_range(BlockNumber::from(3u32), BlockNumber::from(5u32));
let mut collected = Vec::new();
while let Some(note) = reader.next().await.unwrap() {
collected.push(note);
}
assert_eq!(collected.len(), 2);
assert_eq!(collected[0].id(), note_b3.id());
assert_eq!(collected[1].id(), note_b5.id());
}
#[tokio::test]
async fn input_note_reader_filters_by_consumer_and_block_range() {
let store = create_test_store().await;
let consumer_a =
AccountId::try_from(ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE).unwrap();
let consumer_b = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap();
let alice_at_1 = create_consumed_input_note_with_consumer(consumer_a, 20, 1, 0);
let alice_at_3 = create_consumed_input_note_with_consumer(consumer_a, 21, 3, 0);
let bob_at_3 = create_consumed_input_note_with_consumer(consumer_b, 22, 3, 1);
let alice_at_5 = create_consumed_input_note_with_consumer(consumer_a, 23, 5, 0);
store
.upsert_input_notes(&[alice_at_1, alice_at_3.clone(), bob_at_3, alice_at_5.clone()])
.await
.unwrap();
let store: Arc<dyn Store> = Arc::new(store);
let mut reader = InputNoteReader::new(store, consumer_a)
.in_block_range(BlockNumber::from(3u32), BlockNumber::from(5u32));
let mut collected = Vec::new();
while let Some(note) = reader.next().await.unwrap() {
collected.push(note);
}
assert_eq!(collected.len(), 2);
assert_eq!(collected[0].id(), alice_at_3.id());
assert_eq!(collected[1].id(), alice_at_5.id());
for note in &collected {
assert_eq!(note.consumer_account(), Some(consumer_a));
}
}
#[tokio::test]
async fn input_note_reader_finds_externally_consumed_notes() {
let store = create_test_store().await;
let consumer = AccountId::try_from(ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE).unwrap();
let mut tracked_note = create_consumed_external_input_note(0, 1, Some(consumer));
tracked_note.set_consumed_tx_order(Some(0));
let mut untracked_note = create_consumed_external_input_note(1, 2, None);
untracked_note.set_consumed_tx_order(Some(0));
store
.upsert_input_notes(&[tracked_note.clone(), untracked_note.clone()])
.await
.unwrap();
let in_store = store.get_input_notes(NoteFilter::Consumed).await.unwrap();
assert_eq!(in_store.len(), 2);
let store: Arc<dyn Store> = Arc::new(store);
let mut reader = InputNoteReader::new(store, consumer);
let mut collected = Vec::new();
while let Some(n) = reader.next().await.unwrap() {
collected.push(n);
}
assert_eq!(
collected.len(),
1,
"InputNoteReader should return externally-consumed notes when the consumer account is tracked",
);
assert_eq!(collected[0].id(), tracked_note.id());
assert_eq!(collected[0].consumer_account(), Some(consumer));
}
#[tokio::test]
async fn consumed_input_notes_ordered_by_block_height_then_tx_order() {
let store = create_test_store().await;
let mut note_block3 = create_consumed_external_input_note(0, 3, None);
let mut note_block1 = create_consumed_external_input_note(1, 1, None);
let mut note_block2 = create_consumed_external_input_note(2, 2, None);
note_block3.set_consumed_tx_order(Some(0));
note_block1.set_consumed_tx_order(Some(1));
note_block2.set_consumed_tx_order(Some(0));
store
.upsert_input_notes(&[note_block3.clone(), note_block1.clone(), note_block2.clone()])
.await
.unwrap();
let notes = store.get_input_notes(NoteFilter::Consumed).await.unwrap();
assert_eq!(notes.len(), 3);
assert_eq!(notes[0].id(), note_block1.id()); assert_eq!(notes[1].id(), note_block2.id()); assert_eq!(notes[2].id(), note_block3.id()); }
#[tokio::test]
async fn consumed_input_notes_same_block_ordered_by_tx_order() {
let store = create_test_store().await;
let mut note_tx2 = create_consumed_external_input_note(10, 5, None);
let mut note_tx0 = create_consumed_external_input_note(11, 5, None);
let mut note_tx1 = create_consumed_external_input_note(12, 5, None);
note_tx2.set_consumed_tx_order(Some(2));
note_tx0.set_consumed_tx_order(Some(0));
note_tx1.set_consumed_tx_order(Some(1));
store
.upsert_input_notes(&[note_tx2.clone(), note_tx0.clone(), note_tx1.clone()])
.await
.unwrap();
let notes = store.get_input_notes(NoteFilter::Consumed).await.unwrap();
assert_eq!(notes.len(), 3);
assert_eq!(notes[0].id(), note_tx0.id()); assert_eq!(notes[1].id(), note_tx1.id()); assert_eq!(notes[2].id(), note_tx2.id()); }
#[tokio::test]
async fn consumed_input_notes_null_tx_order_sort_last_within_block() {
let store = create_test_store().await;
let mut note_with_order = create_consumed_external_input_note(20, 5, None);
let note_without_order = create_consumed_external_input_note(21, 5, None);
note_with_order.set_consumed_tx_order(Some(0));
store
.upsert_input_notes(&[note_with_order.clone(), note_without_order.clone()])
.await
.unwrap();
let notes = store.get_input_notes(NoteFilter::Consumed).await.unwrap();
assert_eq!(notes.len(), 2);
assert_eq!(notes[0].id(), note_with_order.id());
assert_eq!(notes[1].id(), note_without_order.id());
}