diff --git a/docs/docs-developers/docs/resources/migration_notes.md b/docs/docs-developers/docs/resources/migration_notes.md index 81871bcea410..737b84730c70 100644 --- a/docs/docs-developers/docs/resources/migration_notes.md +++ b/docs/docs-developers/docs/resources/migration_notes.md @@ -9,6 +9,40 @@ Aztec is in active development. Each version may introduce breaking changes that ## TBD +### [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. + +`process_private_note_msg` takes the raw `msg_metadata` and `msg_content` rather than already-decoded note fields, so it handles decoding (and silently discards undecodable messages) on your behalf: + +```diff +- attempt_note_discovery( +- contract_address, +- tx_hash, +- unique_note_hashes_in_tx, +- first_nullifier_in_tx, +- compute_note_hash, +- compute_note_nullifier, +- owner, +- storage_slot, +- randomness, +- note_type_id, +- packed_note, +- ); ++ process_private_note_msg( ++ contract_address, ++ tx_hash, ++ unique_note_hashes_in_tx, ++ first_nullifier_in_tx, ++ compute_note_hash, ++ compute_note_nullifier, ++ msg_metadata, ++ msg_content, ++ ); +``` + +**Impact**: Custom message handlers that reused the standard note message processing pipeline must switch to `process_private_note_msg`. Contracts using only built-in private note handling are unaffected. + ### [aztec-up] Bundled binaries are no longer exposed under bare names on `PATH` The Aztec installer previously placed bundled binaries directly into `$HOME/.aztec/current/bin` under bare names (`forge`, `nargo`, `bb`, `pxe`, ...). Anything with the same name in your own `PATH` was silently shadowed in unrelated projects. diff --git a/noir-projects/aztec-nr/aztec/src/macros/aztec/compute_note_hash_and_nullifier.nr b/noir-projects/aztec-nr/aztec/src/macros/aztec/compute_note_hash_and_nullifier.nr index 32a39cc3910d..2f8144b3d6a4 100644 --- a/noir-projects/aztec-nr/aztec/src/macros/aztec/compute_note_hash_and_nullifier.nr +++ b/noir-projects/aztec-nr/aztec/src/macros/aztec/compute_note_hash_and_nullifier.nr @@ -126,9 +126,12 @@ comptime fn generate_contract_library_method_compute_note_hash() -> Quoted { let if_note_type_id_match_statements = if_note_type_id_match_statements_list.join(quote {}); quote { - /// Unpacks an array into a note corresponding to `note_type_id` and then computes its note hash (non-siloed). + /// Unpacks an array into a note corresponding to `note_type_id` and then computes its note hash + /// (non-siloed). /// - /// The signature of this function notably matches the `aztec::messages::discovery::ComputeNoteHash` type, and so it can be used to call functions from that module such as `do_sync_state` and `attempt_note_discovery`. + /// The signature of this function notably matches the `aztec::messages::discovery::ComputeNoteHash` type, + /// and so it can be used to call functions from that module such as `do_sync_state` and + /// `process_private_note_msg`. /// /// This function is automatically injected by the `#[aztec]` macro. #[contract_library_method] @@ -240,7 +243,9 @@ comptime fn generate_contract_library_method_compute_note_nullifier() -> Quoted quote { /// Computes a note's inner nullifier (non-siloed) given its unique note hash, preimage and extra data. /// - /// The signature of this function notably matches the `aztec::messages::discovery::ComputeNoteNullifier` type, and so it can be used to call functions from that module such as `do_sync_state` and `attempt_note_discovery`. + /// The signature of this function notably matches the `aztec::messages::discovery::ComputeNoteNullifier` + /// type, and so it can be used to call functions from that module such as `do_sync_state` and + /// `process_private_note_msg`. /// /// This function is automatically injected by the `#[aztec]` macro. #[contract_library_method] diff --git a/noir-projects/aztec-nr/aztec/src/messages/discovery/private_notes.nr b/noir-projects/aztec-nr/aztec/src/messages/discovery/private_notes.nr index 642aec44d24f..052636b847c0 100644 --- a/noir-projects/aztec-nr/aztec/src/messages/discovery/private_notes.nr +++ b/noir-projects/aztec-nr/aztec/src/messages/discovery/private_notes.nr @@ -9,7 +9,26 @@ use crate::{ protocol::{address::AztecAddress, constants::MAX_NOTE_HASHES_PER_TX, traits::ToField}, }; -pub(crate) unconstrained fn process_private_note_msg( +/// Processes a private note message, attempting to discover and enqueue any notes it contains. +/// +/// For each note recovered from the message whose computed note hash matches a unique note hash in the transaction, +/// a [`NoteValidationRequest`](crate::messages::processing::NoteValidationRequest) is pushed onto the ephemeral array +/// at `NOTE_VALIDATION_REQUESTS_ARRAY_BASE_SLOT` via +/// [`enqueue_note_for_validation`](crate::messages::processing::enqueue_note_for_validation). PXE later drains this +/// array during `validate_and_store_enqueued_notes_and_events`, after which the notes are retrievable via `get_notes`. +/// +/// Messages that fail to decode, or whose computed note hash matches nothing in the transaction, are discarded +/// (with a debug or warning log respectively) and produce no validation requests. Decode failures are not treated +/// as errors since messages may originate from malicious senders and we don't want them to be able to brick message +/// processing. +/// +/// ## Use Cases +/// +/// This function is invoked automatically by aztec-nr when handling messages with the built-in private note type id, +/// so contracts do not normally need to call it directly. It is exposed for use by custom message handlers (see +/// [`CustomMessageHandler`](crate::messages::discovery::CustomMessageHandler)) that might want to use the standard +/// note message processing pipeline while extending it with custom logic. +pub unconstrained fn process_private_note_msg( contract_address: AztecAddress, tx_hash: Field, unique_note_hashes_in_tx: BoundedVec, @@ -48,7 +67,7 @@ pub(crate) unconstrained fn process_private_note_msg( /// Attempts discovery of a note given information about its contents and the transaction in which it is suspected the /// note was created. -pub unconstrained fn attempt_note_discovery( +unconstrained fn attempt_note_discovery( contract_address: AztecAddress, tx_hash: Field, unique_note_hashes_in_tx: BoundedVec, diff --git a/noir-projects/aztec-nr/aztec/src/messages/logs/note.nr b/noir-projects/aztec-nr/aztec/src/messages/logs/note.nr index b78a17b09102..7750d0d630b8 100644 --- a/noir-projects/aztec-nr/aztec/src/messages/logs/note.nr +++ b/noir-projects/aztec-nr/aztec/src/messages/logs/note.nr @@ -19,10 +19,12 @@ pub(crate) global PRIVATE_NOTE_MSG_PLAINTEXT_RANDOMNESS_INDEX: u32 = 2; /// encryption overhead and extra fields in the message (e.g. message type id, storage slot, randomness, etc.). pub global MAX_NOTE_PACKED_LEN: u32 = MAX_MESSAGE_CONTENT_LEN - PRIVATE_NOTE_MSG_PLAINTEXT_RESERVED_FIELDS_LEN; -/// Creates the plaintext for a private note message (i.e. one of type [`PRIVATE_NOTE_MSG_TYPE_ID`]). +/// Creates the plaintext for a private note message with the given `msg_type_id`. /// -/// This plaintext is meant to be decoded via [`decode_private_note_message`]. -pub fn encode_private_note_message( +/// Shared encoder used by [`encode_private_note_message`] and by custom message handlers that use the same format as +/// standard private note message but perform additional validation logic. +pub fn encode_private_note_message_with_msg_type_id( + msg_type_id: u64, note: Note, owner: AztecAddress, storage_slot: Field, @@ -49,7 +51,28 @@ where } // Notes use the note type id for metadata - encode_message(PRIVATE_NOTE_MSG_TYPE_ID, Note::get_id() as u64, msg_content) + encode_message(msg_type_id, Note::get_id() as u64, msg_content) +} + +/// Creates the plaintext for a private note message (i.e. one of type [`PRIVATE_NOTE_MSG_TYPE_ID`]). +/// +/// This plaintext is meant to be decoded via [`decode_private_note_message`]. +pub fn encode_private_note_message( + note: Note, + owner: AztecAddress, + storage_slot: Field, + randomness: Field, +) -> [Field; PRIVATE_NOTE_MSG_PLAINTEXT_RESERVED_FIELDS_LEN + ::N + MESSAGE_EXPANDED_METADATA_LEN] +where + Note: NoteType + Packable, +{ + encode_private_note_message_with_msg_type_id( + PRIVATE_NOTE_MSG_TYPE_ID, + note, + owner, + storage_slot, + randomness, + ) } /// Decodes the plaintext from a private note message (i.e. one of type [`PRIVATE_NOTE_MSG_TYPE_ID`]). diff --git a/noir-projects/aztec-nr/aztec/src/messages/processing/mod.nr b/noir-projects/aztec-nr/aztec/src/messages/processing/mod.nr index 9fff728312c8..15083399a9e1 100644 --- a/noir-projects/aztec-nr/aztec/src/messages/processing/mod.nr +++ b/noir-projects/aztec-nr/aztec/src/messages/processing/mod.nr @@ -4,7 +4,9 @@ pub mod offchain; mod message_context; pub use message_context::MessageContext; -pub(crate) mod note_validation_request; +mod note_validation_request; +pub use note_validation_request::NoteValidationRequest; + pub(crate) mod log_retrieval_request; pub(crate) mod log_retrieval_response; pub(crate) mod pending_tagged_log; @@ -17,10 +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, - note_validation_request::NoteValidationRequest, - }, + processing::{log_retrieval_request::LogRetrievalRequest, log_retrieval_response::LogRetrievalResponse}, }, oracle::message_processing, }; diff --git a/noir-projects/aztec-nr/aztec/src/messages/processing/note_validation_request.nr b/noir-projects/aztec-nr/aztec/src/messages/processing/note_validation_request.nr index 24dd806948d7..8c08ef470d45 100644 --- a/noir-projects/aztec-nr/aztec/src/messages/processing/note_validation_request.nr +++ b/noir-projects/aztec-nr/aztec/src/messages/processing/note_validation_request.nr @@ -1,11 +1,11 @@ use crate::messages::logs::note::MAX_NOTE_PACKED_LEN; -use crate::protocol::{address::AztecAddress, traits::Serialize}; +use crate::protocol::{address::AztecAddress, traits::{Deserialize, Serialize}}; /// Intermediate struct used to perform batch note validation by PXE. The /// `aztec_utl_validateAndStoreEnqueuedNotesAndEvents` oracle expects for values of this type to be stored in a /// `EphemeralArray`. -#[derive(Serialize)] -pub(crate) struct NoteValidationRequest { +#[derive(Serialize, Deserialize)] +pub struct NoteValidationRequest { pub contract_address: AztecAddress, pub owner: AztecAddress, pub storage_slot: Field, diff --git a/noir-projects/aztec-nr/aztec/src/note/confirmed_note.nr b/noir-projects/aztec-nr/aztec/src/note/confirmed_note.nr index 57b217943e83..22deff1a5731 100644 --- a/noir-projects/aztec-nr/aztec/src/note/confirmed_note.nr +++ b/noir-projects/aztec-nr/aztec/src/note/confirmed_note.nr @@ -33,7 +33,7 @@ pub struct ConfirmedNote { /// The note hash used to prove existence. /// /// Whether this note hash is unsiloed or unique depends on the note's metadata. - pub(crate) proven_note_hash: Field, + pub proven_note_hash: Field, } impl ConfirmedNote { diff --git a/noir-projects/aztec-nr/aztec/src/note/lifecycle.nr b/noir-projects/aztec-nr/aztec/src/note/lifecycle.nr index 70773faaa250..b432378a02a7 100644 --- a/noir-projects/aztec-nr/aztec/src/note/lifecycle.nr +++ b/noir-projects/aztec-nr/aztec/src/note/lifecycle.nr @@ -11,13 +11,22 @@ use crate::{ use crate::protocol::{address::AztecAddress, traits::Packable}; /// A note that was created in the current contract call. +/// +/// This struct holds a freshly created note along with the side-effect counter that the kernel uses to order note +/// creations within a transaction. It is produced by [`create_note`] and is typically wrapped in a +/// [`NoteMessage`](crate::note::NoteMessage), which is responsible for delivering the note's information to its +/// recipient so that it is not lost. +/// +/// Unlike [`ConfirmedNote`](crate::note::ConfirmedNote), which represents a note whose existence has been proven +/// (either by reading it from PXE or by checking historical state), a `NewNote` represents a note whose creation is +/// still pending in the current transaction's side-effect stream. Its note hash has been pushed into the +/// [`PrivateContext`] but has not yet been siloed nor inserted into the note hash tree. pub struct NewNote { - pub(crate) note: Note, - pub(crate) owner: AztecAddress, - pub(crate) storage_slot: Field, - pub(crate) randomness: Field, - /// The [`PrivateContext`] side-effect counter associated with the creation of this note. - pub(crate) note_hash_counter: u32, + pub note: Note, + pub owner: AztecAddress, + pub storage_slot: Field, + pub randomness: Field, + pub note_hash_counter: u32, } impl NewNote { diff --git a/noir-projects/aztec-nr/aztec/src/oracle/get_membership_witness.nr b/noir-projects/aztec-nr/aztec/src/oracle/get_membership_witness.nr index 8c1783f085b3..646ab45f510a 100644 --- a/noir-projects/aztec-nr/aztec/src/oracle/get_membership_witness.nr +++ b/noir-projects/aztec-nr/aztec/src/oracle/get_membership_witness.nr @@ -11,16 +11,17 @@ unconstrained fn get_note_hash_membership_witness_oracle( note_hash: Field, ) -> MembershipWitness {} -#[oracle(aztec_utl_getBlockHashMembershipWitness)] +#[oracle(aztec_utl_getBlockHashMembershipWitnessV2)] unconstrained fn get_block_hash_membership_witness_oracle( anchor_block_hash: Field, block_hash: Field, -) -> MembershipWitness {} +) -> Option> {} // Note: get_nullifier_membership_witness function is implemented in get_nullifier_membership_witness.nr /// Returns a membership witness for a `note_hash` in the note hash tree whose root is defined in // `anchor_block_header`. +// TODO(https://linear.app/aztec-labs/issue/F-652): add Noir tests for this oracle pub unconstrained fn get_note_hash_membership_witness( anchor_block_header: BlockHeader, note_hash: Field, @@ -37,6 +38,103 @@ pub unconstrained fn get_block_hash_membership_witness( anchor_block_header: BlockHeader, block_hash: Field, ) -> MembershipWitness { + let anchor_block_hash = anchor_block_header.hash(); + get_block_hash_membership_witness_oracle(anchor_block_hash, block_hash).expect( + f"Block hash {block_hash} not found in the archive tree at anchor block {anchor_block_hash}.", + ) +} + +/// Same as [`get_block_hash_membership_witness`], but returns `None` instead of erroring when the block is not present +/// in the archive tree. Intended for callers that need to handle absence (e.g. existence checks via `.is_some()`). +pub unconstrained fn get_maybe_block_hash_membership_witness( + anchor_block_header: BlockHeader, + block_hash: Field, +) -> Option> { let anchor_block_hash = anchor_block_header.hash(); get_block_hash_membership_witness_oracle(anchor_block_hash, block_hash) } + +mod test { + use crate::oracle::block_header::get_block_header_at; + use crate::protocol::{merkle_tree::root::root_from_sibling_path, traits::Hash}; + use crate::test::helpers::test_environment::TestEnvironment; + use super::{get_block_hash_membership_witness, get_maybe_block_hash_membership_witness}; + + #[test] + unconstrained fn get_block_hash_membership_witness_returns_valid_witness_for_known_block() { + let env = TestEnvironment::new(); + + env.mine_block(); + env.mine_block(); + env.mine_block(); + env.mine_block(); + + env.private_context(|context| { + let anchor = context.anchor_block_header; + let target_block_number = anchor.block_number() - 2; + + let target_header = get_block_header_at(target_block_number, *context); + let target_hash = target_header.hash(); + + let witness = get_block_hash_membership_witness(anchor, target_hash); + + assert_eq( + root_from_sibling_path(target_hash, witness.leaf_index, witness.sibling_path), + anchor.last_archive.root, + ); + }); + } + + #[test(should_fail_with = "not found in the archive tree at anchor block")] + unconstrained fn get_block_hash_membership_witness_panics_for_unknown_block() { + let env = TestEnvironment::new(); + + env.mine_block(); + env.mine_block(); + + env.private_context(|context| { + let anchor = context.anchor_block_header; + let _witness = get_block_hash_membership_witness(anchor, 0xdeadbeef); + }); + } + + #[test] + unconstrained fn get_maybe_block_hash_membership_witness_returns_some_for_known_block() { + let env = TestEnvironment::new(); + + env.mine_block(); + env.mine_block(); + env.mine_block(); + env.mine_block(); + + env.private_context(|context| { + let anchor = context.anchor_block_header; + let target_block_number = anchor.block_number() - 2; + + let target_header = get_block_header_at(target_block_number, *context); + let target_hash = target_header.hash(); + + let witness = get_maybe_block_hash_membership_witness(anchor, target_hash).expect( + f"Expected Some witness for known block hash", + ); + + assert_eq( + root_from_sibling_path(target_hash, witness.leaf_index, witness.sibling_path), + anchor.last_archive.root, + ); + }); + } + + #[test] + unconstrained fn get_maybe_block_hash_membership_witness_returns_none_for_unknown_block() { + let env = TestEnvironment::new(); + + env.mine_block(); + env.mine_block(); + + env.private_context(|context| { + let anchor = context.anchor_block_header; + assert(get_maybe_block_hash_membership_witness(anchor, 0xdeadbeef).is_none()); + }); + } +} diff --git a/noir-projects/aztec-nr/aztec/src/oracle/message_processing.nr b/noir-projects/aztec-nr/aztec/src/oracle/message_processing.nr index c34026410b02..49f7de27589e 100644 --- a/noir-projects/aztec-nr/aztec/src/oracle/message_processing.nr +++ b/noir-projects/aztec-nr/aztec/src/oracle/message_processing.nr @@ -4,6 +4,7 @@ use crate::messages::processing::{ 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. @@ -62,3 +63,11 @@ pub(crate) unconstrained fn get_message_contexts_by_tx_hash( #[oracle(aztec_utl_getMessageContextsByTxHash_v2)] unconstrained fn get_message_contexts_by_tx_hash_v2_oracle(request_array_slot: Field) -> Field {} + +/// Fetches all effects of a settled transaction by its hash. +pub unconstrained fn get_tx_effect(tx_hash: Field) -> Option { + get_tx_effect_oracle(tx_hash) +} + +#[oracle(aztec_utl_getTxEffect)] +unconstrained fn get_tx_effect_oracle(tx_hash: Field) -> Option {} diff --git a/noir-projects/aztec-nr/aztec/src/oracle/version.nr b/noir-projects/aztec-nr/aztec/src/oracle/version.nr index df5086ccad04..39ce39d881aa 100644 --- a/noir-projects/aztec-nr/aztec/src/oracle/version.nr +++ b/noir-projects/aztec-nr/aztec/src/oracle/version.nr @@ -11,7 +11,7 @@ /// 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 = 2; +pub global ORACLE_VERSION_MINOR: Field = 3; /// Asserts that the version of the oracle is compatible with the version expected by the contract. pub fn assert_compatible_oracle_version() { diff --git a/noir-projects/contract-snapshots/tests/snapshots/compile_failure/invalid_note/snapshots__stderr.snap b/noir-projects/contract-snapshots/tests/snapshots/compile_failure/invalid_note/snapshots__stderr.snap index 278bb9319a40..c8727783a33e 100644 --- a/noir-projects/contract-snapshots/tests/snapshots/compile_failure/invalid_note/snapshots__stderr.snap +++ b/noir-projects/contract-snapshots/tests/snapshots/compile_failure/invalid_note/snapshots__stderr.snap @@ -22,9 +22,9 @@ error: InvalidNote has a packed length of 9 fields, which exceeds the maximum al 6: process_message_plaintext at /noir-projects/aztec-nr/aztec/src/messages/discovery/process_message.nr:76:13 7: process_private_note_msg - at /noir-projects/aztec-nr/aztec/src/messages/discovery/private_notes.nr:27:9 + at /noir-projects/aztec-nr/aztec/src/messages/discovery/private_notes.nr:46:9 8: attempt_note_discovery - at /noir-projects/aztec-nr/aztec/src/messages/discovery/private_notes.nr:64:28 + at /noir-projects/aztec-nr/aztec/src/messages/discovery/private_notes.nr:83:28 9: attempt_note_nonce_discovery at /noir-projects/aztec-nr/aztec/src/messages/discovery/nonce_discovery.nr:46:27 10: generate_contract_library_method_compute_note_hash diff --git a/noir-projects/contract-snapshots/tests/snapshots/expand/amm_contract/snapshots__expanded.snap b/noir-projects/contract-snapshots/tests/snapshots/expand/amm_contract/snapshots__expanded.snap index c8c61c578431..66137377e0fe 100644 --- a/noir-projects/contract-snapshots/tests/snapshots/expand/amm_contract/snapshots__expanded.snap +++ b/noir-projects/contract-snapshots/tests/snapshots/expand/amm_contract/snapshots__expanded.snap @@ -2,6 +2,7 @@ source: tests/snapshots.rs expression: stdout --- + use aztec::macros::aztec; use aztec::macros::aztec; @@ -333,9 +334,12 @@ pub contract AMM { }) } - /// Unpacks an array into a note corresponding to `note_type_id` and then computes its note hash (non-siloed). + /// Unpacks an array into a note corresponding to `note_type_id` and then computes its note hash + /// (non-siloed). /// - /// The signature of this function notably matches the `aztec::messages::discovery::ComputeNoteHash` type, and so it can be used to call functions from that module such as `do_sync_state` and `attempt_note_discovery`. + /// The signature of this function notably matches the `aztec::messages::discovery::ComputeNoteHash` type, + /// and so it can be used to call functions from that module such as `do_sync_state` and + /// `process_private_note_msg`. /// /// This function is automatically injected by the `#[aztec]` macro. #[contract_library_method] @@ -358,7 +362,9 @@ pub contract AMM { /// Computes a note's inner nullifier (non-siloed) given its unique note hash, preimage and extra data. /// - /// The signature of this function notably matches the `aztec::messages::discovery::ComputeNoteNullifier` type, and so it can be used to call functions from that module such as `do_sync_state` and `attempt_note_discovery`. + /// The signature of this function notably matches the `aztec::messages::discovery::ComputeNoteNullifier` + /// type, and so it can be used to call functions from that module such as `do_sync_state` and + /// `process_private_note_msg`. /// /// This function is automatically injected by the `#[aztec]` macro. #[contract_library_method] diff --git a/noir-projects/contract-snapshots/tests/snapshots/expand/avm_gadgets_test_contract/snapshots__expanded.snap b/noir-projects/contract-snapshots/tests/snapshots/expand/avm_gadgets_test_contract/snapshots__expanded.snap index 485012d47992..2b03d6beb0fa 100644 --- a/noir-projects/contract-snapshots/tests/snapshots/expand/avm_gadgets_test_contract/snapshots__expanded.snap +++ b/noir-projects/contract-snapshots/tests/snapshots/expand/avm_gadgets_test_contract/snapshots__expanded.snap @@ -2,6 +2,7 @@ source: tests/snapshots.rs expression: stdout --- + use aztec::macros::aztec; use aztec::macros::aztec; diff --git a/noir-projects/contract-snapshots/tests/snapshots/expand/avm_test_contract/snapshots__expanded.snap b/noir-projects/contract-snapshots/tests/snapshots/expand/avm_test_contract/snapshots__expanded.snap index 6522da57d5bb..19e218fb7a6d 100644 --- a/noir-projects/contract-snapshots/tests/snapshots/expand/avm_test_contract/snapshots__expanded.snap +++ b/noir-projects/contract-snapshots/tests/snapshots/expand/avm_test_contract/snapshots__expanded.snap @@ -2,6 +2,7 @@ source: tests/snapshots.rs expression: stdout --- + use aztec::macros::aztec; use aztec::macros::aztec; diff --git a/noir-projects/contract-snapshots/tests/snapshots/expand/public_fns_with_emit_repro_contract/snapshots__expanded.snap b/noir-projects/contract-snapshots/tests/snapshots/expand/public_fns_with_emit_repro_contract/snapshots__expanded.snap index f51522828ebc..d34ab63a9bd7 100644 --- a/noir-projects/contract-snapshots/tests/snapshots/expand/public_fns_with_emit_repro_contract/snapshots__expanded.snap +++ b/noir-projects/contract-snapshots/tests/snapshots/expand/public_fns_with_emit_repro_contract/snapshots__expanded.snap @@ -2,6 +2,7 @@ source: tests/snapshots.rs expression: stdout --- + use aztec::macros::aztec; use aztec::macros::aztec; diff --git a/noir-projects/contract-snapshots/tests/snapshots/expand/storage_proof_test_contract/snapshots__expanded.snap b/noir-projects/contract-snapshots/tests/snapshots/expand/storage_proof_test_contract/snapshots__expanded.snap index c53983f3697f..9487c0b24d9c 100644 --- a/noir-projects/contract-snapshots/tests/snapshots/expand/storage_proof_test_contract/snapshots__expanded.snap +++ b/noir-projects/contract-snapshots/tests/snapshots/expand/storage_proof_test_contract/snapshots__expanded.snap @@ -2,6 +2,7 @@ source: tests/snapshots.rs expression: stdout --- + use aztec::macros::aztec; use aztec::macros::aztec; diff --git a/noir-projects/contract-snapshots/tests/snapshots/expand/token_contract/snapshots__expanded.snap b/noir-projects/contract-snapshots/tests/snapshots/expand/token_contract/snapshots__expanded.snap index 4b8a1ce18747..2732a06e0f9c 100644 --- a/noir-projects/contract-snapshots/tests/snapshots/expand/token_contract/snapshots__expanded.snap +++ b/noir-projects/contract-snapshots/tests/snapshots/expand/token_contract/snapshots__expanded.snap @@ -2,6 +2,7 @@ source: tests/snapshots.rs expression: stdout --- + use aztec::macros::aztec; use aztec::macros::aztec; @@ -351,9 +352,12 @@ pub contract Token { }) } - /// Unpacks an array into a note corresponding to `note_type_id` and then computes its note hash (non-siloed). + /// Unpacks an array into a note corresponding to `note_type_id` and then computes its note hash + /// (non-siloed). /// - /// The signature of this function notably matches the `aztec::messages::discovery::ComputeNoteHash` type, and so it can be used to call functions from that module such as `do_sync_state` and `attempt_note_discovery`. + /// The signature of this function notably matches the `aztec::messages::discovery::ComputeNoteHash` type, + /// and so it can be used to call functions from that module such as `do_sync_state` and + /// `process_private_note_msg`. /// /// This function is automatically injected by the `#[aztec]` macro. #[contract_library_method] @@ -376,7 +380,9 @@ pub contract Token { /// Computes a note's inner nullifier (non-siloed) given its unique note hash, preimage and extra data. /// - /// The signature of this function notably matches the `aztec::messages::discovery::ComputeNoteNullifier` type, and so it can be used to call functions from that module such as `do_sync_state` and `attempt_note_discovery`. + /// The signature of this function notably matches the `aztec::messages::discovery::ComputeNoteNullifier` + /// type, and so it can be used to call functions from that module such as `do_sync_state` and + /// `process_private_note_msg`. /// /// This function is automatically injected by the `#[aztec]` macro. #[contract_library_method] diff --git a/noir-projects/noir-contracts/Nargo.toml b/noir-projects/noir-contracts/Nargo.toml index 2e44a5d00b70..18c7d9689d5e 100644 --- a/noir-projects/noir-contracts/Nargo.toml +++ b/noir-projects/noir-contracts/Nargo.toml @@ -80,6 +80,7 @@ members = [ "contracts/test/storage_proof_test_contract", "contracts/test/test_contract", "contracts/test/test_log_contract", + "contracts/test/tx_effect_oracle_test_contract", "contracts/test/unit_return_type_contract", "contracts/test/updatable_contract", "contracts/test/updated_contract" diff --git a/noir-projects/noir-contracts/contracts/app/token_blacklist_contract/src/main.nr b/noir-projects/noir-contracts/contracts/app/token_blacklist_contract/src/main.nr index 4cf688bdcd47..c50f84e73b0d 100644 --- a/noir-projects/noir-contracts/contracts/app/token_blacklist_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/app/token_blacklist_contract/src/main.nr @@ -21,10 +21,11 @@ pub contract TokenBlacklist { hash::compute_secret_hash, macros::{functions::{authorize_once, external, initializer, only_self, view}, storage::storage}, messages::{ - discovery::private_notes::attempt_note_discovery, message_delivery::MessageDelivery, + discovery::private_notes::process_private_note_msg, encoding::decode_message, + logs::note::encode_private_note_message, message_delivery::MessageDelivery, processing::validate_and_store_enqueued_notes_and_events, }, - note::{note_getter_options::NoteGetterOptions, note_interface::{NoteHash, NoteProperties, NoteType}}, + note::{note_getter_options::NoteGetterOptions, note_interface::{NoteHash, NoteProperties}}, protocol::traits::ToField, state_vars::{DelayedPublicMutable, Map, Owned, PrivateSet, PublicMutable, StateVariable}, utils::comparison::Comparator, @@ -33,7 +34,7 @@ pub contract TokenBlacklist { use std::ops::{Add, Sub}; use crate::types::{roles::UserFlags, transparent_note::TransparentNote}; - use aztec::protocol::{address::AztecAddress, constants::MAX_NOTE_HASHES_PER_TX, traits::Packable}; + use aztec::protocol::{address::AztecAddress, constants::MAX_NOTE_HASHES_PER_TX}; use balance_set::BalanceSet; // Changing an address' roles has a certain delay before it goes into effect. Here set to 1 day. @@ -257,9 +258,9 @@ pub contract TokenBlacklist { } // We cannot replace this function with the standard `process_message` function because the transparent note - // originates in public and hence we cannot emit it as an offchain message. We could construct the offchain message - // "manually" and then pass it to the `process_message` function, but this doesn't seem to be worth the effort - // given that the TransparentNote flow is deprecated and kept around only for testing purposes. + // originates in public and hence we cannot emit it as an offchain message. Instead we construct the private note + // message "manually" and feed it into the standard `process_private_note_msg` pipeline. The TransparentNote flow + // is deprecated and kept around only for testing purposes. #[external("utility")] unconstrained fn process_transparent_note( contract_address: AztecAddress, @@ -272,23 +273,26 @@ pub contract TokenBlacklist { ) { let note = TransparentNote { amount, secret_hash }; let storage_slot = TokenBlacklist::storage_layout().pending_shields.slot; - let note_type_id = TransparentNote::get_id(); - let packed_note = BoundedVec::from_array(note.pack()); // We use AztecAddress::zero() as the owner since TransparentNote is not owned by a specific address but instead // by anyone who has the secret (preimage of the secret_hash stored in the note). - attempt_note_discovery( + let encoded_msg = encode_private_note_message( + note, + AztecAddress::zero(), + storage_slot, + TRANSPARENT_NOTE_RANDOMNESS, + ); + let (_, msg_metadata, msg_content) = decode_message(BoundedVec::from_array(encoded_msg)).unwrap(); + + process_private_note_msg( contract_address, tx_hash, unique_note_hashes_in_tx, first_nullifier_in_tx, _compute_note_hash, _compute_note_nullifier, - AztecAddress::zero(), - storage_slot, - TRANSPARENT_NOTE_RANDOMNESS, - note_type_id, - packed_note, + msg_metadata, + msg_content, ); // At this point, the note is pending validation and storage in the database. We must call diff --git a/noir-projects/noir-contracts/contracts/test/tx_effect_oracle_test_contract/Nargo.toml b/noir-projects/noir-contracts/contracts/test/tx_effect_oracle_test_contract/Nargo.toml new file mode 100644 index 000000000000..d23f3ca6768d --- /dev/null +++ b/noir-projects/noir-contracts/contracts/test/tx_effect_oracle_test_contract/Nargo.toml @@ -0,0 +1,8 @@ +[package] +name = "tx_effect_oracle_test_contract" +authors = [""] +compiler_version = ">=0.25.0" +type = "contract" + +[dependencies] +aztec = { path = "../../../../aztec-nr/aztec" } diff --git a/noir-projects/noir-contracts/contracts/test/tx_effect_oracle_test_contract/src/main.nr b/noir-projects/noir-contracts/contracts/test/tx_effect_oracle_test_contract/src/main.nr new file mode 100644 index 000000000000..131eea1cdffc --- /dev/null +++ b/noir-projects/noir-contracts/contracts/test/tx_effect_oracle_test_contract/src/main.nr @@ -0,0 +1,22 @@ +use aztec::macros::aztec; + +/// A contract used to exercise the `aztec_utl_getTxEffect` oracle end-to-end. +#[aztec] +pub contract TxEffectOracleTest { + use aztec::{ + macros::functions::external, + oracle::message_processing::get_tx_effect, + protocol::{hash::poseidon2_hash, traits::Serialize}, + }; + + #[external("utility")] + unconstrained fn get_tx_effect_hash(tx_hash: Field) -> Field { + let e = get_tx_effect(tx_hash).unwrap(); + poseidon2_hash(e.serialize()) + } + + #[external("utility")] + unconstrained fn assert_is_none(tx_hash: Field) { + assert(get_tx_effect(tx_hash).is_none(), "expected get_tx_effect to return None"); + } +} diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/contract_class_log.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/contract_class_log.nr index 0c418767ad39..bbf8879a5560 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/contract_class_log.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/contract_class_log.nr @@ -1,9 +1,11 @@ use crate::{ - abis::log::Log, address::AztecAddress, constants::CONTRACT_CLASS_LOG_SIZE_IN_FIELDS, - traits::Empty, + abis::log::Log, + address::AztecAddress, + constants::CONTRACT_CLASS_LOG_SIZE_IN_FIELDS, + traits::{Deserialize, Empty, Serialize}, }; -#[derive(Eq)] +#[derive(Eq, Serialize, Deserialize)] pub struct ContractClassLog { pub log: Log, pub contract_address: AztecAddress, diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/blob_data/tx_effect.nr b/noir-projects/noir-protocol-circuits/crates/types/src/blob_data/tx_effect.nr index 9aaf908c74f1..6e23ae61da97 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/blob_data/tx_effect.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/blob_data/tx_effect.nr @@ -8,13 +8,13 @@ use crate::{ MAX_NULLIFIERS_PER_TX, MAX_PRIVATE_LOGS_PER_TX, MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, }, - traits::Empty, + traits::{Empty, Serialize}, }; -#[derive(Eq)] +#[derive(Eq, Serialize)] pub struct TxEffect { - pub tx_hash: Field, pub revert_code: u8, + pub tx_hash: Field, pub transaction_fee: Field, pub note_hashes: [Field; MAX_NOTE_HASHES_PER_TX], pub nullifiers: [Field; MAX_NULLIFIERS_PER_TX], @@ -28,8 +28,8 @@ pub struct TxEffect { impl Empty for TxEffect { fn empty() -> Self { TxEffect { - tx_hash: 0, revert_code: 0, + tx_hash: 0, transaction_fee: 0, note_hashes: [0; MAX_NOTE_HASHES_PER_TX], nullifiers: [0; MAX_NULLIFIERS_PER_TX], diff --git a/playground/vite.config.ts b/playground/vite.config.ts index a465bb66a3e2..e97e4e7832c1 100644 --- a/playground/vite.config.ts +++ b/playground/vite.config.ts @@ -136,9 +136,10 @@ export default defineConfig(({ mode }) => { // Bump log: // - AD: bumped from 1600 => 1680 as we now have a 20kb msgpack lib in bb.js and other logic got us 50kb higher, adding some wiggle room. // - MW: bumped from 1700 => 1750 after adding the noble curves pkg to foundation required for blob batching calculations. + // - JB: bumped from 1750 => 1800 after adding the `aztec_utl_getTxEffect` oracle handler, which pulls TxEffect / FlatPublicLogs / PrivateLog / PublicDataWrite into the eager PXE import path (#22979). { pattern: /assets\/index-.*\.js$/, - maxSizeKB: 1750, + maxSizeKB: 1800, description: 'Main entrypoint, hard limit', }, // Bump log: diff --git a/yarn-project/end-to-end/package.json b/yarn-project/end-to-end/package.json index db7eb8e5a22c..ea9cbf14a40a 100644 --- a/yarn-project/end-to-end/package.json +++ b/yarn-project/end-to-end/package.json @@ -2,7 +2,10 @@ "name": "@aztec/end-to-end", "version": "0.0.0", "type": "module", - "exports": "./dest/index.js", + "exports": { + ".": "./dest/index.js", + "./*": "./dest/*" + }, "inherits": [ "../package.common.json", "./package.local.json" diff --git a/yarn-project/end-to-end/src/e2e_tx_effect_oracle.test.ts b/yarn-project/end-to-end/src/e2e_tx_effect_oracle.test.ts new file mode 100644 index 000000000000..3755a88e45e3 --- /dev/null +++ b/yarn-project/end-to-end/src/e2e_tx_effect_oracle.test.ts @@ -0,0 +1,105 @@ +import type { AztecAddress } from '@aztec/aztec.js/addresses'; +import { Fr } from '@aztec/aztec.js/fields'; +import type { AztecNode } from '@aztec/aztec.js/node'; +import type { Wallet } from '@aztec/aztec.js/wallet'; +import { + MAX_CONTRACT_CLASS_LOGS_PER_TX, + MAX_L2_TO_L1_MSGS_PER_TX, + MAX_NOTE_HASHES_PER_TX, + MAX_NULLIFIERS_PER_TX, + MAX_PRIVATE_LOGS_PER_TX, + MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, +} from '@aztec/constants'; +import { padArrayEnd } from '@aztec/foundation/collection'; +import { poseidon2Hash } from '@aztec/foundation/crypto/poseidon'; +import { TxEffectOracleTestContract } from '@aztec/noir-test-contracts.js/TxEffectOracleTest'; +import { PublicDataWrite } from '@aztec/stdlib/avm'; +import { ContractClassLog, FlatPublicLogs, PrivateLog } from '@aztec/stdlib/logs'; +import type { TxEffect, TxHash } from '@aztec/stdlib/tx'; + +import { jest } from '@jest/globals'; + +import { setup } from './fixtures/utils.js'; + +const TIMEOUT = 120_000; + +// Exercises the `aztec_utl_getTxEffect` utility oracle end-to-end. The contract hashes the deserialized `TxEffect` +// field-by-field with poseidon2 and asserts the result matches a hash computed in TS over the same fixed-size flat +// field layout used by Noir's auto-derived `Serialize`. Any layout drift between Noir and TS produces a +// mismatch. +// +// Note: This would ideally be an integration test as it only tests an oracle implementation but we currently don't +// have the infrastructure for that. Testing it with TXE is also infeasible as there we would not be able to check the +// obtained tx effect from oracle with the one obtained directly in TS. + +describe('e2e tx effect oracle', () => { + let contract: TxEffectOracleTestContract; + let deployTxHash: TxHash; + jest.setTimeout(TIMEOUT); + + let wallet: Wallet; + let aztecNode: AztecNode; + let defaultAccountAddress: AztecAddress; + let teardown: () => Promise; + + beforeAll(async () => { + ({ + teardown, + wallet, + aztecNode, + accounts: [defaultAccountAddress], + } = await setup(1)); + const { contract: deployed, receipt } = await TxEffectOracleTestContract.deploy(wallet).send({ + from: defaultAccountAddress, + }); + contract = deployed; + deployTxHash = receipt.txHash; + }); + + afterAll(() => teardown()); + + it('tx effect in Noir exactly matches tx effect in TS', async () => { + const nodeEffect = await aztecNode.getTxEffect(deployTxHash); + expect(nodeEffect).toBeDefined(); + + // We compare hashes instead of comparing the tx effects directly as returning the full tx effect from the function + // would be tricky. + const expectedHash = await hashTxEffect(nodeEffect!.data); + const { result: actualHash } = await contract.methods + .get_tx_effect_hash(deployTxHash.hash) + .simulate({ from: defaultAccountAddress }); + + expect(actualHash).toEqual(expectedHash.toBigInt()); + }); + + it('aztec_utl_getTxEffect oracle returns None for a random tx hash', async () => { + await contract.methods.assert_is_none(Fr.random()).simulate({ from: defaultAccountAddress }); + }); +}); + +// Mirrors Noir's auto-derived `Serialize` layout: variable-length collections are padded to their `MAX_*` +// constant. Note that contract class log fields are emitted in Noir struct order (`log.fields`, `log.length`, +// `contract_address`), which differs from `ContractClassLog.toFields()`. +function hashTxEffect(effect: TxEffect): Promise { + const fields = [ + effect.revertCode.toField(), + effect.txHash.hash, + effect.transactionFee, + ...padArrayEnd(effect.noteHashes, Fr.ZERO, MAX_NOTE_HASHES_PER_TX), + ...padArrayEnd(effect.nullifiers, Fr.ZERO, MAX_NULLIFIERS_PER_TX), + ...padArrayEnd(effect.l2ToL1Msgs, Fr.ZERO, MAX_L2_TO_L1_MSGS_PER_TX), + ...padArrayEnd( + effect.publicDataWrites, + PublicDataWrite.empty(), + MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, + ).flatMap(w => w.toFields()), + ...padArrayEnd(effect.privateLogs, PrivateLog.empty(), MAX_PRIVATE_LOGS_PER_TX).flatMap(l => l.toFields()), + ...FlatPublicLogs.fromLogs(effect.publicLogs).toFields(), + ...padArrayEnd(effect.contractClassLogs, ContractClassLog.empty(), MAX_CONTRACT_CLASS_LOGS_PER_TX).flatMap(l => [ + ...l.fields.toFields(), + new Fr(l.emittedLength), + l.contractAddress.toField(), + ]), + ]; + return poseidon2Hash(fields); +} diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/interfaces.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/interfaces.ts index 524741739c16..7c4f1397a65b 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/interfaces.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/interfaces.ts @@ -12,7 +12,7 @@ import type { PublicKeys } from '@aztec/stdlib/keys'; import type { ContractClassLog, Tag } from '@aztec/stdlib/logs'; import type { Note, NoteStatus } from '@aztec/stdlib/note'; import { type NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees'; -import type { BlockHeader } from '@aztec/stdlib/tx'; +import type { BlockHeader, TxEffect, TxHash } from '@aztec/stdlib/tx'; import type { UtilityContext } from '../noir-structs/utility_context.js'; import type { MessageLoadOracleInputs } from './message_load_oracle_inputs.js'; @@ -144,6 +144,7 @@ export interface IUtilityExecutionOracle { ): Promise; getLogsByTagV2(requestArrayBaseSlot: Fr): Promise; getMessageContextsByTxHashV2(requestArrayBaseSlot: Fr): Promise; + getTxEffect(txHash: TxHash): Promise; getMessageContextsByTxHash( contractAddress: AztecAddress, messageContextRequestsArrayBaseSlot: Fr, diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle.ts index 64bb76053692..f325f1f49f1a 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/oracle.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/oracle.ts @@ -1,6 +1,17 @@ +import { + ARCHIVE_HEIGHT, + MAX_CONTRACT_CLASS_LOGS_PER_TX, + MAX_L2_TO_L1_MSGS_PER_TX, + MAX_NOTE_HASHES_PER_TX, + MAX_NULLIFIERS_PER_TX, + MAX_PRIVATE_LOGS_PER_TX, + MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, +} from '@aztec/constants'; import { BlockNumber } from '@aztec/foundation/branded-types'; +import { padArrayEnd } from '@aztec/foundation/collection'; import { Fr } from '@aztec/foundation/curves/bn254'; import { Point } from '@aztec/foundation/curves/grumpkin'; +import { MembershipWitness } from '@aztec/foundation/trees'; import { type ACIRCallback, type ACVMField, @@ -11,9 +22,11 @@ import { toACVMField, } from '@aztec/simulator/client'; import { FunctionSelector, NoteSelector } from '@aztec/stdlib/abi'; +import { PublicDataWrite } from '@aztec/stdlib/avm'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; import { BlockHash } from '@aztec/stdlib/block'; -import { ContractClassLog, ContractClassLogFields } from '@aztec/stdlib/logs'; +import { ContractClassLog, ContractClassLogFields, FlatPublicLogs, PrivateLog } from '@aztec/stdlib/logs'; +import { TxEffect, TxHash } from '@aztec/stdlib/tx'; import { ORACLE_VERSION_MAJOR, ORACLE_VERSION_MINOR } from '../../oracle_version.js'; import type { IMiscOracle, IPrivateExecutionOracle, IUtilityExecutionOracle } from './interfaces.js'; @@ -237,6 +250,7 @@ export class Oracle { return witness.toNoirRepresentation(); } + // TODO(https://linear.app/aztec-labs/issue/F-651): drop this // eslint-disable-next-line camelcase async aztec_utl_getBlockHashMembershipWitness( [anchorBlockHash]: ACVMField[], @@ -254,6 +268,20 @@ export class Oracle { return witness.toNoirRepresentation(); } + // TODO(https://linear.app/aztec-labs/issue/F-651): rename to aztec_utl_getBlockHashMembershipWitness + // eslint-disable-next-line camelcase + async aztec_utl_getBlockHashMembershipWitnessV2( + [anchorBlockHash]: ACVMField[], + [blockHash]: ACVMField[], + ): Promise<(ACVMField | ACVMField[])[]> { + const parsedAnchorBlockHash = BlockHash.fromString(anchorBlockHash); + const parsedBlockHash = BlockHash.fromString(blockHash); + + const witness = await this.handlerAsUtility().getBlockHashMembershipWitness(parsedAnchorBlockHash, parsedBlockHash); + const effective = witness ?? MembershipWitness.empty(ARCHIVE_HEIGHT); + return [toACVMField(witness !== undefined ? 1 : 0), ...effective.toNoirRepresentation()]; + } + // eslint-disable-next-line camelcase async aztec_utl_getNullifierMembershipWitness( [blockHash]: ACVMField[], @@ -684,6 +712,36 @@ export class Oracle { return [toACVMField(responseSlot)]; } + // eslint-disable-next-line camelcase + async aztec_utl_getTxEffect([txHash]: ACVMField[]): Promise<(ACVMField | ACVMField[])[]> { + const txEffect = await this.handlerAsUtility().getTxEffect(TxHash.fromField(Fr.fromString(txHash))); + // The Noir oracle returns `Option`. ACVM expands this into 12 destination slots: the Option discriminant + // followed by the 11 leaf slots of `TxEffect` (each scalar is one slot, each top-level array is one slot, and + // the nested `PublicLogs { length, payload }` splits into two slots). + const effect = txEffect ?? TxEffect.empty(); + const flatPublicLogs = FlatPublicLogs.fromLogs(effect.publicLogs); + return [ + toACVMField(txEffect === null ? 0 : 1), + toACVMField(effect.revertCode.toField()), + toACVMField(effect.txHash.hash), + toACVMField(effect.transactionFee), + padArrayEnd(effect.noteHashes, Fr.ZERO, MAX_NOTE_HASHES_PER_TX).map(toACVMField), + padArrayEnd(effect.nullifiers, Fr.ZERO, MAX_NULLIFIERS_PER_TX).map(toACVMField), + padArrayEnd(effect.l2ToL1Msgs, Fr.ZERO, MAX_L2_TO_L1_MSGS_PER_TX).map(toACVMField), + padArrayEnd(effect.publicDataWrites, PublicDataWrite.empty(), MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX) + .flatMap(w => w.toFields()) + .map(toACVMField), + padArrayEnd(effect.privateLogs, PrivateLog.empty(), MAX_PRIVATE_LOGS_PER_TX) + .flatMap(l => l.toFields()) + .map(toACVMField), + toACVMField(flatPublicLogs.length), + flatPublicLogs.payload.map(toACVMField), + padArrayEnd(effect.contractClassLogs, ContractClassLog.empty(), MAX_CONTRACT_CLASS_LOGS_PER_TX) + .flatMap(l => [...l.fields.toFields(), new Fr(l.emittedLength), l.contractAddress.toField()]) + .map(toACVMField), + ]; + } + // eslint-disable-next-line camelcase aztec_utl_setCapsule( [contractAddress]: ACVMField[], diff --git a/yarn-project/pxe/src/contract_function_simulator/oracle/utility_execution_oracle.ts b/yarn-project/pxe/src/contract_function_simulator/oracle/utility_execution_oracle.ts index 88eeb1fe8996..114ae2e6e7ef 100644 --- a/yarn-project/pxe/src/contract_function_simulator/oracle/utility_execution_oracle.ts +++ b/yarn-project/pxe/src/contract_function_simulator/oracle/utility_execution_oracle.ts @@ -29,7 +29,7 @@ import { MessageContext, deriveAppSiloedSharedSecret } from '@aztec/stdlib/logs' import { getNonNullifiedL1ToL2MessageWitness } from '@aztec/stdlib/messaging'; import type { NoteStatus } from '@aztec/stdlib/note'; import { MerkleTreeId, type NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees'; -import type { BlockHeader, Capsule, IndexedTxEffect, OffchainEffect, TxHash } from '@aztec/stdlib/tx'; +import type { BlockHeader, Capsule, IndexedTxEffect, OffchainEffect, TxEffect, TxHash } from '@aztec/stdlib/tx'; import { createContractLogger, logContractMessage, stripAztecnrLogPrefix } from '../../contract_logging.js'; import type { ContractSyncService } from '../../contract_sync/contract_sync_service.js'; @@ -790,6 +790,23 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra return this.ephemeralArrayService.newArray(maybeMessageContexts.map(MessageContext.toSerializedOption)); } + /** + * Fetches the effects of a transaction by its hash. Returns null if the tx is not found or is beyond the anchor + * block. + */ + public async getTxEffect(txHash: TxHash): Promise { + if (txHash.hash.isZero()) { + throw new Error('Invalid tx hash passed into aztec_utl_getTxEffect oracle handler'); + } + + const txEffect = await this.aztecNode.getTxEffect(txHash); + if (!txEffect || txEffect.l2BlockNumber > this.anchorBlockHeader.getBlockNumber()) { + return null; + } + + return txEffect.data; + } + public setCapsule(contractAddress: AztecAddress, slot: Fr, capsule: Fr[], scope: AztecAddress): void { if (!contractAddress.equals(this.contractAddress)) { // TODO(#10727): instead of this check that this.contractAddress is allowed to access the external DB diff --git a/yarn-project/pxe/src/oracle_version.ts b/yarn-project/pxe/src/oracle_version.ts index 58e6fed79bdc..226a84631cba 100644 --- a/yarn-project/pxe/src/oracle_version.ts +++ b/yarn-project/pxe/src/oracle_version.ts @@ -11,7 +11,7 @@ /// 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. export const ORACLE_VERSION_MAJOR = 22; -export const ORACLE_VERSION_MINOR = 2; +export const ORACLE_VERSION_MINOR = 3; /// This hash is computed from the Oracle interface and is used to detect when that interface changes. When it does, /// you need to either: @@ -19,4 +19,4 @@ export const ORACLE_VERSION_MINOR = 2; /// - increment only `ORACLE_VERSION_MINOR` if the change is additive (a new oracle was added). /// /// These constants must be kept in sync between this file and `noir-projects/aztec-nr/aztec/src/oracle/version.nr`. -export const ORACLE_INTERFACE_HASH = '193fe3f9fee6a84d26803e636c9746dd805a4f389d44a0618de75c2c5eb4912e'; +export const ORACLE_INTERFACE_HASH = '0649632102f27d1411749e46733b96103eaac948914d328afc4b90d3bf6e5af5'; diff --git a/yarn-project/txe/src/rpc_translator.ts b/yarn-project/txe/src/rpc_translator.ts index 581f1cd5ffa7..fbe68b1de04c 100644 --- a/yarn-project/txe/src/rpc_translator.ts +++ b/yarn-project/txe/src/rpc_translator.ts @@ -1,6 +1,7 @@ import type { ContractInstanceWithAddress } from '@aztec/aztec.js/contracts'; import { Fr, Point } from '@aztec/aztec.js/fields'; import { + ARCHIVE_HEIGHT, MAX_NOTE_HASHES_PER_TX, MAX_NULLIFIERS_PER_TX, MAX_PRIVATE_LOGS_PER_TX, @@ -8,6 +9,7 @@ import { PRIVATE_LOG_SIZE_IN_FIELDS, } from '@aztec/constants'; import { BlockNumber } from '@aztec/foundation/branded-types'; +import { MembershipWitness } from '@aztec/foundation/trees'; import { type IMiscOracle, type IPrivateExecutionOracle, @@ -776,6 +778,7 @@ export class RPCTranslator { return toForeignCallResult(witness.toNoirRepresentation()); } + // TODO(https://linear.app/aztec-labs/issue/F-651): drop this // eslint-disable-next-line camelcase async aztec_utl_getBlockHashMembershipWitness( foreignAnchorBlockHash: ForeignCallSingle, @@ -794,6 +797,20 @@ export class RPCTranslator { return toForeignCallResult(witness.toNoirRepresentation()); } + // TODO(https://linear.app/aztec-labs/issue/F-651): rename to aztec_utl_getBlockHashMembershipWitness + // eslint-disable-next-line camelcase + async aztec_utl_getBlockHashMembershipWitnessV2( + foreignAnchorBlockHash: ForeignCallSingle, + foreignBlockHash: ForeignCallSingle, + ) { + const anchorBlockHash = new BlockHash(fromSingle(foreignAnchorBlockHash)); + const blockHash = new BlockHash(fromSingle(foreignBlockHash)); + + const witness = await this.handlerAsUtility().getBlockHashMembershipWitness(anchorBlockHash, blockHash); + const effective = witness ?? MembershipWitness.empty(ARCHIVE_HEIGHT); + return toForeignCallResult([toSingle(new Fr(witness !== undefined ? 1 : 0)), ...effective.toNoirRepresentation()]); + } + // eslint-disable-next-line camelcase async aztec_utl_getLowNullifierMembershipWitness( foreignBlockHash: ForeignCallSingle,