use alloc::collections::BTreeMap;
use miden_protocol::account::AccountId;
use miden_protocol::block::BlockHeader;
use miden_protocol::note::{NoteId, NoteInclusionProof, Nullifier};
use crate::ClientError;
use crate::rpc::RpcError;
use crate::rpc::domain::note::CommittedNote;
use crate::rpc::domain::nullifier::NullifierUpdate;
use crate::store::{InputNoteRecord, OutputNoteRecord};
use crate::transaction::{TransactionRecord, TransactionStatus};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum NoteUpdateType {
None,
Insert,
Update,
}
#[derive(Clone, Debug)]
pub struct InputNoteUpdate {
note: InputNoteRecord,
update_type: NoteUpdateType,
}
impl InputNoteUpdate {
fn new_none(note: InputNoteRecord) -> Self {
Self { note, update_type: NoteUpdateType::None }
}
fn new_insert(note: InputNoteRecord) -> Self {
Self {
note,
update_type: NoteUpdateType::Insert,
}
}
fn new_update(note: InputNoteRecord) -> Self {
Self {
note,
update_type: NoteUpdateType::Update,
}
}
pub fn inner(&self) -> &InputNoteRecord {
&self.note
}
fn inner_mut(&mut self) -> &mut InputNoteRecord {
self.update_type = match self.update_type {
NoteUpdateType::None | NoteUpdateType::Update => NoteUpdateType::Update,
NoteUpdateType::Insert => NoteUpdateType::Insert,
};
&mut self.note
}
pub fn update_type(&self) -> &NoteUpdateType {
&self.update_type
}
pub fn id(&self) -> NoteId {
self.note.id()
}
pub fn consumed_tx_order(&self) -> Option<u32> {
self.note.state().consumed_tx_order()
}
}
#[derive(Clone, Debug)]
pub struct OutputNoteUpdate {
note: OutputNoteRecord,
update_type: NoteUpdateType,
}
impl OutputNoteUpdate {
fn new_none(note: OutputNoteRecord) -> Self {
Self { note, update_type: NoteUpdateType::None }
}
fn new_insert(note: OutputNoteRecord) -> Self {
Self {
note,
update_type: NoteUpdateType::Insert,
}
}
fn new_update(note: OutputNoteRecord) -> Self {
Self {
note,
update_type: NoteUpdateType::Update,
}
}
pub fn inner(&self) -> &OutputNoteRecord {
&self.note
}
fn inner_mut(&mut self) -> &mut OutputNoteRecord {
self.update_type = match self.update_type {
NoteUpdateType::None | NoteUpdateType::Update => NoteUpdateType::Update,
NoteUpdateType::Insert => NoteUpdateType::Insert,
};
&mut self.note
}
pub fn update_type(&self) -> &NoteUpdateType {
&self.update_type
}
pub fn id(&self) -> NoteId {
self.note.id()
}
}
#[derive(Clone, Debug, Default)]
pub struct NoteUpdateTracker {
input_notes: BTreeMap<NoteId, InputNoteUpdate>,
output_notes: BTreeMap<NoteId, OutputNoteUpdate>,
input_notes_by_nullifier: BTreeMap<Nullifier, NoteId>,
output_notes_by_nullifier: BTreeMap<Nullifier, NoteId>,
nullifier_order: BTreeMap<Nullifier, u32>,
}
impl NoteUpdateTracker {
pub fn new(
input_notes: impl IntoIterator<Item = InputNoteRecord>,
output_notes: impl IntoIterator<Item = OutputNoteRecord>,
) -> Self {
let mut tracker = Self::default();
for note in input_notes {
tracker.insert_input_note(note, NoteUpdateType::None);
}
for note in output_notes {
tracker.insert_output_note(note, NoteUpdateType::None);
}
tracker
}
pub fn for_transaction_updates(
new_input_notes: impl IntoIterator<Item = InputNoteRecord>,
updated_input_notes: impl IntoIterator<Item = InputNoteRecord>,
new_output_notes: impl IntoIterator<Item = OutputNoteRecord>,
) -> Self {
let mut tracker = Self::default();
for note in new_input_notes {
tracker.insert_input_note(note, NoteUpdateType::Insert);
}
for note in updated_input_notes {
tracker.insert_input_note(note, NoteUpdateType::Update);
}
for note in new_output_notes {
tracker.insert_output_note(note, NoteUpdateType::Insert);
}
tracker
}
pub fn updated_input_notes(&self) -> impl Iterator<Item = &InputNoteUpdate> {
self.input_notes.values().filter(|note| {
matches!(note.update_type, NoteUpdateType::Insert | NoteUpdateType::Update)
})
}
pub fn updated_output_notes(&self) -> impl Iterator<Item = &OutputNoteUpdate> {
self.output_notes.values().filter(|note| {
matches!(note.update_type, NoteUpdateType::Insert | NoteUpdateType::Update)
})
}
pub fn is_empty(&self) -> bool {
self.input_notes.is_empty() && self.output_notes.is_empty()
}
pub fn unspent_nullifiers(&self) -> impl Iterator<Item = Nullifier> {
let input_note_unspent_nullifiers = self
.input_notes
.values()
.filter(|note| !note.inner().is_consumed())
.map(|note| note.inner().nullifier());
let output_note_unspent_nullifiers = self
.output_notes
.values()
.filter(|note| !note.inner().is_consumed())
.filter_map(|note| note.inner().nullifier());
input_note_unspent_nullifiers.chain(output_note_unspent_nullifiers)
}
pub fn extend_nullifiers(&mut self, nullifiers: impl IntoIterator<Item = Nullifier>) {
for nullifier in nullifiers {
let next_pos =
u32::try_from(self.nullifier_order.len()).expect("nullifier count exceeds u32");
self.nullifier_order.entry(nullifier).or_insert(next_pos);
}
}
pub(crate) fn apply_new_public_note(
&mut self,
mut public_note_data: InputNoteRecord,
block_header: &BlockHeader,
) -> Result<(), ClientError> {
public_note_data.block_header_received(block_header)?;
self.insert_input_note(public_note_data, NoteUpdateType::Insert);
Ok(())
}
pub(crate) fn apply_committed_note_state_transitions(
&mut self,
committed_note: &CommittedNote,
block_header: &BlockHeader,
) -> Result<bool, ClientError> {
let inclusion_proof = committed_note.inclusion_proof().clone();
let is_tracked_as_input_note =
if let Some(input_note_record) = self.get_input_note_by_id(*committed_note.note_id()) {
let metadata = committed_note.metadata().cloned().ok_or_else(|| {
ClientError::RpcError(RpcError::ExpectedDataMissing(format!(
"full metadata for committed note {}",
committed_note.note_id()
)))
})?;
input_note_record.inclusion_proof_received(inclusion_proof.clone(), metadata)?;
input_note_record.block_header_received(block_header)?;
true
} else {
false
};
self.try_commit_output_note(*committed_note.note_id(), inclusion_proof)?;
Ok(is_tracked_as_input_note)
}
pub(crate) fn apply_output_note_inclusion_proofs(
&mut self,
committed_notes: &[CommittedNote],
) -> Result<(), ClientError> {
for committed_note in committed_notes {
self.try_commit_output_note(
*committed_note.note_id(),
committed_note.inclusion_proof().clone(),
)?;
}
Ok(())
}
fn try_commit_output_note(
&mut self,
note_id: NoteId,
inclusion_proof: NoteInclusionProof,
) -> Result<(), ClientError> {
if let Some(output_note) = self.get_output_note_by_id(note_id) {
output_note.inclusion_proof_received(inclusion_proof)?;
}
Ok(())
}
pub(crate) fn apply_nullifiers_state_transitions<'a>(
&mut self,
nullifier_update: &NullifierUpdate,
mut committed_transactions: impl Iterator<Item = &'a TransactionRecord>,
external_consumer_account: Option<AccountId>,
) -> Result<(), ClientError> {
let order = self.get_nullifier_order(nullifier_update.nullifier);
if let Some(input_note_update) =
self.get_input_note_update_by_nullifier(nullifier_update.nullifier)
{
if let Some(consumer_transaction) = committed_transactions
.find(|t| input_note_update.inner().consumer_transaction_id() == Some(&t.id))
{
if let TransactionStatus::Committed { block_number, .. } =
consumer_transaction.status
{
input_note_update
.inner_mut()
.transaction_committed(consumer_transaction.id, block_number)?;
}
} else {
input_note_update.inner_mut().consumed_externally(
nullifier_update.nullifier,
nullifier_update.block_num,
external_consumer_account,
)?;
}
input_note_update.inner_mut().set_consumed_tx_order(order);
}
if let Some(output_note_record) =
self.get_output_note_by_nullifier(nullifier_update.nullifier)
{
output_note_record
.nullifier_received(nullifier_update.nullifier, nullifier_update.block_num)?;
}
Ok(())
}
fn get_nullifier_order(&self, nullifier: Nullifier) -> Option<u32> {
self.nullifier_order.get(&nullifier).copied()
}
fn get_input_note_by_id(&mut self, note_id: NoteId) -> Option<&mut InputNoteRecord> {
self.input_notes.get_mut(¬e_id).map(InputNoteUpdate::inner_mut)
}
fn get_output_note_by_id(&mut self, note_id: NoteId) -> Option<&mut OutputNoteRecord> {
self.output_notes.get_mut(¬e_id).map(OutputNoteUpdate::inner_mut)
}
fn get_input_note_update_by_nullifier(
&mut self,
nullifier: Nullifier,
) -> Option<&mut InputNoteUpdate> {
let note_id = self.input_notes_by_nullifier.get(&nullifier).copied()?;
self.input_notes.get_mut(¬e_id)
}
fn get_output_note_by_nullifier(
&mut self,
nullifier: Nullifier,
) -> Option<&mut OutputNoteRecord> {
let note_id = self.output_notes_by_nullifier.get(&nullifier).copied()?;
self.output_notes.get_mut(¬e_id).map(OutputNoteUpdate::inner_mut)
}
fn insert_input_note(&mut self, note: InputNoteRecord, update_type: NoteUpdateType) {
let note_id = note.id();
let nullifier = note.nullifier();
self.input_notes_by_nullifier.insert(nullifier, note_id);
let update = match update_type {
NoteUpdateType::None => InputNoteUpdate::new_none(note),
NoteUpdateType::Insert => InputNoteUpdate::new_insert(note),
NoteUpdateType::Update => InputNoteUpdate::new_update(note),
};
self.input_notes.insert(note_id, update);
}
fn insert_output_note(&mut self, note: OutputNoteRecord, update_type: NoteUpdateType) {
let note_id = note.id();
if let Some(nullifier) = note.nullifier() {
self.output_notes_by_nullifier.insert(nullifier, note_id);
}
let update = match update_type {
NoteUpdateType::None => OutputNoteUpdate::new_none(note),
NoteUpdateType::Insert => OutputNoteUpdate::new_insert(note),
NoteUpdateType::Update => OutputNoteUpdate::new_update(note),
};
self.output_notes.insert(note_id, update);
}
}