Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
182 changes: 85 additions & 97 deletions noir-projects/aztec-nr/aztec/src/discovery/partial_notes.nr
Original file line number Diff line number Diff line change
Expand Up @@ -58,102 +58,90 @@ pub unconstrained fn fetch_and_process_public_partial_note_completion_logs<Env>(
[pending_partial_notes.len() as Field],
);

let mut i = &mut 0;
whyle(
|| *i < pending_partial_notes.len(),
|| {
let pending_partial_note: DeliveredPendingPartialNote = pending_partial_notes.get(*i);

let maybe_log = get_log_by_tag(pending_partial_note.note_completion_log_tag);
if maybe_log.is_none() {
debug_log_format(
"Found no completion logs for partial note with tag {}",
[pending_partial_note.note_completion_log_tag],
);
*i += 1 as u32;
// Note that we're not removing the pending partial note from the PXE DB, so we will continue searching
// for this tagged log when performing message discovery in the future until we either find it or the
// entry is somehow removed from the PXE DB.
} else {
debug_log_format(
"Completion log found for partial note with tag {}",
[pending_partial_note.note_completion_log_tag],
);
let log = maybe_log.unwrap();

// Public logs have an extra field at the beginning with the contract address, which we use to verify
// that we're getting the logs from the expected contract.
// TODO(#10273): improve how contract log siloing is handled
assert_eq(
log.log_content.get(0),
contract_address.to_field(),
"Got a public log emitted by a different contract",
);

// Public fields are assumed to all be placed at the end of the packed representation, so we combine the
// private and public packed fields (i.e. the contents of the log sans the extra fields) to get the
// complete packed content.
let packed_public_note_content: BoundedVec<_, MAX_PUBLIC_PARTIAL_NOTE_PACKED_CONTENT_LENGTH> =
array::subbvec(log.log_content, NON_PACKED_CONTENT_FIELDS_IN_PUBLIC_LOG);
let complete_packed_note_content = array::append(
pending_partial_note.packed_private_note_content,
packed_public_note_content,
);

let discovered_notes = attempt_note_nonce_discovery(
log.unique_note_hashes_in_tx,
log.first_nullifier_in_tx,
compute_note_hash_and_nullifier,
contract_address,
pending_partial_note.storage_slot,
pending_partial_note.note_type_id,
complete_packed_note_content,
);

debug_log_format(
"Discovered {0} notes for partial note with tag {1}",
[discovered_notes.len() as Field, pending_partial_note.note_completion_log_tag],
);

array::for_each_in_bounded_vec(
discovered_notes,
|discovered_note: DiscoveredNoteInfo, _| {
// TODO:(#10728): decide how to handle notes that fail delivery. This could be due to e.g. a
// temporary node connectivity issue - is simply throwing good enough here?
assert(
deliver_note(
contract_address,
pending_partial_note.storage_slot,
discovered_note.nonce,
complete_packed_note_content,
discovered_note.note_hash,
discovered_note.inner_nullifier,
log.tx_hash,
pending_partial_note.recipient,
),
"Failed to deliver note",
);
},
);

// Because there is only a single log for a given tag, once we've processed the tagged log then we
// simply delete the pending work entry, regardless of whether it was actually completed or not.
// TODO(#11627): only remove the pending entry if we actually process a log that results in the note
// being completed.
pending_partial_notes.remove(*i);

// We don't increment `i` here, because CapsuleArray is contiguous and its `remove(...)` function
// shifts the elements to the left if the removed element is not the last element.
}
},
);
}

/// Custom version of a while loop, calls `body` repeatedly until `condition` returns false. To be removed once Noir
/// supports looping in unconstrained code.
fn whyle<Env, Env2>(condition: fn[Env]() -> bool, body: fn[Env2]() -> ()) {
if condition() {
body();
whyle(condition, body);
let mut i = 0;
while i < pending_partial_notes.len() {
let pending_partial_note: DeliveredPendingPartialNote = pending_partial_notes.get(i);

let maybe_log = get_log_by_tag(pending_partial_note.note_completion_log_tag);
if maybe_log.is_none() {
debug_log_format(
"Found no completion logs for partial note with tag {}",
[pending_partial_note.note_completion_log_tag],
);
i += 1 as u32;
// Note that we're not removing the pending partial note from the PXE DB, so we will continue searching
// for this tagged log when performing message discovery in the future until we either find it or the
// entry is somehow removed from the PXE DB.
} else {
debug_log_format(
"Completion log found for partial note with tag {}",
[pending_partial_note.note_completion_log_tag],
);
let log = maybe_log.unwrap();

// Public logs have an extra field at the beginning with the contract address, which we use to verify
// that we're getting the logs from the expected contract.
// TODO(#10273): improve how contract log siloing is handled
assert_eq(
log.log_content.get(0),
contract_address.to_field(),
"Got a public log emitted by a different contract",
);

// Public fields are assumed to all be placed at the end of the packed representation, so we combine the
// private and public packed fields (i.e. the contents of the log sans the extra fields) to get the
// complete packed content.
let packed_public_note_content: BoundedVec<_, MAX_PUBLIC_PARTIAL_NOTE_PACKED_CONTENT_LENGTH> =
array::subbvec(log.log_content, NON_PACKED_CONTENT_FIELDS_IN_PUBLIC_LOG);
let complete_packed_note_content = array::append(
pending_partial_note.packed_private_note_content,
packed_public_note_content,
);

let discovered_notes = attempt_note_nonce_discovery(
log.unique_note_hashes_in_tx,
log.first_nullifier_in_tx,
compute_note_hash_and_nullifier,
contract_address,
pending_partial_note.storage_slot,
pending_partial_note.note_type_id,
complete_packed_note_content,
);

debug_log_format(
"Discovered {0} notes for partial note with tag {1}",
[discovered_notes.len() as Field, pending_partial_note.note_completion_log_tag],
);

array::for_each_in_bounded_vec(
discovered_notes,
|discovered_note: DiscoveredNoteInfo, _| {
// TODO:(#10728): decide how to handle notes that fail delivery. This could be due to e.g. a
// temporary node connectivity issue - is simply throwing good enough here?
assert(
deliver_note(
contract_address,
pending_partial_note.storage_slot,
discovered_note.nonce,
complete_packed_note_content,
discovered_note.note_hash,
discovered_note.inner_nullifier,
log.tx_hash,
pending_partial_note.recipient,
),
"Failed to deliver note",
);
},
);

// Because there is only a single log for a given tag, once we've processed the tagged log then we
// simply delete the pending work entry, regardless of whether it was actually completed or not.
// TODO(#11627): only remove the pending entry if we actually process a log that results in the note
// being completed.
pending_partial_notes.remove(i);

// We don't increment `i` here, because CapsuleArray is contiguous and its `remove(...)` function
// shifts the elements to the left if the removed element is not the last element.
}
}
}