Skip to content

Commit 25c8b04

Browse files
authored
feat!: add test env note discovery (#16384)
This adds `TestEnvironment::discover_note`, which is needed to emulate the note discovery that happens in real contracts for notes created in a `TestEnvironment::pirvate_context`. With this we can now extend our testing of note-based state variables, as well as properly tests constructs such as `DelayedPrivateMutable`. I introduced some minor adjustments to the internals of the message encoding functions to better reuse those capabitilies. These are part of a larger set of changes we'll likely want to introduce as we unify and improve the note and event emission APIs. edit: I also added some tests for note destruction and checking that we do not return those notes, both in transient and settled scenarios. I'm only testing private context and not utility context to keep the number of tests here small, but we should later expand these, perhaps with some more organization of the internals of the note getter API. Interestingly, I had to pseudo introduce the notion of 'syncing' and how it relates to nullified note discovery (#12553) here, since direct context usage does not run the autogenerated note discovery code. This resulted in some findings, notably the note data provider being a bit annoying about requiring knowledge of the recipient of the notes that were being nullified, seemingly for no good reason. I had to remove all of that because `env::discover_note` sets the zero address as the recipient as none are assumed to exist, and so when looking up notes we'd want to check for nullification we'd find none since our PXE had no accounts. This now searches _all_ notes in scope, regardless of who their recipient is.
2 parents 9521586 + 6ab2d33 commit 25c8b04

File tree

26 files changed

+1161
-290
lines changed

26 files changed

+1161
-290
lines changed

noir-projects/aztec-nr/aztec/src/macros/aztec.nr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ comptime fn generate_process_message() -> Quoted {
276276
message_ciphertext: BoundedVec<Field, aztec::messages::encoding::MESSAGE_CIPHERTEXT_LEN>,
277277
message_context: aztec::messages::processing::message_context::MessageContext,
278278
) {
279-
aztec::messages::discovery::process_message::do_process_message(
279+
aztec::messages::discovery::process_message::process_message_ciphertext(
280280
context.this_address(),
281281
_compute_note_hash_and_nullifier,
282282
message_ciphertext,

noir-projects/aztec-nr/aztec/src/messages/discovery/mod.nr

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ pub mod process_message;
88

99
use crate::{
1010
messages::{
11-
discovery::{private_notes::MAX_NOTE_PACKED_LEN, process_message::do_process_message},
11+
discovery::{
12+
private_notes::MAX_NOTE_PACKED_LEN, process_message::process_message_ciphertext,
13+
},
1214
processing::{
1315
get_private_logs, pending_tagged_log::PendingTaggedLog,
1416
validate_enqueued_notes_and_events,
@@ -59,7 +61,7 @@ pub struct NoteHashAndNullifier {
5961
/// };
6062
/// }
6163
/// ```
62-
type ComputeNoteHashAndNullifier<Env> = unconstrained fn[Env](/* packed_note */BoundedVec<Field, MAX_NOTE_PACKED_LEN>, /* storage_slot */ Field, /* note_type_id */ Field, /* contract_address */ AztecAddress, /* note nonce */ Field) -> Option<NoteHashAndNullifier>;
64+
pub type ComputeNoteHashAndNullifier<Env> = unconstrained fn[Env](/* packed_note */BoundedVec<Field, MAX_NOTE_PACKED_LEN>, /* storage_slot */ Field, /* note_type_id */ Field, /* contract_address */ AztecAddress, /* note nonce */ Field) -> Option<NoteHashAndNullifier>;
6365

6466
/// Performs the message discovery process, in which private logs are downloaded and inspected to find new private
6567
/// notes, partial notes and events, etc., and pending partial notes are processed to search for their completion logs.
@@ -88,7 +90,7 @@ pub unconstrained fn discover_new_messages<Env>(
8890
// We remove the tag from the pending tagged log and process the message ciphertext contained in it.
8991
let message_ciphertext = array::subbvec(pending_tagged_log.log, 1);
9092

91-
do_process_message(
93+
process_message_ciphertext(
9294
contract_address,
9395
compute_note_hash_and_nullifier,
9496
message_ciphertext,

noir-projects/aztec-nr/aztec/src/messages/discovery/process_message.nr

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use crate::messages::{
33
ComputeNoteHashAndNullifier, partial_notes::process_partial_note_private_msg,
44
private_events::process_private_event_msg, private_notes::process_private_note_msg,
55
},
6-
encoding::{decode_message, MESSAGE_CIPHERTEXT_LEN},
6+
encoding::{decode_message, MESSAGE_CIPHERTEXT_LEN, MESSAGE_PLAINTEXT_LEN},
77
encryption::{aes128::AES128, log_encryption::LogEncryption},
88
msg_type::{
99
PARTIAL_NOTE_PRIVATE_MSG_TYPE_ID, PRIVATE_EVENT_MSG_TYPE_ID, PRIVATE_NOTE_MSG_TYPE_ID,
@@ -25,19 +25,31 @@ use protocol_types::{address::AztecAddress, debug_log::{debug_log, debug_log_for
2525
///
2626
/// Events are processed by computing an event commitment from the serialized event data and its randomness field, then
2727
/// enqueueing the event data and commitment for validation.
28-
pub unconstrained fn do_process_message<Env>(
28+
pub unconstrained fn process_message_ciphertext<Env>(
2929
contract_address: AztecAddress,
3030
compute_note_hash_and_nullifier: ComputeNoteHashAndNullifier<Env>,
3131
message_ciphertext: BoundedVec<Field, MESSAGE_CIPHERTEXT_LEN>,
3232
message_context: MessageContext,
3333
) {
34-
let message = AES128::decrypt_log(message_ciphertext, message_context.recipient);
34+
process_message_plaintext(
35+
contract_address,
36+
compute_note_hash_and_nullifier,
37+
AES128::decrypt_log(message_ciphertext, message_context.recipient),
38+
message_context,
39+
);
40+
}
3541

42+
pub unconstrained fn process_message_plaintext<Env>(
43+
contract_address: AztecAddress,
44+
compute_note_hash_and_nullifier: ComputeNoteHashAndNullifier<Env>,
45+
message_plaintext: BoundedVec<Field, MESSAGE_PLAINTEXT_LEN>,
46+
message_context: MessageContext,
47+
) {
3648
// The first thing to do after decrypting the message is to determine what type of message we're processing. We
3749
// have 3 message types: private notes, partial notes and events.
3850

3951
// We decode the message to obtain the message type id, metadata and content.
40-
let (msg_type_id, msg_metadata, msg_content) = decode_message(message);
52+
let (msg_type_id, msg_metadata, msg_content) = decode_message(message_plaintext);
4153

4254
if msg_type_id == PRIVATE_NOTE_MSG_TYPE_ID {
4355
debug_log("Processing private note msg");

noir-projects/aztec-nr/aztec/src/messages/logs/note.nr

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,43 +3,36 @@ use crate::{
33
encoding::encode_message,
44
encryption::{aes128::AES128, log_encryption::LogEncryption},
55
logs::utils::prefix_with_tag,
6-
msg_type::PARTIAL_NOTE_PRIVATE_MSG_TYPE_ID,
6+
msg_type::{PARTIAL_NOTE_PRIVATE_MSG_TYPE_ID, PRIVATE_NOTE_MSG_TYPE_ID},
77
},
88
note::note_interface::NoteType,
99
};
1010
use protocol_types::{
11-
address::AztecAddress,
12-
constants::{PRIVATE_LOG_CIPHERTEXT_LEN, PRIVATE_LOG_SIZE_IN_FIELDS},
13-
traits::Packable,
11+
address::AztecAddress, constants::PRIVATE_LOG_SIZE_IN_FIELDS, traits::Packable,
1412
};
1513

1614
// TODO(#16881): once partial notes support emission via an offchain message we will most likely want to remove this.
17-
pub fn compute_partial_note_log<Note>(
18-
note: Note,
15+
pub fn compute_partial_note_private_content_log<PartialNotePrivateContent>(
16+
partial_note_private_content: PartialNotePrivateContent,
1917
storage_slot: Field,
2018
recipient: AztecAddress,
2119
) -> [Field; PRIVATE_LOG_SIZE_IN_FIELDS]
2220
where
23-
Note: NoteType + Packable,
21+
PartialNotePrivateContent: NoteType + Packable,
2422
{
25-
let ciphertext = compute_note_message_ciphertext(
26-
note,
23+
let message_plaintext = partial_note_private_content_to_message_plaintext(
24+
partial_note_private_content,
2725
storage_slot,
28-
recipient,
29-
PARTIAL_NOTE_PRIVATE_MSG_TYPE_ID,
3026
);
27+
let message_ciphertext = AES128::encrypt_log(message_plaintext, recipient);
3128

32-
let log = prefix_with_tag(ciphertext, recipient);
33-
34-
log
29+
prefix_with_tag(message_ciphertext, recipient)
3530
}
3631

37-
pub fn compute_note_message_ciphertext<Note>(
32+
pub fn private_note_to_message_plaintext<Note>(
3833
note: Note,
3934
storage_slot: Field,
40-
recipient: AztecAddress,
41-
msg_type: u64,
42-
) -> [Field; PRIVATE_LOG_CIPHERTEXT_LEN]
35+
) -> [Field; <Note as Packable>::N + 2]
4336
where
4437
Note: NoteType + Packable,
4538
{
@@ -53,7 +46,29 @@ where
5346
}
5447

5548
// Notes use the note type id for metadata
56-
let plaintext = encode_message(msg_type, Note::get_id() as u64, msg_content);
49+
encode_message(PRIVATE_NOTE_MSG_TYPE_ID, Note::get_id() as u64, msg_content)
50+
}
51+
52+
pub fn partial_note_private_content_to_message_plaintext<PartialNotePrivateContent>(
53+
partial_note_private_content: PartialNotePrivateContent,
54+
storage_slot: Field,
55+
) -> [Field; <PartialNotePrivateContent as Packable>::N + 2]
56+
where
57+
PartialNotePrivateContent: NoteType + Packable,
58+
{
59+
let packed_private_content = partial_note_private_content.pack();
5760

58-
AES128::encrypt_log(plaintext, recipient)
61+
// A partial note message's content is the storage slot followed by the packed private content representation
62+
let mut msg_content = [0; 1 + <PartialNotePrivateContent as Packable>::N];
63+
msg_content[0] = storage_slot;
64+
for i in 0..packed_private_content.len() {
65+
msg_content[1 + i] = packed_private_content[i];
66+
}
67+
68+
encode_message(
69+
PARTIAL_NOTE_PRIVATE_MSG_TYPE_ID,
70+
// Notes use the note type id for metadata
71+
PartialNotePrivateContent::get_id() as u64,
72+
msg_content,
73+
)
5974
}

noir-projects/aztec-nr/aztec/src/note/note_emission.nr

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
use crate::{
22
context::PrivateContext,
33
messages::{
4-
logs::{note::compute_note_message_ciphertext, utils::prefix_with_tag},
4+
encryption::{aes128::AES128, log_encryption::LogEncryption},
5+
logs::{note::private_note_to_message_plaintext, utils::prefix_with_tag},
56
message_delivery::MessageDelivery,
6-
msg_type::PRIVATE_NOTE_MSG_TYPE_ID,
77
offchain_messages::emit_offchain_message,
88
},
99
note::note_interface::NoteType,
@@ -52,11 +52,9 @@ where
5252

5353
let ciphertext = remove_constraints_if(
5454
!constrained_encryption,
55-
|| compute_note_message_ciphertext(
56-
self.note,
57-
self.storage_slot,
55+
|| AES128::encrypt_log(
56+
private_note_to_message_plaintext(self.note, self.storage_slot),
5857
recipient,
59-
PRIVATE_NOTE_MSG_TYPE_ID,
6058
),
6159
);
6260

noir-projects/aztec-nr/aztec/src/note/note_getter.nr

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ where
8181
{
8282
// Safety: Constraining that we got a valid note from the oracle is fairly straightforward: all we need to do
8383
// is check that the metadata is correct, and that the note exists.
84-
let retrieved_note = unsafe { get_note_internal::<Note>(storage_slot) };
84+
let retrieved_note = unsafe { view_note::<Note>(storage_slot) };
8585

8686
// For settled notes, the contract address is implicitly checked since the hash returned from
8787
// `compute_note_hash_for_read_request` is siloed and kernels verify the siloing during note read request
@@ -189,7 +189,7 @@ where
189189
(notes, note_hashes)
190190
}
191191

192-
unconstrained fn get_note_internal<Note>(storage_slot: Field) -> RetrievedNote<Note>
192+
pub unconstrained fn view_note<Note>(storage_slot: Field) -> RetrievedNote<Note>
193193
where
194194
Note: NoteType + Packable,
195195
{
@@ -210,7 +210,7 @@ where
210210
NoteStatus.ACTIVE,
211211
);
212212

213-
opt_notes[0].expect(f"Failed to get a note") // Notice: we don't allow dummies to be returned from get_note (singular).
213+
opt_notes[0].expect(f"Failed to get a note")
214214
}
215215

216216
unconstrained fn get_notes_internal<Note, let M: u32, PreprocessorArgs, FilterArgs>(

noir-projects/aztec-nr/aztec/src/note/note_getter/test.nr

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ where
3737
}
3838

3939
#[test]
40-
unconstrained fn processes_single_note() {
40+
unconstrained fn constrain_get_notes_processes_single_note() {
4141
let env = TestEnvironment::new();
4242

4343
env.private_context(|context| {
@@ -55,7 +55,7 @@ unconstrained fn processes_single_note() {
5555
}
5656

5757
#[test]
58-
unconstrained fn processes_many_notes() {
58+
unconstrained fn constrain_get_notes_processes_many_notes() {
5959
let env = TestEnvironment::new();
6060

6161
env.private_context(|context| {
@@ -75,7 +75,7 @@ unconstrained fn processes_many_notes() {
7575
}
7676

7777
#[test]
78-
unconstrained fn collapses_notes_at_the_beginning_of_the_array() {
78+
unconstrained fn constrain_get_notes_collapses_notes_at_the_beginning_of_the_array() {
7979
let env = TestEnvironment::new();
8080

8181
env.private_context(|context| {
@@ -106,7 +106,7 @@ unconstrained fn collapses_notes_at_the_beginning_of_the_array() {
106106
}
107107

108108
#[test]
109-
unconstrained fn can_return_zero_notes() {
109+
unconstrained fn constrain_get_notes_can_return_zero_notes() {
110110
let env = TestEnvironment::new();
111111

112112
env.private_context(|context| {
@@ -120,7 +120,7 @@ unconstrained fn can_return_zero_notes() {
120120
}
121121

122122
#[test(should_fail_with = "Got more notes than limit.")]
123-
unconstrained fn rejects_mote_notes_than_limit() {
123+
unconstrained fn constrain_get_notes_rejects_mote_notes_than_limit() {
124124
let env = TestEnvironment::new();
125125

126126
env.private_context(|context| {
@@ -138,7 +138,7 @@ unconstrained fn rejects_mote_notes_than_limit() {
138138
}
139139

140140
#[test]
141-
unconstrained fn applies_filter_before_constraining() {
141+
unconstrained fn constrain_get_notes_applies_filter_before_constraining() {
142142
let env = TestEnvironment::new();
143143

144144
env.private_context(|context| {
@@ -175,7 +175,7 @@ unconstrained fn applies_filter_before_constraining() {
175175
}
176176

177177
#[test(should_fail_with = "Note contract address mismatch.")]
178-
unconstrained fn rejects_mismatched_address() {
178+
unconstrained fn constrain_get_notes_rejects_mismatched_address() {
179179
let env = TestEnvironment::new();
180180

181181
env.private_context(|context| {
@@ -189,7 +189,7 @@ unconstrained fn rejects_mismatched_address() {
189189
}
190190

191191
#[test(should_fail_with = "Mismatch return note field.")]
192-
unconstrained fn rejects_mismatched_selector() {
192+
unconstrained fn constrain_get_notes_rejects_mismatched_selector() {
193193
let env = TestEnvironment::new();
194194

195195
env.private_context(|context| {
@@ -211,7 +211,7 @@ unconstrained fn rejects_mismatched_selector() {
211211
}
212212

213213
#[test(should_fail_with = "Return notes not sorted in descending order.")]
214-
unconstrained fn rejects_mismatched_desc_sort_order() {
214+
unconstrained fn constrain_get_notes_rejects_mismatched_desc_sort_order() {
215215
let env = TestEnvironment::new();
216216

217217
env.private_context(|context| {
@@ -229,7 +229,7 @@ unconstrained fn rejects_mismatched_desc_sort_order() {
229229
}
230230

231231
#[test(should_fail_with = "Return notes not sorted in ascending order.")]
232-
unconstrained fn rejects_mismatched_asc_sort_order() {
232+
unconstrained fn constrain_get_notes_rejects_mismatched_asc_sort_order() {
233233
let env = TestEnvironment::new();
234234

235235
env.private_context(|context| {

noir-projects/aztec-nr/aztec/src/state_vars/private_mutable.nr

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,15 @@ use crate::context::{PrivateContext, UtilityContext};
77
use crate::note::{
88
lifecycle::{create_note, destroy_note_unsafe},
99
note_emission::NoteEmission,
10-
note_getter::{get_note, view_notes},
10+
note_getter::{get_note, view_note},
1111
note_interface::{NoteHash, NoteType},
12-
note_viewer_options::NoteViewerOptions,
1312
};
1413
use crate::note::retrieved_note::RetrievedNote;
1514
use crate::oracle::notes::check_nullifier_exists;
1615
use crate::state_vars::storage::HasStorageSlot;
1716

17+
mod test;
18+
1819
/// # PrivateMutable
1920
///
2021
/// PrivateMutable is a private state variable type, which enables you to read, mutate,
@@ -108,8 +109,6 @@ pub struct PrivateMutable<Note, Context> {
108109
}
109110
// docs:end:struct
110111

111-
mod test;
112-
113112
// Private storage slots are not really 'slots' but rather a value in the note hash preimage, so there is no notion of a
114113
// value spilling over multiple slots. For this reason PrivateMutable (and all other private state variables) needs just
115114
// one slot to be reserved, regardless of what it stores.
@@ -465,8 +464,7 @@ where
465464
where
466465
Note: Packable,
467466
{
468-
let mut options = NoteViewerOptions::<Note, <Note as Packable>::N>::new();
469-
view_notes(self.storage_slot, options.set_limit(1)).get(0)
467+
view_note(self.storage_slot).note
470468
}
471469
// docs:end:view_note
472470
}

0 commit comments

Comments
 (0)