diff --git a/noir-projects/aztec-nr/aztec/src/discovery/partial_notes.nr b/noir-projects/aztec-nr/aztec/src/discovery/partial_notes.nr index 7b517c34ae4f..abbbb5f5f323 100644 --- a/noir-projects/aztec-nr/aztec/src/discovery/partial_notes.nr +++ b/noir-projects/aztec-nr/aztec/src/discovery/partial_notes.nr @@ -58,102 +58,90 @@ pub unconstrained fn fetch_and_process_public_partial_note_completion_logs( [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(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. + } } }