Skip to content
Open
Show file tree
Hide file tree
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
23 changes: 23 additions & 0 deletions docs/docs-developers/docs/resources/migration_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,29 @@ Aztec is in active development. Each version may introduce breaking changes that

## TBD

### [Aztec.nr] `LogRetrievalRequest` now includes `source`, `from_block`, and `to_block` fields

`LogRetrievalRequest` has been extended with three new fields to support filtering logs by source and block range. The `get_logs_by_tag` oracle now also returns all matching logs per tag instead of only the first match.

A `LogRetrievalRequest::new(contract_address, tag)` constructor is provided that defaults to querying both public and private logs with no block range filter:

```rust
LogRetrievalRequest::new(contract_address, my_tag)
```

If you need to customize source or block range, construct the struct manually with the new fields:

```diff
LogRetrievalRequest {
tag: my_tag,
+ source: LogSource.PUBLIC_AND_PRIVATE,
+ from_block: Option::none(),
+ to_block: Option::none(),
}
```

`source` controls which RPCs are queried: `LogSource.PRIVATE`, `LogSource.PUBLIC`, or `LogSource.PUBLIC_AND_PRIVATE`. `from_block` and `to_block` define a half-open `[from, to)` block range filter. Both are `Option<Field>` and default to `Option::none()` (no filtering).

### [Aztec.nr] `emit_private_log_unsafe` / `emit_raw_note_log_unsafe` now take `BoundedVec`

The old array-based `emit_private_log_unsafe(tag, log: [Field; N], length)` and `emit_raw_note_log_unsafe(tag, log: [Field; N], length, note_hash_counter)` have been removed. The temporary `_vec_unsafe` variants introduced in a prior release have been renamed to take their place.
Expand Down
29 changes: 29 additions & 0 deletions noir-projects/aztec-nr/aztec/src/ephemeral/mod.nr
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::oracle::ephemeral;
use crate::protocol::traits::{Deserialize, Serialize};
use crate::protocol::utils::{reader::Reader, writer::Writer};

/// A dynamically sized array that exists only during a single contract call frame.
///
Expand Down Expand Up @@ -101,6 +102,34 @@ impl<T> EphemeralArray<T> {
}
}

/// Serializes an `EphemeralArray` as its slot identifier, allowing oracle function signatures to use
/// `EphemeralArray<T>` instead of opaque `Field` slots.
impl<T> Serialize for EphemeralArray<T> {
let N: u32 = 1;

fn serialize(self) -> [Field; Self::N] {
[self.slot]
}

fn stream_serialize<let K: u32>(self, writer: &mut Writer<K>) {
writer.write(self.slot);
}
}

/// Deserializes a single Field into an `EphemeralArray` handle, treating the field value as the slot identifier.
/// This is the inverse of [`Serialize`].
impl<T> Deserialize for EphemeralArray<T> {
let N: u32 = 1;

fn deserialize(fields: [Field; Self::N]) -> Self {
Self { slot: fields[0] }
}

fn stream_deserialize<let K: u32>(reader: &mut Reader<K>) -> Self {
Self { slot: reader.read() }
}
}

mod test {
use crate::test::helpers::test_environment::TestEnvironment;
use crate::test::mocks::MockStruct;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::{
capsules::CapsuleArray,
ephemeral::EphemeralArray,
messages::{
discovery::{ComputeNoteHash, ComputeNoteNullifier, nonce_discovery::attempt_note_nonce_discovery},
encoding::MAX_MESSAGE_CONTENT_LEN,
Expand Down Expand Up @@ -88,17 +89,17 @@ pub(crate) unconstrained fn fetch_and_process_partial_note_completion_logs(
// Each of the pending partial notes might get completed by a log containing its public values. For performance
// reasons, we fetch all of these logs concurrently and then process them one by one, minimizing the amount of time
// waiting for the node roundtrip.
let maybe_completion_logs = get_pending_partial_notes_completion_logs(contract_address, pending_partial_notes);
let completion_logs = get_pending_partial_notes_completion_logs(contract_address, pending_partial_notes);

// Each entry in the maybe completion logs array corresponds to the entry in the pending partial notes array at the
// same index. This means we can use the same index as we iterate through the responses to get both the partial
// note and the log that might complete it.
assert_eq(maybe_completion_logs.len(), pending_partial_notes.len());
// Each entry in the completion logs array corresponds to the entry in the pending partial notes array at the same
// index. Each inner array contains all matching LogRetrievalResponses.
assert_eq(completion_logs.len(), pending_partial_notes.len());

maybe_completion_logs.for_each(|i, maybe_log: Option<LogRetrievalResponse>| {
completion_logs.for_each(|i, logs_for_tag: EphemeralArray<LogRetrievalResponse>| {
let pending_partial_note = pending_partial_notes.get(i);
let num_logs = logs_for_tag.len();

if maybe_log.is_none() {
if num_logs == 0 {
aztecnr_debug_log_format!("Found no completion logs for partial note with tag {}")(
[pending_partial_note.note_completion_log_tag],
);
Expand All @@ -107,10 +108,15 @@ pub(crate) unconstrained fn fetch_and_process_partial_note_completion_logs(
// 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 array.
} else {
assert(
num_logs == 1,
f"Expected at most 1 completion log per partial note, got {num_logs}",
);

aztecnr_debug_log_format!("Completion log found for partial note with tag {}")([
pending_partial_note.note_completion_log_tag,
]);
let log = maybe_log.unwrap();
let log = logs_for_tag.get(0);

// The first field in the completion log payload is the storage slot, followed by the public note
// content fields.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,89 @@
use crate::protocol::{address::AztecAddress, traits::Serialize};

/// A request for the `bulk_retrieve_logs` oracle to fetch either:
/// - a public log emitted by `contract_address` with `unsiloed_tag`
/// - a private log with tag equal to `compute_siloed_private_log_first_field(contract_address, unsiloed_tag)`.
pub(crate) struct LogSourceEnum {
pub PRIVATE: Field,
pub PUBLIC: Field,
pub PUBLIC_AND_PRIVATE: Field,
}

pub(crate) global LogSource: LogSourceEnum =
LogSourceEnum { PRIVATE: 0, PUBLIC: 1, PUBLIC_AND_PRIVATE: 2 };

/// A request for the `bulk_retrieve_logs` oracle to fetch all logs matching a tag.
#[derive(Serialize)]
pub(crate) struct LogRetrievalRequest {
pub contract_address: AztecAddress,
pub unsiloed_tag: Field,
// TODO(#15052): choose source: public, private or either (current behavior)
/// Which log source to query: public, private, or both (the default). See [`LogSource`].
pub source: Field,
/// Inclusive lower bound on block number. When unset, logs from the first block are included.
pub from_block: Option<Field>,
/// Exclusive upper bound on block number. When unset, logs up to the anchor block are included.
pub to_block: Option<Field>,
}

impl LogRetrievalRequest {
/// Creates a request that queries both public and private logs with no block range filter.
pub(crate) fn new(contract_address: AztecAddress, unsiloed_tag: Field) -> Self {
LogRetrievalRequest {
contract_address,
unsiloed_tag,
source: LogSource.PUBLIC_AND_PRIVATE,
from_block: Option::none(),
to_block: Option::none(),
}
}
}

mod test {
use crate::protocol::{address::AztecAddress, traits::{FromField, Serialize}};
use super::LogRetrievalRequest;
use super::{LogRetrievalRequest, LogSource};

#[test]
fn serialization_of_defaults_matches_typescript() {
let request = LogRetrievalRequest {
contract_address: AztecAddress::from_field(1),
unsiloed_tag: 2,
source: LogSource.PUBLIC_AND_PRIVATE,
from_block: Option::none(),
to_block: Option::none(),
};

// We define the serialization in Noir and the deserialization in TS. If the deserialization changes from
// the snapshot value below, then log_retrieval_request.test.ts must be updated with the same value.
// Ideally we'd autogenerate this, but for now we only have single-sided snapshot generation, from TS to
// Noir, which is not what we need here.
let expected_serialization = [
0x0000000000000000000000000000000000000000000000000000000000000001,
0x0000000000000000000000000000000000000000000000000000000000000002,
0x0000000000000000000000000000000000000000000000000000000000000002,
0x0000000000000000000000000000000000000000000000000000000000000000,
0x0000000000000000000000000000000000000000000000000000000000000000,
0x0000000000000000000000000000000000000000000000000000000000000000,
0x0000000000000000000000000000000000000000000000000000000000000000,
];

assert_eq(request.serialize(), expected_serialization);
}

#[test]
fn serialization_matches_typescript() {
let request = LogRetrievalRequest { contract_address: AztecAddress::from_field(1), unsiloed_tag: 2 };
fn serialization_with_values_matches_typescript() {
let request = LogRetrievalRequest {
contract_address: AztecAddress::from_field(1),
unsiloed_tag: 2,
source: LogSource.PUBLIC,
from_block: Option::some(10),
to_block: Option::some(20),
};

// We define the serialization in Noir and the deserialization in TS. If the deserialization changes from the
// snapshot value below, then log_retrieval_request.test.ts must be updated with the same value. Ideally we'd
// autogenerate this, but for now we only have single-sided snapshot generation, from TS to Noir, which is not
// what we need here.
let expected_serialization = [
0x0000000000000000000000000000000000000000000000000000000000000001,
0x0000000000000000000000000000000000000000000000000000000000000002,
0x0000000000000000000000000000000000000000000000000000000000000001,
0x0000000000000000000000000000000000000000000000000000000000000001,
0x000000000000000000000000000000000000000000000000000000000000000a,
0x0000000000000000000000000000000000000000000000000000000000000001,
0x0000000000000000000000000000000000000000000000000000000000000014,
];

assert_eq(request.serialize(), expected_serialization);
Expand Down
30 changes: 15 additions & 15 deletions noir-projects/aztec-nr/aztec/src/messages/processing/mod.nr
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ use crate::{
discovery::partial_notes::DeliveredPendingPartialNote,
encoding::MESSAGE_CIPHERTEXT_LEN,
logs::{event::MAX_EVENT_SERIALIZED_LEN, note::MAX_NOTE_PACKED_LEN},
processing::{log_retrieval_request::LogRetrievalRequest, log_retrieval_response::LogRetrievalResponse},
processing::{
log_retrieval_request::LogRetrievalRequest,
log_retrieval_response::LogRetrievalResponse,
},
},
oracle::message_processing,
};
Expand Down Expand Up @@ -137,8 +140,8 @@ pub unconstrained fn enqueue_event_for_validation(
/// API (PXE::getPrivateEvents).
pub unconstrained fn validate_and_store_enqueued_notes_and_events(scope: AztecAddress) {
message_processing::validate_and_store_enqueued_notes_and_events(
NOTE_VALIDATION_REQUESTS_ARRAY_BASE_SLOT,
EVENT_VALIDATION_REQUESTS_ARRAY_BASE_SLOT,
EphemeralArray::at(NOTE_VALIDATION_REQUESTS_ARRAY_BASE_SLOT),
EphemeralArray::at(EVENT_VALIDATION_REQUESTS_ARRAY_BASE_SLOT),
MAX_NOTE_PACKED_LEN as Field,
MAX_EVENT_SERIALIZED_LEN as Field,
scope,
Expand All @@ -151,22 +154,19 @@ pub unconstrained fn validate_and_store_enqueued_notes_and_events(scope: AztecAd
}

/// Efficiently queries the node for logs that result in the completion of all `DeliveredPendingPartialNote`s stored in
/// a `CapsuleArray` by performing all node communication concurrently. Returns an `EphemeralArray` with Options
/// for the responses that correspond to the pending partial notes at the same index.
///
/// For example, given an array with pending partial notes `[ p1, p2, p3 ]`, where `p1` and `p3` have corresponding
/// completion logs but `p2` does not, the returned `EphemeralArray` will have contents `[some(p1_log), none(),
/// some(p3_log)]`.
/// a `CapsuleArray` by performing all node communication concurrently. Returns a nested `EphemeralArray`, one inner
/// array per pending partial note, each containing all matching `LogRetrievalResponse`s (which may be empty if no
/// logs were found).
pub(crate) unconstrained fn get_pending_partial_notes_completion_logs(
contract_address: AztecAddress,
pending_partial_notes: CapsuleArray<DeliveredPendingPartialNote>,
) -> EphemeralArray<Option<LogRetrievalResponse>> {
) -> EphemeralArray<EphemeralArray<LogRetrievalResponse>> {
let log_retrieval_requests = EphemeralArray::at(LOG_RETRIEVAL_REQUESTS_ARRAY_BASE_SLOT);

// We create a LogRetrievalRequest for each PendingPartialNote in the EphemeralArray. Because we need the indices in
// the request array to match the indices in the partial note array, we can't use EphemeralArray::for_each, as that
// function has arbitrary iteration order. Instead, we manually iterate the array from the beginning and push into
// the requests array, which we expect to be empty.
// We create a LogRetrievalRequest for each PendingPartialNote in the EphemeralArray. Because we need the indices
// in the request array to match the indices in the partial note array, we can't use EphemeralArray::for_each, as
// that function has arbitrary iteration order. Instead, we manually iterate the array from the beginning and push
// into the requests array, which we expect to be empty.
let mut i = 0;
let pending_partial_notes_count = pending_partial_notes.len();
while i < pending_partial_notes_count {
Expand All @@ -177,7 +177,7 @@ pub(crate) unconstrained fn get_pending_partial_notes_completion_logs(
pending_partial_note.note_completion_log_tag,
DOM_SEP__NOTE_COMPLETION_LOG_TAG,
);
log_retrieval_requests.push(LogRetrievalRequest { contract_address, unsiloed_tag: log_tag });
log_retrieval_requests.push(LogRetrievalRequest::new(contract_address, log_tag));
i += 1;
}

Expand Down
41 changes: 23 additions & 18 deletions noir-projects/aztec-nr/aztec/src/oracle/message_processing.nr
Original file line number Diff line number Diff line change
@@ -1,32 +1,32 @@
use crate::ephemeral::EphemeralArray;
use crate::messages::processing::{
event_validation_request::EventValidationRequest,
log_retrieval_request::LogRetrievalRequest, log_retrieval_response::LogRetrievalResponse, MessageContext,
pending_tagged_log::PendingTaggedLog,
NoteValidationRequest, pending_tagged_log::PendingTaggedLog,
};
use crate::protocol::address::AztecAddress;
use crate::protocol::blob_data::TxEffect;

/// Finds new private logs that may have been sent to all registered accounts in PXE in the current contract and
/// returns them in an ephemeral array with an oracle-allocated base slot.
pub(crate) unconstrained fn get_pending_tagged_logs(scope: AztecAddress) -> EphemeralArray<PendingTaggedLog> {
let result_slot = get_pending_tagged_logs_oracle(scope);
EphemeralArray::at(result_slot)
get_pending_tagged_logs_oracle(scope)
}

#[oracle(aztec_utl_getPendingTaggedLogs)]
unconstrained fn get_pending_tagged_logs_oracle(scope: AztecAddress) -> Field {}
unconstrained fn get_pending_tagged_logs_oracle(scope: AztecAddress) -> EphemeralArray<PendingTaggedLog> {}

/// Validates note/event requests stored in ephemeral arrays.
pub(crate) unconstrained fn validate_and_store_enqueued_notes_and_events(
note_validation_requests_array_slot: Field,
event_validation_requests_array_slot: Field,
note_validation_requests: EphemeralArray<NoteValidationRequest>,
event_validation_requests: EphemeralArray<EventValidationRequest>,
max_note_packed_len: Field,
max_event_serialized_len: Field,
scope: AztecAddress,
) {
validate_and_store_enqueued_notes_and_events_oracle(
note_validation_requests_array_slot,
event_validation_requests_array_slot,
note_validation_requests,
event_validation_requests,
max_note_packed_len,
max_event_serialized_len,
scope,
Expand All @@ -35,34 +35,39 @@ pub(crate) unconstrained fn validate_and_store_enqueued_notes_and_events(

#[oracle(aztec_utl_validateAndStoreEnqueuedNotesAndEvents)]
unconstrained fn validate_and_store_enqueued_notes_and_events_oracle(
note_validation_requests_array_slot: Field,
event_validation_requests_array_slot: Field,
note_validation_requests: EphemeralArray<NoteValidationRequest>,
event_validation_requests: EphemeralArray<EventValidationRequest>,
max_note_packed_len: Field,
max_event_serialized_len: Field,
scope: AztecAddress,
) {}

/// Fetches logs by tag from an ephemeral request array and returns a response ephemeral array.
/// Fetches all logs matching each request's tag and returns a nested ephemeral array.
///
/// Each element in the outer array is an inner `EphemeralArray<LogRetrievalResponse>` containing all matching logs for
/// the request at the same index (which may be empty if no logs were found).
pub(crate) unconstrained fn get_logs_by_tag(
requests: EphemeralArray<LogRetrievalRequest>,
) -> EphemeralArray<Option<LogRetrievalResponse>> {
let response_slot = get_logs_by_tag_oracle(requests.slot);
EphemeralArray::at(response_slot)
) -> EphemeralArray<EphemeralArray<LogRetrievalResponse>> {
get_logs_by_tag_oracle(requests)
}

#[oracle(aztec_utl_getLogsByTag)]
unconstrained fn get_logs_by_tag_oracle(request_array_slot: Field) -> Field {}
unconstrained fn get_logs_by_tag_oracle(
requests: EphemeralArray<LogRetrievalRequest>,
) -> EphemeralArray<EphemeralArray<LogRetrievalResponse>> {}

/// Resolves message contexts for tx hashes in an ephemeral request array and returns a response ephemeral array.
pub(crate) unconstrained fn get_message_contexts_by_tx_hash(
requests: EphemeralArray<Field>,
) -> EphemeralArray<Option<MessageContext>> {
let response_slot = get_message_contexts_by_tx_hash_oracle(requests.slot);
EphemeralArray::at(response_slot)
get_message_contexts_by_tx_hash_oracle(requests)
}

#[oracle(aztec_utl_getMessageContextsByTxHash)]
unconstrained fn get_message_contexts_by_tx_hash_oracle(request_array_slot: Field) -> Field {}
unconstrained fn get_message_contexts_by_tx_hash_oracle(
requests: EphemeralArray<Field>,
) -> EphemeralArray<Option<MessageContext>> {}

/// Fetches all effects of a settled transaction by its hash.
pub unconstrained fn get_tx_effect(tx_hash: Field) -> Option<TxEffect> {
Expand Down
7 changes: 3 additions & 4 deletions noir-projects/aztec-nr/aztec/src/oracle/shared_secret.nr
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ global GET_SHARED_SECRETS_REQUEST_SLOT: Field =
#[oracle(aztec_utl_getSharedSecrets)]
unconstrained fn get_shared_secrets_oracle(
address: AztecAddress,
eph_pks_slot: Field,
eph_pks: EphemeralArray<Point>,
contract_address: AztecAddress,
) -> Field {}
) -> EphemeralArray<Field> {}

/// Convenience wrapper around [`get_shared_secrets`] for a single ephemeral public key.
pub unconstrained fn get_shared_secret(
Expand Down Expand Up @@ -48,8 +48,7 @@ pub unconstrained fn get_shared_secrets<let N: u32>(
EphemeralArray::at(GET_SHARED_SECRETS_REQUEST_SLOT).clear();
eph_pks.for_each(|pk| request_array.push(pk));

let response_slot = get_shared_secrets_oracle(address, request_array.slot, contract_address);
let response_array: EphemeralArray<Field> = EphemeralArray::at(response_slot);
let response_array = get_shared_secrets_oracle(address, request_array, contract_address);
assert(
response_array.len() == eph_pks.len(),
"get_shared_secrets: response length does not match request length",
Expand Down
Loading
Loading