Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ pub mod private_notes;

use crate::messages::{
discovery::{private_logs::process_private_log, private_notes::MAX_NOTE_PACKED_LEN},
processing::{get_private_logs, pending_tagged_log::PendingTaggedLog},
processing::{get_private_logs, pending_tagged_log::PendingTaggedLog, validate_enqueued_notes},
};

pub struct NoteHashAndNullifier {
Expand Down Expand Up @@ -80,4 +80,8 @@ pub unconstrained fn discover_new_messages<Env>(
contract_address,
compute_note_hash_and_nullifier,
);

// Finally we validate all notes that were found as part of the previous processes, resulting in them being added to
// PXE's database and retrievable via oracles.
validate_enqueued_notes(contract_address);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ use crate::{
messages::{
discovery::{ComputeNoteHashAndNullifier, nonce_discovery::attempt_note_nonce_discovery},
encoding::MAX_MESSAGE_CONTENT_LEN,
processing::enqueue_note_for_validation,
},
oracle::message_discovery::{deliver_note, get_public_log_by_tag},
oracle::message_processing::get_public_log_by_tag,
utils::array,
};

Expand Down Expand Up @@ -125,20 +126,15 @@ pub unconstrained fn fetch_and_process_public_partial_note_completion_logs<Env>(
);

discovered_notes.for_each(|discovered_note| {
// 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,
discovered_note.note_hash,
discovered_note.inner_nullifier,
log.tx_hash,
pending_partial_note.recipient,
),
"Failed to deliver note",
enqueue_note_for_validation(
contract_address,
pending_partial_note.storage_slot,
discovered_note.nonce,
complete_packed_note,
discovered_note.note_hash,
discovered_note.inner_nullifier,
log.tx_hash,
pending_partial_note.recipient,
);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ use crate::{
messages::{
discovery::{ComputeNoteHashAndNullifier, nonce_discovery::attempt_note_nonce_discovery},
encoding::MAX_MESSAGE_CONTENT_LEN,
processing::enqueue_note_for_validation,
},
oracle,
utils::array,
};
use protocol_types::{
Expand Down Expand Up @@ -73,20 +73,15 @@ pub unconstrained fn attempt_note_discovery<Env>(
);

discovered_notes.for_each(|discovered_note| {
// TODO:(#10728): handle notes that fail delivery. This could be due to e.g. a temporary node connectivity
// issue, and we should perhaps not have marked the tag index as taken.
assert(
oracle::message_discovery::deliver_note(
contract_address,
storage_slot,
discovered_note.nonce,
packed_note,
discovered_note.note_hash,
discovered_note.inner_nullifier,
tx_hash,
recipient,
),
"Failed to deliver note",
enqueue_note_for_validation(
contract_address,
storage_slot,
discovered_note.nonce,
packed_note,
discovered_note.note_hash,
discovered_note.inner_nullifier,
tx_hash,
recipient,
);
});
}
Expand Down
76 changes: 69 additions & 7 deletions noir-projects/aztec-nr/aztec/src/messages/processing/mod.nr
Original file line number Diff line number Diff line change
@@ -1,22 +1,84 @@
pub(crate) mod pending_tagged_log;
pub(crate) mod note_validation_request;

use crate::{capsules::CapsuleArray, oracle};
use crate::messages::processing::pending_tagged_log::PendingTaggedLog;
use crate::messages::{
discovery::private_notes::MAX_NOTE_PACKED_LEN,
processing::{
note_validation_request::NoteValidationRequest, pending_tagged_log::PendingTaggedLog,
},
};
use protocol_types::{address::AztecAddress, hash::sha256_to_field};

// Base slot for the pending tagged log array to which the fetch_tagged_logs oracle inserts found private logs.
global PENDING_TAGGED_LOG_ARRAY_BASE_SLOT: Field =
sha256_to_field("AZTEC_NR::PENDING_TAGGED_LOG_ARRAY_BASE_SLOT".as_bytes());

/// Returns a `CapsuleArray` with all private logs that were found and need to be processed.
global NOTE_VALIDATION_REQUESTS_ARRAY_BASE_SLOT: Field = sha256_to_field(
"AZTEC_NR::NOTE_VALIDATION_REQUESTS_ARRAY_BASE_SLOT".as_bytes(),
);

/// Searches for private logs emitted by `contract_address` that might contain messages for one of the local accounts,
/// and stores them in a `CapsuleArray` which is then returned.
pub(crate) unconstrained fn get_private_logs(
contract_address: AztecAddress,
) -> CapsuleArray<PendingTaggedLog> {
// We will eventually perform log discovery via tagging here, but for now we simply call the `fetchTaggedLogs`
// oracle. This makes PXE synchronize tags, download logs and store the pending tagged logs in capsule array which
// are then retrieved and processed here.
oracle::message_discovery::fetch_tagged_logs(PENDING_TAGGED_LOG_ARRAY_BASE_SLOT);
// oracle. This makes PXE synchronize tags, download logs and store the pending tagged logs in a capsule array.
oracle::message_processing::fetch_tagged_logs(PENDING_TAGGED_LOG_ARRAY_BASE_SLOT);

CapsuleArray::at(contract_address, PENDING_TAGGED_LOG_ARRAY_BASE_SLOT)
}

/// Enqueues a note for validation by PXE, so that it becomes aware of a note's existence allowing for later retrieval
/// via `get_notes` oracle. The note will be scoped to `contract_address`, meaning other contracts will not be able to
/// access it unless authorized.
///
/// In order for the note validation and insertion to occur, `validate_enqueued_notes` must be later called. For optimal
/// performance, accumulate as many note validation requests as possible and then validate them all at the end (which
/// results in PXE minimizing the number of network round-trips).
///
/// The `packed_note` is what `getNotes` will later return. PXE indexes notes by `storage_slot`, so this value
/// is typically used to filter notes that correspond to different state variables. `note_hash` and `nullifier` are
/// the inner hashes, i.e. the raw hashes returned by `NoteHash::compute_note_hash` and
/// `NoteHash::compute_nullifier`. PXE will verify that the siloed unique note hash was inserted into the tree
/// at `tx_hash`, and will store the nullifier to later check for nullification.
///
/// `recipient` is the account to which the note was sent to. Other accounts will not be able to access this note (e.g.
/// other accounts will not be able to see one another's token balance notes, even in the same PXE) unless authorized.
pub(crate) unconstrained fn enqueue_note_for_validation(
contract_address: AztecAddress,
storage_slot: Field,
nonce: Field,
packed_note: BoundedVec<Field, MAX_NOTE_PACKED_LEN>,
note_hash: Field,
nullifier: Field,
tx_hash: Field,
recipient: AztecAddress,
) {
// We store requests in a `CapsuleArray`, which PXE will later read from and deserialize into its version of the
// Noir `NoteValidationRequest`
CapsuleArray::at(contract_address, NOTE_VALIDATION_REQUESTS_ARRAY_BASE_SLOT).push(
NoteValidationRequest {
contract_address,
storage_slot,
nonce,
packed_note,
note_hash,
nullifier,
tx_hash,
recipient,
},
)
}

// Get the logs from the capsule array and process them one by one
CapsuleArray::<PendingTaggedLog>::at(contract_address, PENDING_TAGGED_LOG_ARRAY_BASE_SLOT)
/// Validates all note validation requests enqueued via `enqueue_note_for_validation`, inserting them into the note
/// database and making them queryable via `get_notes`.
///
/// This automatically clears the validation request queue, so no further work needs to be done by the caller.
pub(crate) unconstrained fn validate_enqueued_notes(contract_address: AztecAddress) {
oracle::message_processing::validate_enqueued_notes(
contract_address,
NOTE_VALIDATION_REQUESTS_ARRAY_BASE_SLOT,
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
use crate::messages::discovery::private_notes::MAX_NOTE_PACKED_LEN;
use protocol_types::{address::AztecAddress, traits::Serialize};

/// Intermediate struct used to perform batch note validation by PXE. The `validateEnqueuedNotes` oracle expects for
/// values of this type to be stored in a `CapsuleArray`.
#[derive(Serialize)]
pub(crate) struct NoteValidationRequest {
pub contract_address: AztecAddress,
pub storage_slot: Field,
pub nonce: Field,
pub packed_note: BoundedVec<Field, MAX_NOTE_PACKED_LEN>,
pub note_hash: Field,
pub nullifier: Field,
pub tx_hash: Field,
pub recipient: AztecAddress,
}

mod test {
use super::NoteValidationRequest;
use protocol_types::{address::AztecAddress, traits::{FromField, Serialize}};

#[test]
fn serialization_matches_typescript() {
let request = NoteValidationRequest {
contract_address: AztecAddress::from_field(1),
storage_slot: 2,
nonce: 3,
packed_note: BoundedVec::from_array([4, 5]),
note_hash: 6,
nullifier: 7,
tx_hash: 8,
recipient: AztecAddress::from_field(9),
};

// We define the serialization in Noir and the deserialization in TS. If the deserialization changes from the
// snapshot value below, then note_validation_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,
0x0000000000000000000000000000000000000000000000000000000000000003,
0x0000000000000000000000000000000000000000000000000000000000000004,
0x0000000000000000000000000000000000000000000000000000000000000005,
0x0000000000000000000000000000000000000000000000000000000000000000,
0x0000000000000000000000000000000000000000000000000000000000000000,
0x0000000000000000000000000000000000000000000000000000000000000000,
0x0000000000000000000000000000000000000000000000000000000000000000,
0x0000000000000000000000000000000000000000000000000000000000000000,
0x0000000000000000000000000000000000000000000000000000000000000000,
0x0000000000000000000000000000000000000000000000000000000000000000,
0x0000000000000000000000000000000000000000000000000000000000000000,
0x0000000000000000000000000000000000000000000000000000000000000000,
0x0000000000000000000000000000000000000000000000000000000000000000,
0x0000000000000000000000000000000000000000000000000000000000000002,
0x0000000000000000000000000000000000000000000000000000000000000006,
0x0000000000000000000000000000000000000000000000000000000000000007,
0x0000000000000000000000000000000000000000000000000000000000000008,
0x0000000000000000000000000000000000000000000000000000000000000009,
];

assert_eq(request.serialize(), expected_serialization);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use crate::messages::discovery::private_notes::MAX_NOTE_PACKED_LEN;
use dep::protocol_types::{
address::AztecAddress,
constants::{MAX_NOTE_HASHES_PER_TX, PUBLIC_LOG_PLAINTEXT_LEN},
Expand All @@ -13,41 +12,6 @@ pub unconstrained fn fetch_tagged_logs(pending_tagged_log_array_base_slot: Field
#[oracle(fetchTaggedLogs)]
unconstrained fn fetch_tagged_logs_oracle(pending_tagged_log_array_base_slot: Field) {}

/// Informs PXE of a note's existence so that it can later be retrieved by the `getNotes` oracle. The note will be
/// scoped to `contract_address`, meaning other contracts will not be able to access it unless authorized.
///
/// The packed note is what `getNotes` will later return. PXE indexes notes by `storage_slot`, so this value
/// is typically used to filter notes that correspond to different state variables. `note_hash` and `nullifier` are
/// the inner hashes, i.e. the raw hashes returned by `NoteHash::compute_note_hash` and
/// `NoteHash::compute_nullifier`. PXE will verify that the siloed unique note hash was inserted into the tree
/// at `tx_hash`, and will store the nullifier to later check for nullification.
///
/// `recipient` is the account to which the note was sent to. Other accounts will not be able to access this note (e.g.
/// other accounts will not be able to see one another's token balance notes, even in the same PXE) unless authorized.
///
/// Returns true if the note was successfully delivered and added to PXE's database.
pub unconstrained fn deliver_note(
contract_address: AztecAddress,
storage_slot: Field,
nonce: Field,
packed_note: BoundedVec<Field, MAX_NOTE_PACKED_LEN>,
note_hash: Field,
nullifier: Field,
tx_hash: Field,
recipient: AztecAddress,
) -> bool {
deliver_note_oracle(
contract_address,
storage_slot,
nonce,
packed_note,
note_hash,
nullifier,
tx_hash,
recipient,
)
}

/// The plaintext of a public log (i.e. the content minus the tag), plus contextual information about the transaction
// in which the log was emitted. This is the data required in order to discover notes that are being delivered in a
// log.
Expand All @@ -71,20 +35,18 @@ pub unconstrained fn get_public_log_by_tag(
get_public_log_by_tag_oracle(tag, contract_address)
}

#[oracle(deliverNote)]
unconstrained fn deliver_note_oracle(
contract_address: AztecAddress,
storage_slot: Field,
nonce: Field,
packed_note: BoundedVec<Field, MAX_NOTE_PACKED_LEN>,
note_hash: Field,
nullifier: Field,
tx_hash: Field,
recipient: AztecAddress,
) -> bool {}

#[oracle(getPublicLogByTag)]
unconstrained fn get_public_log_by_tag_oracle(
tag: Field,
contract_address: AztecAddress,
) -> Option<PublicLogWithTxData> {}

pub(crate) unconstrained fn validate_enqueued_notes(
contract_address: AztecAddress,
base_slot: Field,
) {
validate_enqueued_notes_oracle(contract_address, base_slot);
}

#[oracle(validateEnqueuedNotes)]
unconstrained fn validate_enqueued_notes_oracle(contract_address: AztecAddress, base_slot: Field) {}
2 changes: 1 addition & 1 deletion noir-projects/aztec-nr/aztec/src/oracle/mod.nr
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ pub mod get_membership_witness;
pub mod keys;
pub mod key_validation_request;
pub mod logs;
pub mod message_discovery;
pub mod message_processing;
pub mod notes;
pub mod random;
pub mod shared_secret;
Expand Down
2 changes: 1 addition & 1 deletion yarn-project/kv-store/src/indexeddb/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export interface AztecIDBSchema extends DBSchema {
export class AztecIndexedDBStore implements AztecAsyncKVStore {
#rootDB: IDBPDatabase<AztecIDBSchema>;
#name: string;
#currentTx?: IDBPTransaction<AztecIDBSchema, ['data'], 'readwrite'>;
#currentTx: IDBPTransaction<AztecIDBSchema, ['data'], 'readwrite'> | undefined;

#containers = new Set<
| IndexedDBAztecArray<any>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Fr } from '@aztec/foundation/fields';
import { AztecAddress } from '@aztec/stdlib/aztec-address';
import { TxHash } from '@aztec/stdlib/tx';

import { NoteValidationRequest } from './note_validation_request.js';

describe('NoteValidationRequest', () => {
it('output of Noir serialization deserializes as expected', () => {
const serialized = [
'0x0000000000000000000000000000000000000000000000000000000000000001',
'0x0000000000000000000000000000000000000000000000000000000000000002',
'0x0000000000000000000000000000000000000000000000000000000000000003',
'0x0000000000000000000000000000000000000000000000000000000000000004',
'0x0000000000000000000000000000000000000000000000000000000000000005',
'0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000002',
'0x0000000000000000000000000000000000000000000000000000000000000006',
'0x0000000000000000000000000000000000000000000000000000000000000007',
'0x0000000000000000000000000000000000000000000000000000000000000008',
'0x0000000000000000000000000000000000000000000000000000000000000009',
].map(Fr.fromHexString);

const request = NoteValidationRequest.fromFields(serialized);

expect(request.contractAddress).toEqual(AztecAddress.fromBigInt(1n));
expect(request.storageSlot).toEqual(new Fr(2));
expect(request.nonce).toEqual(new Fr(3));
expect(request.content).toEqual([new Fr(4), new Fr(5)]);
expect(request.noteHash).toEqual(new Fr(6));
expect(request.nullifier).toEqual(new Fr(7));
expect(request.txHash).toEqual(TxHash.fromBigInt(8n));
expect(request.recipient).toEqual(AztecAddress.fromBigInt(9n));
});
});
Loading