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
17 changes: 17 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,23 @@ 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.

If you construct `LogRetrievalRequest` manually, you must provide 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<u32>` and default to `Option::none()` (no filtering).

### [Aztec.nr] `attempt_note_discovery` is no longer exposed; use `process_private_note_msg`

`attempt_note_discovery` is now crate-private. Custom message handlers (implementations of `CustomMessageHandler`) that previously called it directly should call `process_private_note_msg` instead, which runs the standard private note message decoding and discovery pipeline.
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,18 @@ 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_log_slots = 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 log slots array corresponds to the entry in the pending partial notes array at
// the same index. Each slot points to an inner EphemeralArray<LogRetrievalResponse> with all matching logs.
assert_eq(completion_log_slots.len(), pending_partial_notes.len());

maybe_completion_logs.for_each(|i, maybe_log: Option<LogRetrievalResponse>| {
completion_log_slots.for_each(|i, inner_slot: Field| {
let logs_for_tag: EphemeralArray<LogRetrievalResponse> = EphemeralArray::at(inner_slot);
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 +109,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,76 @@
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>,
}

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

#[test]
fn serialization_matches_typescript() {
let request = LogRetrievalRequest { contract_address: AztecAddress::from_field(1), unsiloed_tag: 2 };
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.
// 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_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),
};

let expected_serialization = [
0x0000000000000000000000000000000000000000000000000000000000000001,
0x0000000000000000000000000000000000000000000000000000000000000002,
0x0000000000000000000000000000000000000000000000000000000000000001,
0x0000000000000000000000000000000000000000000000000000000000000001,
0x000000000000000000000000000000000000000000000000000000000000000a,
0x0000000000000000000000000000000000000000000000000000000000000001,
0x0000000000000000000000000000000000000000000000000000000000000014,
];

assert_eq(request.serialize(), expected_serialization);
Expand Down
29 changes: 16 additions & 13 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,7 @@ 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, LogSource},
},
oracle::message_processing,
};
Expand Down Expand Up @@ -151,22 +151,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 an `EphemeralArray` of slots, one per
/// pending partial note. Each slot identifies an inner `EphemeralArray<LogRetrievalResponse>` with all matching logs
/// for that note (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<Field> {
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 +174,13 @@ 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 {
contract_address,
unsiloed_tag: log_tag,
source: LogSource.PUBLIC_AND_PRIVATE,
from_block: Option::none(),
to_block: Option::none(),
});
i += 1;
}

Expand Down
10 changes: 6 additions & 4 deletions noir-projects/aztec-nr/aztec/src/oracle/message_processing.nr
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use crate::ephemeral::EphemeralArray;
use crate::messages::processing::{
log_retrieval_request::LogRetrievalRequest, log_retrieval_response::LogRetrievalResponse, MessageContext,
pending_tagged_log::PendingTaggedLog,
log_retrieval_request::LogRetrievalRequest, MessageContext, pending_tagged_log::PendingTaggedLog,
};
use crate::protocol::address::AztecAddress;
use crate::protocol::blob_data::TxEffect;
Expand Down Expand Up @@ -42,10 +41,13 @@ unconstrained fn validate_and_store_enqueued_notes_and_events_oracle(
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 an ephemeral array of slots.
///
/// Each element in the returned array is a `Field` slot that identifies 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>> {
) -> EphemeralArray<Field> {
let response_slot = get_logs_by_tag_v2_oracle(requests.slot);
EphemeralArray::at(response_slot)
}
Expand Down
4 changes: 2 additions & 2 deletions noir-projects/aztec-nr/aztec/src/oracle/version.nr
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
/// the PXE and used to provide helpful error messages if a contract calls an oracle that doesn't exist. We don't throw
/// immediately if AZTEC_NR_MINOR > PXE_MINOR because if a contract is updated to use a newer Aztec.nr dependency
/// without actually using any of the new oracles then there is no reason to throw.
pub global ORACLE_VERSION_MAJOR: Field = 22;
pub global ORACLE_VERSION_MINOR: Field = 3;
pub global ORACLE_VERSION_MAJOR: Field = 23;
pub global ORACLE_VERSION_MINOR: Field = 0;

/// Asserts that the version of the oracle is compatible with the version expected by the contract.
pub fn assert_compatible_oracle_version() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
/// the PXE and used to provide helpful error messages if a contract calls an oracle that doesn't exist. We don't throw
/// immediately if AZTEC_NR_MINOR > PXE_MINOR because if a contract is updated to use a newer Aztec.nr dependency
/// without actually using any of the new oracles then there is no reason to throw.
pub global ORACLE_VERSION_MAJOR: Field = 22;
pub global ORACLE_VERSION_MINOR: Field = 1;
pub global ORACLE_VERSION_MAJOR: Field = 23;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would have expected this to be 12 (the version we decided protocol contracts were going to use), but apparently we are keeping it up to date with PXE's version

pub global ORACLE_VERSION_MINOR: Field = 0;

/// Asserts that the version of the oracle is compatible with the version expected by the contract.
pub fn assert_compatible_oracle_version() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,88 @@
import { BlockNumber } from '@aztec/foundation/branded-types';
import { Fr } from '@aztec/foundation/curves/bn254';
import { AztecAddress } from '@aztec/stdlib/aztec-address';
import { Tag } from '@aztec/stdlib/logs';

import { LogRetrievalRequest } from './log_retrieval_request.js';
import { LogRetrievalRequest, LogSource } from './log_retrieval_request.js';

describe('LogRetrievalRequest', () => {
it('output of Noir serialization deserializes as expected', () => {
it('output of Noir serialization with defaults deserializes as expected', () => {
const serialized = [
'0x0000000000000000000000000000000000000000000000000000000000000001',
'0x0000000000000000000000000000000000000000000000000000000000000002',
'0x0000000000000000000000000000000000000000000000000000000000000002',
'0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000',
].map(Fr.fromHexString);

const request = LogRetrievalRequest.fromFields(serialized);

expect(request.contractAddress).toEqual(AztecAddress.fromBigInt(1n));
expect(request.tag).toEqual(new Tag(new Fr(2)));
expect(request.source).toEqual(LogSource.PUBLIC_AND_PRIVATE);
expect(request.fromBlock).toBeUndefined();
expect(request.toBlock).toBeUndefined();
});

it('output of Noir serialization with values deserializes as expected', () => {
const serialized = [
'0x0000000000000000000000000000000000000000000000000000000000000001',
'0x0000000000000000000000000000000000000000000000000000000000000002',
'0x0000000000000000000000000000000000000000000000000000000000000001',
'0x0000000000000000000000000000000000000000000000000000000000000001',
'0x000000000000000000000000000000000000000000000000000000000000000a',
'0x0000000000000000000000000000000000000000000000000000000000000001',
'0x0000000000000000000000000000000000000000000000000000000000000014',
].map(Fr.fromHexString);

const request = LogRetrievalRequest.fromFields(serialized);

expect(request.contractAddress).toEqual(AztecAddress.fromBigInt(1n));
expect(request.tag).toEqual(new Tag(new Fr(2)));
expect(request.source).toEqual(LogSource.PUBLIC);
expect(request.fromBlock).toEqual(BlockNumber(10));
expect(request.toBlock).toEqual(BlockNumber(20));
});

it('rejects an invalid LogSource value', () => {
const serialized = [
'0x0000000000000000000000000000000000000000000000000000000000000001',
'0x0000000000000000000000000000000000000000000000000000000000000002',
'0x000000000000000000000000000000000000000000000000000000000000002a', // 42 — invalid
'0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000',
].map(Fr.fromHexString);

expect(() => LogRetrievalRequest.fromFields(serialized)).toThrow(/Invalid LogSource value 42/);
});

it('accepts all valid LogSource values', () => {
for (const source of [LogSource.PRIVATE, LogSource.PUBLIC, LogSource.PUBLIC_AND_PRIVATE]) {
const fields = new LogRetrievalRequest(AztecAddress.fromBigInt(1n), new Tag(new Fr(2)), source).toFields();
const restored = LogRetrievalRequest.fromFields(fields);
expect(restored.source).toEqual(source);
}
});

it('round-trips through toFields and fromFields', () => {
const original = new LogRetrievalRequest(
AztecAddress.fromBigInt(42n),
new Tag(new Fr(99)),
LogSource.PRIVATE,
BlockNumber(5),
BlockNumber(100),
);

const restored = LogRetrievalRequest.fromFields(original.toFields());

expect(restored.contractAddress).toEqual(original.contractAddress);
expect(restored.tag).toEqual(original.tag);
expect(restored.source).toEqual(original.source);
expect(restored.fromBlock).toEqual(original.fromBlock);
expect(restored.toBlock).toEqual(original.toBlock);
});
});
Loading
Loading