Skip to content
59 changes: 20 additions & 39 deletions crates/rust-client/src/note/note_update_tracker.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
use alloc::collections::BTreeMap;

use miden_protocol::account::AccountId;
use miden_protocol::block::BlockHeader;
use miden_protocol::block::{BlockHeader, BlockNumber};
use miden_protocol::note::{NoteId, NoteInclusionProof, Nullifier};
use miden_protocol::transaction::TransactionId;

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};

// NOTE UPDATE
// ================================================================================================
Expand Down Expand Up @@ -167,10 +167,6 @@ pub struct NoteUpdateTracker {
input_notes_by_nullifier: BTreeMap<Nullifier, NoteId>,
/// Fast lookup map from nullifier to output note id.
output_notes_by_nullifier: BTreeMap<Nullifier, NoteId>,
/// Map from nullifier to its per-account position in the consuming transaction order.
/// Nullifiers from the same account are in execution order; ordering across different
/// accounts is not guaranteed.
nullifier_order: BTreeMap<Nullifier, u32>,
}

impl NoteUpdateTracker {
Expand Down Expand Up @@ -266,18 +262,6 @@ impl NoteUpdateTracker {
input_note_unspent_nullifiers.chain(output_note_unspent_nullifiers)
}

/// Appends nullifiers to the per-account ordered nullifier list.
///
/// Nullifiers from the same account must be in execution order; ordering across different
/// accounts is not guaranteed.
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);
}
}

// UPDATE METHODS
// --------------------------------------------------------------------------------------------

Expand Down Expand Up @@ -355,6 +339,18 @@ impl NoteUpdateTracker {
Ok(())
}

/// Returns the consumer transaction ID of the input note identified by the given nullifier,
/// if the note exists and has a consumer transaction.
pub(crate) fn consumer_transaction_id_by_nullifier(
&self,
nullifier: Nullifier,
) -> Option<TransactionId> {
let note_id = self.input_notes_by_nullifier.get(&nullifier).copied()?;
self.input_notes
.get(&note_id)
.and_then(|update| update.inner().consumer_transaction_id().copied())
}

/// Applies the necessary state transitions to the [`NoteUpdateTracker`] when a note is
/// nullified in a block.
///
Expand All @@ -364,28 +360,19 @@ impl NoteUpdateTracker {
/// consumption by untracked accounts as well as consumption by tracked accounts whose
/// transactions were submitted by other client instances. If a local transaction was
/// processing the note and it didn't get committed, the transaction should be discarded.
pub(crate) fn apply_nullifiers_state_transitions<'a>(
pub(crate) fn apply_nullifiers_state_transitions(
&mut self,
nullifier_update: &NullifierUpdate,
mut committed_transactions: impl Iterator<Item = &'a TransactionRecord>,
committed_consumer: Option<(TransactionId, BlockNumber)>,
external_consumer_account: Option<AccountId>,
consumed_tx_order: Option<u32>,
) -> 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 Some((tx_id, block_number)) = committed_consumer {
// The note was being processed by a local transaction that just got committed
if let TransactionStatus::Committed { block_number, .. } =
consumer_transaction.status
{
input_note_update
.inner_mut()
.transaction_committed(consumer_transaction.id, block_number)?;
}
input_note_update.inner_mut().transaction_committed(tx_id, block_number)?;
} else {
// The note was consumed by a transaction not submitted by this client.
// If the consuming account is tracked, external_consumer_account will be Some.
Expand All @@ -395,7 +382,7 @@ impl NoteUpdateTracker {
external_consumer_account,
)?;
}
input_note_update.inner_mut().set_consumed_tx_order(order);
input_note_update.inner_mut().set_consumed_tx_order(consumed_tx_order);
}

if let Some(output_note_record) =
Expand All @@ -411,12 +398,6 @@ impl NoteUpdateTracker {
// PRIVATE HELPERS
// --------------------------------------------------------------------------------------------

/// Returns the position of the given nullifier in the consuming transaction order, or `None`
/// if it is not present.
fn get_nullifier_order(&self, nullifier: Nullifier) -> Option<u32> {
self.nullifier_order.get(&nullifier).copied()
}

/// Returns a mutable reference to the input note record with the provided ID if it exists.
fn get_input_note_by_id(&mut self, note_id: NoteId) -> Option<&mut InputNoteRecord> {
self.input_notes.get_mut(&note_id).map(InputNoteUpdate::inner_mut)
Expand Down
18 changes: 10 additions & 8 deletions crates/rust-client/src/sync/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,9 @@ pub use tag::{NoteTagRecord, NoteTagSource};
mod state_sync;
pub use state_sync::{NoteUpdateAction, OnNoteReceived, StateSync, StateSyncInput};

mod state_sync_update;
pub use state_sync_update::{
AccountUpdates,
BlockUpdates,
StateSyncUpdate,
TransactionUpdateTracker,
};
mod updates;
pub use state_sync::TransactionUpdateTracker;
pub use updates::{AccountUpdates, BlockUpdates, StateSyncUpdate};

/// Client synchronization methods.
impl<AUTH> Client<AUTH>
Expand Down Expand Up @@ -137,7 +133,13 @@ where
let input = self.build_sync_input().await?;

// Get the sync update from the network
let state_sync_update = state_sync.sync_state(&mut current_partial_mmr, input).await?;
let Some(state_sync_update) =
state_sync.sync_state(&mut current_partial_mmr, input).await?
else {
let block_num = u32::try_from(current_partial_mmr.num_leaves().saturating_sub(1))
.map_err(|_| ClientError::InvalidPartialMmrForest)?;
return Ok(SyncSummary::new_empty(block_num.into()));
};

let sync_summary: SyncSummary = (&state_sync_update).into();
debug!(sync_summary = ?sync_summary, "Sync summary computed");
Expand Down
Loading
Loading