diff --git a/noir-projects/aztec-nr/aztec/src/discovery/mod.nr b/noir-projects/aztec-nr/aztec/src/discovery/mod.nr index c53dc8914471..ee3db0fdb402 100644 --- a/noir-projects/aztec-nr/aztec/src/discovery/mod.nr +++ b/noir-projects/aztec-nr/aztec/src/discovery/mod.nr @@ -3,6 +3,7 @@ use protocol_types::{address::AztecAddress, debug_log::debug_log}; pub mod nonce_discovery; pub mod partial_notes; +pub mod pending_tagged_log; pub mod private_logs; pub mod private_notes; diff --git a/noir-projects/aztec-nr/aztec/src/discovery/pending_tagged_log.nr b/noir-projects/aztec-nr/aztec/src/discovery/pending_tagged_log.nr new file mode 100644 index 000000000000..93d6be234c29 --- /dev/null +++ b/noir-projects/aztec-nr/aztec/src/discovery/pending_tagged_log.nr @@ -0,0 +1,142 @@ +use protocol_types::{ + address::AztecAddress, + constants::{MAX_NOTE_HASHES_PER_TX, PRIVATE_LOG_SIZE_IN_FIELDS}, + hash::sha256_to_field, + traits::{Deserialize, Serialize}, +}; + +// Base slot for the pending tagged log array to which the syncNotes oracle inserts found private logs. +pub(crate) global PENDING_TAGGED_LOG_ARRAY_BASE_SLOT: Field = sha256_to_field( + "AZTEC_NR::PENDING_TAGGED_PENDING_TAGGED_LOG_ARRAY_BASE_SLOT".as_bytes(), +); + +/// Represents a log as it is stored in the pending tagged log array to which the syncNotes oracle inserts found private log. +#[derive(Deserialize, Serialize)] +pub(crate) struct PendingTaggedLog { + pub log: BoundedVec, + pub tx_hash: Field, + pub unique_note_hashes_in_tx: BoundedVec, + pub first_nullifier_in_tx: Field, + pub recipient: AztecAddress, + pub log_index_in_tx: Field, +} + +mod test { + use crate::discovery::pending_tagged_log::PendingTaggedLog; + use protocol_types::{address::AztecAddress, traits::{FromField, Serialize}}; + + #[test] + unconstrained fn pending_tagged_log_serialization_matches_typescript() { + let log = BoundedVec::from_array([1, 2, 3]); + let tx_hash = 123; + let unique_note_hashes = BoundedVec::from_array([4, 5]); + let first_nullifier = 6; + let recipient = AztecAddress::from_field(789); + let log_index_in_tx = 10; + let pending_log = PendingTaggedLog { + log, + tx_hash, + unique_note_hashes_in_tx: unique_note_hashes, + first_nullifier_in_tx: first_nullifier, + recipient, + log_index_in_tx, + }; + + let serialized = pending_log.serialize(); + + // The following value was generated by `pending_tagged_log.test.ts` + // --> Run the test with AZTEC_GENERATE_TEST_DATA=1 flag to update test data. + let serialized_pending_tagged_log_from_typescript = [ + 0x0000000000000000000000000000000000000000000000000000000000000001, + 0x0000000000000000000000000000000000000000000000000000000000000002, + 0x0000000000000000000000000000000000000000000000000000000000000003, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000003, + 0x000000000000000000000000000000000000000000000000000000000000007b, + 0x0000000000000000000000000000000000000000000000000000000000000004, + 0x0000000000000000000000000000000000000000000000000000000000000005, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000000, + 0x0000000000000000000000000000000000000000000000000000000000000002, + 0x0000000000000000000000000000000000000000000000000000000000000006, + 0x0000000000000000000000000000000000000000000000000000000000000315, + 0x000000000000000000000000000000000000000000000000000000000000000a, + ]; + + assert_eq(serialized, serialized_pending_tagged_log_from_typescript); + } +} diff --git a/noir-projects/aztec-nr/aztec/src/discovery/private_logs.nr b/noir-projects/aztec-nr/aztec/src/discovery/private_logs.nr index 6b6cf1399623..0e52b9ed4442 100644 --- a/noir-projects/aztec-nr/aztec/src/discovery/private_logs.nr +++ b/noir-projects/aztec-nr/aztec/src/discovery/private_logs.nr @@ -1,7 +1,11 @@ use crate::{ + capsules::CapsuleArray, discovery::{ - ComputeNoteHashAndNullifier, MAX_LOG_CONTENT_LEN, - partial_notes::process_partial_note_private_log, PRIVATE_LOG_EXPANDED_METADATA_LEN, + ComputeNoteHashAndNullifier, + MAX_LOG_CONTENT_LEN, + partial_notes::process_partial_note_private_log, + pending_tagged_log::{PENDING_TAGGED_LOG_ARRAY_BASE_SLOT, PendingTaggedLog}, + PRIVATE_LOG_EXPANDED_METADATA_LEN, private_notes::process_private_note_log, }, encrypted_logs::{ @@ -19,7 +23,6 @@ use crate::{ use protocol_types::{ abis::event_selector::EventSelector, address::AztecAddress, - constants::{MAX_NOTE_HASHES_PER_TX, PRIVATE_LOG_SIZE_IN_FIELDS}, debug_log::{debug_log, debug_log_format}, traits::FromField, }; @@ -31,14 +34,31 @@ use crate::encrypted_logs::log_encryption::PRIVATE_LOG_PLAINTEXT_SIZE_IN_FIELDS; /// are stored in the PXE capsules so that `fetch_and_process_public_partial_note_completion_logs` can later search for /// public logs that will complete them. pub unconstrained fn fetch_and_process_private_tagged_logs( - _contract_address: AztecAddress, - _compute_note_hash_and_nullifier: ComputeNoteHashAndNullifier, + contract_address: AztecAddress, + compute_note_hash_and_nullifier: ComputeNoteHashAndNullifier, ) { - // We will eventually fetch tagged logs, decrypt and process them here, but for now we simply call the `syncNotes` - // oracle. This has PXE perform tag synchronization, log download, decryption, and finally calls to the the - // `process_log` contract function with the decrypted payload, which will in turn call `do_process_log` with a - // decrypted log, letting us continue the work outside of PXE. - sync_notes(); + // We will eventually perform log discovery via tagging here, but for now we simply call the `syncNotes` oracle. + // This makes PXE synchronize tags, download logs and store the pending tagged logs in capsule array which are then + // retrieved and processed here. + sync_notes(PENDING_TAGGED_LOG_ARRAY_BASE_SLOT); + + // Get logs from capsules + let logs = + CapsuleArray::::at(contract_address, PENDING_TAGGED_LOG_ARRAY_BASE_SLOT); + let len = logs.len(); + + // We iterate over the logs in reverse order to avoid shifting elements. + let mut i = len; + while i > 0 { + i -= 1; + + // Get and process each log + let log = logs.get(i); + process_log(contract_address, compute_note_hash_and_nullifier, log); + + // Remove the log from the capsule array + logs.remove(i); + } } /// Processes a log's ciphertext by decrypting it and then searching the plaintext for private notes or partial notes. @@ -50,21 +70,20 @@ pub unconstrained fn fetch_and_process_private_tagged_logs( /// /// Partial notes result in a pending partial note entry being stored in a PXE capsule, which will later be retrieved to /// search for the note's completion public log. -pub unconstrained fn do_process_log( +unconstrained fn process_log( contract_address: AztecAddress, - log: BoundedVec, - tx_hash: Field, - unique_note_hashes_in_tx: BoundedVec, - first_nullifier_in_tx: Field, - log_index_in_tx: Field, - recipient: AztecAddress, compute_note_hash_and_nullifier: ComputeNoteHashAndNullifier, + pending_tagged_log: PendingTaggedLog, ) { - debug_log_format("Processing log with tag {0}", [log.get(0)]); + debug_log_format( + "Processing log with tag {0}", + [pending_tagged_log.log.get(0)], + ); + + // The tag is ignored for now. + let ciphertext = array::subbvec(pending_tagged_log.log, 1); - // The first field of the log is the tag, which we don't need right now, and the rest is all ciphertext. - let ciphertext = array::subbvec(log, 1); - let log_plaintext = AES128::decrypt_log(ciphertext, recipient); + let log_plaintext = AES128::decrypt_log(ciphertext, pending_tagged_log.recipient); // The first thing to do after decrypting the log is to determine what type of private log we're processing. We // have 3 log types: private note logs, partial note logs and event logs. @@ -76,10 +95,10 @@ pub unconstrained fn do_process_log( process_private_note_log( contract_address, - tx_hash, - unique_note_hashes_in_tx, - first_nullifier_in_tx, - recipient, + pending_tagged_log.tx_hash, + pending_tagged_log.unique_note_hashes_in_tx, + pending_tagged_log.first_nullifier_in_tx, + pending_tagged_log.recipient, compute_note_hash_and_nullifier, log_metadata, log_content, @@ -87,7 +106,12 @@ pub unconstrained fn do_process_log( } else if log_type_id == PARTIAL_NOTE_PRIVATE_LOG_TYPE_ID { debug_log("Processing partial note private log"); - process_partial_note_private_log(contract_address, recipient, log_metadata, log_content); + process_partial_note_private_log( + contract_address, + pending_tagged_log.recipient, + log_metadata, + log_content, + ); } else if log_type_id == PRIVATE_EVENT_LOG_TYPE_ID { debug_log("Processing private event log"); @@ -96,14 +120,13 @@ pub unconstrained fn do_process_log( store_private_event_log( contract_address, - recipient, + pending_tagged_log.recipient, event_selector, log_content, - tx_hash, - log_index_in_tx, + pending_tagged_log.tx_hash, + pending_tagged_log.log_index_in_tx, ); } else { - // TODO(#11569): handle events debug_log_format( "Unknown log type id {0} (probably belonging to an event log)", [log_type_id as Field], diff --git a/noir-projects/aztec-nr/aztec/src/macros/aztec.nr b/noir-projects/aztec-nr/aztec/src/macros/aztec.nr index 9824f990c2c4..66fbc8a19654 100644 --- a/noir-projects/aztec-nr/aztec/src/macros/aztec.nr +++ b/noir-projects/aztec-nr/aztec/src/macros/aztec.nr @@ -22,7 +22,6 @@ pub comptime fn aztec(m: Module) -> Quoted { let contract_library_method_compute_note_hash_and_nullifier = generate_contract_library_method_compute_note_hash_and_nullifier(); - let process_log = generate_process_log(); let note_exports = generate_note_exports(); let public_dispatch = generate_public_dispatch(m); let sync_notes = generate_sync_notes(); @@ -31,7 +30,6 @@ pub comptime fn aztec(m: Module) -> Quoted { $note_exports $interface $contract_library_method_compute_note_hash_and_nullifier - $process_log $public_dispatch $sync_notes } @@ -256,63 +254,6 @@ comptime fn generate_contract_library_method_compute_note_hash_and_nullifier() - } } -comptime fn generate_process_log() -> Quoted { - // This mandatory function processes a log emitted by the contract. This is currently used to process private logs - // and perform message discovery, resulting in new private notes, partial notes and events. - // The bulk of the work of this function is done by aztec::discovery::do_process_log, so all we need to do is call - // that function. - - // We'll produce the entire body of the function in one go and then insert it into the function. - let notes = NOTES.entries(); - - if notes.len() > 0 { - quote { - unconstrained fn process_log( - log_ciphertext: BoundedVec, - tx_hash: Field, - unique_note_hashes_in_tx: BoundedVec, - first_nullifier_in_tx: Field, - log_index_in_tx: Field, - recipient: aztec::protocol_types::address::AztecAddress, - ) { - // Because this unconstrained function is injected after the contract is processed by the macros, it'll - // not be modified by the macros that alter unconstrained functions. As such, we need to manually inject - // the unconstrained execution context since it will not be available otherwise. - let context = dep::aztec::context::unconstrained_context::UnconstrainedContext::new(); - - // TODO(#10727): allow other contracts to process logs and deliver notes - let contract_address = context.this_address(); - - aztec::discovery::private_logs::do_process_log( - contract_address, - log_ciphertext, - tx_hash, - unique_note_hashes_in_tx, - first_nullifier_in_tx, - log_index_in_tx, - recipient, - _compute_note_hash_and_nullifier, - ); - } - } - } else { - // Contracts with no notes still implement this function to avoid having special-casing, the implementation - // simply throws immediately. - quote { - unconstrained fn process_log( - _log_ciphertext: BoundedVec, - _tx_hash: Field, - _unique_note_hashes_in_tx: BoundedVec, - _first_nullifier_in_tx: Field, - _log_index_in_tx: Field, - _recipient: aztec::protocol_types::address::AztecAddress, - ) { - panic(f"This contract does not use private notes") - } - } - } -} - comptime fn generate_note_exports() -> Quoted { let notes = NOTES.values(); // Second value in each tuple is `note_packed_len` and that is ignored here because it's only used when diff --git a/noir-projects/aztec-nr/aztec/src/oracle/message_discovery.nr b/noir-projects/aztec-nr/aztec/src/oracle/message_discovery.nr index 9bb616a3d5f3..d2933ce7ff4f 100644 --- a/noir-projects/aztec-nr/aztec/src/oracle/message_discovery.nr +++ b/noir-projects/aztec-nr/aztec/src/oracle/message_discovery.nr @@ -4,14 +4,14 @@ use dep::protocol_types::{ constants::{MAX_NOTE_HASHES_PER_TX, PUBLIC_LOG_DATA_SIZE_IN_FIELDS}, }; -/// Finds new notes that may have been sent to all registered accounts in PXE in the current contract and makes them -/// available for later querying via the `get_notes` oracle. -pub unconstrained fn sync_notes() { - sync_notes_oracle(); +/// Finds new private logs that may have been sent to all registered accounts in PXE in the current contract and makes +/// them available for later processing in Noir by storing them in a capsule array. +pub unconstrained fn sync_notes(pending_tagged_log_array_base_slot: Field) { + sync_notes_oracle(pending_tagged_log_array_base_slot); } #[oracle(syncNotes)] -unconstrained fn sync_notes_oracle() {} +unconstrained fn sync_notes_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. diff --git a/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr b/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr index 51a79b38a212..b6a731b51197 100644 --- a/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr @@ -136,6 +136,9 @@ pub contract Counter { } // docs:end:txe_test_increment + // TODO(#13221): I had to comment out all the env.advance_block_by(1) and sync_notes() calls because that triggered + // an issue with how state is committed. The calls to advance_block_by(1) seems to have always been unnecessary + // in this test but anyway we should fix the issue and try to get the block advancing working here. #[test] unconstrained fn extended_incrementing_and_decrementing() { let initial_value = 5; @@ -155,7 +158,7 @@ pub contract Counter { ); // Checking that the note was discovered from private logs - env.advance_block_by(1); + // env.advance_block_by(1); sync_notes(); env.impersonate(contract_address); let counter_slot = Counter::storage_layout().counters.slot; @@ -178,7 +181,7 @@ pub contract Counter { assert(get_counter(owner) == 7); // Checking that the note was discovered from private logs - env.advance_block_by(1); + // env.advance_block_by(1); sync_notes(); let notes: BoundedVec = view_notes(owner_storage_slot, options); @@ -194,7 +197,7 @@ pub contract Counter { assert(notes.len() == 4); // Checking that the note was discovered from private logs - env.advance_block_by(1); + // env.advance_block_by(1); sync_notes(); let notes: BoundedVec = view_notes(owner_storage_slot, options); @@ -209,7 +212,7 @@ pub contract Counter { assert(notes.len() == 4); // Checking that the note was discovered from private logs - env.advance_block_by(1); + // env.advance_block_by(1); sync_notes(); let notes: BoundedVec = view_notes(owner_storage_slot, options); diff --git a/yarn-project/pxe/src/pxe_oracle_interface/pxe_oracle_interface.test.ts b/yarn-project/pxe/src/pxe_oracle_interface/pxe_oracle_interface.test.ts index cb7d69dce9a8..bb47487216c0 100644 --- a/yarn-project/pxe/src/pxe_oracle_interface/pxe_oracle_interface.test.ts +++ b/yarn-project/pxe/src/pxe_oracle_interface/pxe_oracle_interface.test.ts @@ -4,15 +4,12 @@ import { Fq, Fr } from '@aztec/foundation/fields'; import type { Tuple } from '@aztec/foundation/serialize'; import { KeyStore } from '@aztec/key-store'; import { openTmpStore } from '@aztec/kv-store/lmdb-v2'; -import { type AcirSimulator, type SimulationProvider, WASMSimulator } from '@aztec/simulator/client'; -import { type FunctionArtifact, FunctionSelector, FunctionType } from '@aztec/stdlib/abi'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; import { randomInBlock } from '@aztec/stdlib/block'; import { CompleteAddress } from '@aztec/stdlib/contract'; import type { AztecNode } from '@aztec/stdlib/interfaces/client'; import { computeAddress, computeAppTaggingSecret, deriveKeys } from '@aztec/stdlib/keys'; import { IndexedTaggingSecret, PrivateLog, PublicLog, TxScopedL2Log } from '@aztec/stdlib/logs'; -import { randomContractArtifact, randomContractInstanceWithAddress } from '@aztec/stdlib/testing'; import { BlockHeader, GlobalVariables, TxEffect, TxHash } from '@aztec/stdlib/tx'; import { jest } from '@jest/globals'; @@ -52,7 +49,6 @@ describe('PXEOracleInterface', () => { let taggingDataProvider: TaggingDataProvider; let capsuleDataProvider: CapsuleDataProvider; let keyStore: KeyStore; - let simulationProvider: SimulationProvider; let recipient: CompleteAddress; let contractAddress: AztecAddress; @@ -77,11 +73,9 @@ describe('PXEOracleInterface', () => { taggingDataProvider = new TaggingDataProvider(store); capsuleDataProvider = new CapsuleDataProvider(store); keyStore = new KeyStore(store); - simulationProvider = new WASMSimulator(); pxeOracleInterface = new PXEOracleInterface( aztecNode, keyStore, - simulationProvider, contractDataProvider, noteDataProvider, capsuleDataProvider, @@ -102,6 +96,7 @@ describe('PXEOracleInterface', () => { describe('sync tagged logs', () => { const NUM_SENDERS = 10; + let senders: { completeAddress: CompleteAddress; ivsk: Fq; secretKey: Fr }[]; async function generateMockLogs(tagIndex: number) { @@ -153,6 +148,9 @@ describe('PXEOracleInterface', () => { }); } + // Set to a random value in this test we don't care about Noir loading the logs from the capsule array. + const PENDING_TAGGED_LOG_ARRAY_BASE_SLOT = Fr.random(); + beforeEach(async () => { // Set up the address book senders = await timesParallel(NUM_SENDERS, async index => { @@ -166,18 +164,20 @@ describe('PXEOracleInterface', () => { await taggingDataProvider.addSenderAddress(sender.completeAddress.address); } aztecNode.getLogsByTags.mockReset(); + aztecNode.getTxEffect.mockResolvedValue(randomInBlock(await TxEffect.random())); }); it('should sync tagged logs', async () => { const tagIndex = 0; await generateMockLogs(tagIndex); - const syncedLogs = await pxeOracleInterface.syncTaggedLogs(contractAddress); - // We expect to have all logs intended for the recipient, one per sender + 1 with a duplicated tag for the first - // one + half of the logs for the second index - expect(syncedLogs.get(recipient.address.toString())).toHaveLength(NUM_SENDERS + 1 + NUM_SENDERS / 2); + await pxeOracleInterface.syncTaggedLogs(contractAddress, PENDING_TAGGED_LOG_ARRAY_BASE_SLOT); - // Recompute the secrets (as recipient) to ensure indexes are updated + // We expect to have all logs intended for the recipient synced (and hence stored in the capsule for later + // processing), one per sender + 1 with a duplicated tag for the first sender + half of the logs for the second + // index + await expectPendingTaggedLogArrayLengthToBe(contractAddress, NUM_SENDERS + 1 + NUM_SENDERS / 2); + // Recompute the secrets (as recipient) to ensure indexes are updated const ivsk = await keyStore.getMasterIncomingViewingSecretKey(recipient.address); const secrets = await Promise.all( senders.map(sender => @@ -267,9 +267,11 @@ describe('PXEOracleInterface', () => { it('should sync tagged logs with a sender index offset', async () => { const tagIndex = 5; await generateMockLogs(tagIndex); - const syncedLogs = await pxeOracleInterface.syncTaggedLogs(contractAddress); - // We expect to have all logs intended for the recipient, one per sender + 1 with a duplicated tag for the first one + half of the logs for the second index - expect(syncedLogs.get(recipient.address.toString())).toHaveLength(NUM_SENDERS + 1 + NUM_SENDERS / 2); + await pxeOracleInterface.syncTaggedLogs(contractAddress, PENDING_TAGGED_LOG_ARRAY_BASE_SLOT); + + // We expect to have all logs intended for the recipient, one per sender + 1 with a duplicated tag for the first + // one + half of the logs for the second index + await expectPendingTaggedLogArrayLengthToBe(contractAddress, NUM_SENDERS + 1 + NUM_SENDERS / 2); // Recompute the secrets (as recipient) to ensure indexes are updated const ivsk = await keyStore.getMasterIncomingViewingSecretKey(recipient.address); @@ -279,7 +281,7 @@ describe('PXEOracleInterface', () => { ), ); - // First sender should have 2 logs, but keep index 1 since they were built using the same tag + // First sender should have 2 logs, but keep index 6 since they were built using the same tag // Next 4 senders should also have index 6 = offset + 1 // Last 5 senders should have index 7 = offset + 2 const indexes = await taggingDataProvider.getTaggingSecretsIndexesAsRecipient(secrets, recipient.address); @@ -310,11 +312,11 @@ describe('PXEOracleInterface', () => { recipient.address, ); - const syncedLogs = await pxeOracleInterface.syncTaggedLogs(contractAddress); + await pxeOracleInterface.syncTaggedLogs(contractAddress, PENDING_TAGGED_LOG_ARRAY_BASE_SLOT); // Even if our index as recipient is higher than what the sender sent, we should be able to find the logs // since the window starts at Math.max(0, 2 - window_size) = 0 - expect(syncedLogs.get(recipient.address.toString())).toHaveLength(NUM_SENDERS + 1 + NUM_SENDERS / 2); + await expectPendingTaggedLogArrayLengthToBe(contractAddress, NUM_SENDERS + 1 + NUM_SENDERS / 2); // First sender should have 2 logs, but keep index 2 since they were built using the same tag // Next 4 senders should also have index 2 = tagIndex + 1 @@ -349,10 +351,11 @@ describe('PXEOracleInterface', () => { recipient.address, ); - const syncedLogs = await pxeOracleInterface.syncTaggedLogs(contractAddress); + await pxeOracleInterface.syncTaggedLogs(contractAddress, PENDING_TAGGED_LOG_ARRAY_BASE_SLOT); - // Only half of the logs should be synced since we start from index 1 = (11 - window_size), the other half should be skipped - expect(syncedLogs.get(recipient.address.toString())).toHaveLength(NUM_SENDERS / 2); + // Only half of the logs should be synced since we start from index 1 = (11 - window_size), the other half should + // be skipped + await expectPendingTaggedLogArrayLengthToBe(contractAddress, NUM_SENDERS / 2); // Indexes should remain where we set them (window_size + 1) const indexes = await taggingDataProvider.getTaggingSecretsIndexesAsRecipient(secrets, recipient.address); @@ -381,10 +384,11 @@ describe('PXEOracleInterface', () => { recipient.address, ); - let syncedLogs = await pxeOracleInterface.syncTaggedLogs(contractAddress); + await pxeOracleInterface.syncTaggedLogs(contractAddress, PENDING_TAGGED_LOG_ARRAY_BASE_SLOT); + + // No logs should be synced (and hence no capsules stored) since we start from index 2 = 12 - window_size + await expectPendingTaggedLogArrayLengthToBe(contractAddress, 0); - // No logs should be synced since we start from index 2 = 12 - window_size - expect(syncedLogs.get(recipient.address.toString())).toHaveLength(0); // Since no logs were synced, window edge hash not been pushed and for this reason we should have called // the node only once for the initial window expect(aztecNode.getLogsByTags.mock.calls.length).toBe(1); @@ -394,7 +398,7 @@ describe('PXEOracleInterface', () => { // Wipe the database await taggingDataProvider.resetNoteSyncData(); - syncedLogs = await pxeOracleInterface.syncTaggedLogs(contractAddress); + await pxeOracleInterface.syncTaggedLogs(contractAddress, PENDING_TAGGED_LOG_ARRAY_BASE_SLOT); // First sender should have 2 logs, but keep index 1 since they were built using the same tag // Next 4 senders should also have index 1 = offset + 1 @@ -416,10 +420,10 @@ describe('PXEOracleInterface', () => { const tagIndex = 0; await generateMockLogs(tagIndex); - const syncedLogs = await pxeOracleInterface.syncTaggedLogs(contractAddress); + await pxeOracleInterface.syncTaggedLogs(contractAddress, PENDING_TAGGED_LOG_ARRAY_BASE_SLOT); - // Only NUM_SENDERS + 1 logs should be synched, since the rest have blockNumber > MIN_BLOCK_NUMBER_OF_A_LOG - expect(syncedLogs.get(recipient.address.toString())).toHaveLength(NUM_SENDERS + 1); + // Only NUM_SENDERS + 1 logs should be synched, since the rest have blockNumber > 1 + await expectPendingTaggedLogArrayLengthToBe(contractAddress, NUM_SENDERS + 1); }); it('should not sync public tagged logs', async () => { @@ -438,77 +442,24 @@ describe('PXEOracleInterface', () => { aztecNode.getLogsByTags.mockImplementation(tags => { return Promise.resolve(tags.map(tag => logs[tag.toString()] ?? [])); }); - const syncedLogs = await pxeOracleInterface.syncTaggedLogs(contractAddress); + await pxeOracleInterface.syncTaggedLogs(contractAddress, PENDING_TAGGED_LOG_ARRAY_BASE_SLOT); // We expect the above log to be discarded, and so none to be synced - expect(syncedLogs.get(recipient.address.toString())).toHaveLength(0); - }); - }); - - describe('Process logs', () => { - let simulator: MockProxy; - let runUnconstrainedSpy: any; - - let processLogFuncArtifact: FunctionArtifact; - - beforeEach(async () => { - // Set up process_log function artifact --> it is never executed as simulator.runUnconstrained(...) is mocked - processLogFuncArtifact = { - name: 'process_log', - functionType: FunctionType.UNCONSTRAINED, - isInternal: false, - parameters: [], - returnTypes: [], - errorTypes: {}, - isInitializer: false, - isStatic: false, - bytecode: Buffer.alloc(0), - debugSymbols: '', - }; - - // Set up contract instance and artifact - const contractInstance = await randomContractInstanceWithAddress(); - const contractArtifact = randomContractArtifact(); - contractArtifact.functions = [processLogFuncArtifact]; - await contractDataProvider.addContractInstance(contractInstance); - await contractDataProvider.addContractArtifact(contractInstance.currentContractClassId, contractArtifact); - contractAddress = contractInstance.address; - - simulator = mock(); - simulator.runUnconstrained.mockImplementation(() => Promise.resolve({})); - - runUnconstrainedSpy = jest.spyOn(simulator, 'runUnconstrained'); + await expectPendingTaggedLogArrayLengthToBe(contractAddress, 0); }); - function mockTaggedLogs(numLogs: number) { - return Array(numLogs) - .fill(0) - .map(() => new TxScopedL2Log(TxHash.random(), 0, 0, 0, PrivateLog.random(Fr.random()))); - } - - it('should call processLog on multiple logs', async () => { - const numLogs = 3; - - const taggedLogs = mockTaggedLogs(numLogs); - - // Mock getTxEffect to return a TxEffect containing the private logs - aztecNode.getTxEffect.mockImplementation(async () => { - const txEffect = await TxEffect.random(); - txEffect.privateLogs = taggedLogs.map(log => log.log as PrivateLog); - return randomInBlock(txEffect); - }); - - await pxeOracleInterface.processTaggedLogs(contractAddress, taggedLogs, recipient.address, simulator); - - // We test that a call to `processLog` is made with the correct function artifact and contract address - expect(runUnconstrainedSpy).toHaveBeenCalledTimes(numLogs); - expect(runUnconstrainedSpy).toHaveBeenCalledWith( - expect.anything(), - contractAddress, - await FunctionSelector.fromNameAndParameters(processLogFuncArtifact.name, processLogFuncArtifact.parameters), - [], - ); - }, 30_000); + const expectPendingTaggedLogArrayLengthToBe = async (contractAddress: AztecAddress, expectedLength: number) => { + // Capsule array length is stored in the array base slot. + const capsule = await capsuleDataProvider.loadCapsule(contractAddress, PENDING_TAGGED_LOG_ARRAY_BASE_SLOT); + if (expectedLength === 0 && capsule === null) { + // If expected length is 0 we are fine with the capsule not existing since the array might not have been + // initialized yet. + return; + } + expect(capsule).toBeDefined(); + expect(capsule!.length).toBe(1); + expect(capsule![0].toNumber()).toBe(expectedLength); + }; }); const setSyncedBlockNumber = (blockNumber: number) => { diff --git a/yarn-project/pxe/src/pxe_oracle_interface/pxe_oracle_interface.ts b/yarn-project/pxe/src/pxe_oracle_interface/pxe_oracle_interface.ts index f626220c625d..865de15be689 100644 --- a/yarn-project/pxe/src/pxe_oracle_interface/pxe_oracle_interface.ts +++ b/yarn-project/pxe/src/pxe_oracle_interface/pxe_oracle_interface.ts @@ -1,22 +1,13 @@ -import { type L1_TO_L2_MSG_TREE_HEIGHT, MAX_NOTE_HASHES_PER_TX, PRIVATE_LOG_SIZE_IN_FIELDS } from '@aztec/constants'; +import type { L1_TO_L2_MSG_TREE_HEIGHT } from '@aztec/constants'; import { timesParallel } from '@aztec/foundation/collection'; import { Fr, Point } from '@aztec/foundation/fields'; import { createLogger } from '@aztec/foundation/log'; import type { KeyStore } from '@aztec/key-store'; -import { - AcirSimulator, - type ExecutionDataProvider, - MessageLoadOracleInputs, - type SimulationProvider, -} from '@aztec/simulator/client'; +import { type ExecutionDataProvider, MessageLoadOracleInputs } from '@aztec/simulator/client'; import { EventSelector, - type FunctionArtifact, type FunctionArtifactWithContractName, - FunctionCall, FunctionSelector, - FunctionType, - encodeArguments, getFunctionArtifact, } from '@aztec/stdlib/abi'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; @@ -26,7 +17,13 @@ import { computeUniqueNoteHash, siloNoteHash, siloNullifier } from '@aztec/stdli import type { AztecNode } from '@aztec/stdlib/interfaces/client'; import type { KeyValidationRequest } from '@aztec/stdlib/kernel'; import { computeAddressSecret, computeAppTaggingSecret } from '@aztec/stdlib/keys'; -import { IndexedTaggingSecret, LogWithTxData, TxScopedL2Log, deriveEcdhSharedSecret } from '@aztec/stdlib/logs'; +import { + IndexedTaggingSecret, + LogWithTxData, + PendingTaggedLog, + TxScopedL2Log, + deriveEcdhSharedSecret, +} from '@aztec/stdlib/logs'; import { getNonNullifiedL1ToL2MessageWitness } from '@aztec/stdlib/messaging'; import { Note, type NoteStatus } from '@aztec/stdlib/note'; import { MerkleTreeId, type NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees'; @@ -50,7 +47,6 @@ export class PXEOracleInterface implements ExecutionDataProvider { constructor( private aztecNode: AztecNode, private keyStore: KeyStore, - private simulationProvider: SimulationProvider, private contractDataProvider: ContractDataProvider, private noteDataProvider: NoteDataProvider, private capsuleDataProvider: CapsuleDataProvider, @@ -432,17 +428,19 @@ export class PXEOracleInterface implements ExecutionDataProvider { } /** - * Synchronizes the logs tagged with scoped addresses and all the senders in the address book. Returns the found logs - * and updates the indexes of the secrets used to tag them until there are no more logs to sync. - * @param contractAddress - The address of the contract that the logs are tagged for + * Synchronizes the logs tagged with scoped addresses and all the senders in the address book. Stores the found logs + * in CapsuleArray ready for a later retrieval in Aztec.nr. + * @param contractAddress - The address of the contract that the logs are tagged for. + * @param pendingTaggedLogArrayBaseSlot - The base slot of the pending tagged logs capsule array in which + * found logs will be stored. * @param scopes - The scoped addresses to sync logs for. If not provided, all accounts in the address book will be * synced. - * @returns A map of recipient addresses to a list of encrypted logs. */ public async syncTaggedLogs( contractAddress: AztecAddress, + pendingTaggedLogArrayBaseSlot: Fr, scopes?: AztecAddress[], - ): Promise> { + ) { this.log.verbose('Searching for tagged logs', { contract: contractAddress }); const maxBlockNumber = await this.syncDataProvider.getBlockNumber(); @@ -454,14 +452,8 @@ export class PXEOracleInterface implements ExecutionDataProvider { // length, since we don't really know the note they correspond to until we decrypt them. const recipients = scopes ? scopes : await this.keyStore.getAccounts(); - // A map of logs going from recipient address to logs. Note that the logs might have been processed before - // due to us having a sliding window that "looks back" for logs as well. (We look back as there is no guarantee - // that a logs will be received ordered by a given tag index and that the tags won't be reused). - const logsMap = new Map(); const contractName = await this.contractDataProvider.getDebugContractName(contractAddress); for (const recipient of recipients) { - const logsForRecipient: TxScopedL2Log[] = []; - // Get all the secrets for the recipient and sender pairs (#9365) const secrets = await this.#getIndexedTaggingSecretsForSenders(contractAddress, recipient); @@ -500,7 +492,8 @@ export class PXEOracleInterface implements ExecutionDataProvider { // Fetch the logs for the tags and iterate over them const logsByTags = await this.aztecNode.getLogsByTags(tagsForTheWholeWindow); - logsByTags.forEach((logsByTag, logIndex) => { + for (let logIndex = 0; logIndex < logsByTags.length; logIndex++) { + const logsByTag = logsByTags[logIndex]; if (logsByTag.length > 0) { // Discard public logs const filteredLogsByTag = logsByTag.filter(l => !l.isFromPublic); @@ -508,8 +501,16 @@ export class PXEOracleInterface implements ExecutionDataProvider { this.log.warn(`Discarded ${logsByTag.filter(l => l.isFromPublic).length} public logs with matching tags`); } - // The logs for the given tag exist so we store them for later processing - logsForRecipient.push(...filteredLogsByTag); + // We filter out the logs that are newer than the historical block number of the tx currently being constructed + const filteredLogsByBlockNumber = filteredLogsByTag.filter(l => l.blockNumber <= maxBlockNumber); + + // We store the logs in capsules (to later be obtained in Noir) + await this.#storePendingTaggedLogs( + contractAddress, + pendingTaggedLogArrayBaseSlot, + recipient, + filteredLogsByBlockNumber, + ); // We retrieve the indexed tagging secret corresponding to the log as I need that to evaluate whether // a new largest index have been found. @@ -541,7 +542,7 @@ export class PXEOracleInterface implements ExecutionDataProvider { ); } } - }); + } // Now based on the new largest indexes we found, we will construct a new secrets and windows set to fetch logs // for. Note that it's very unlikely that a new log from the current window would appear between the iterations @@ -570,12 +571,6 @@ export class PXEOracleInterface implements ExecutionDataProvider { secretsAndWindows = newSecretsAndWindows; } - // We filter the logs by block number and store them in the map. - logsMap.set( - recipient.toString(), - logsForRecipient.filter(log => log.blockNumber <= maxBlockNumber), - ); - // At this point we have processed all the logs for the recipient so we store the new largest indexes in the db. await this.taggingDataProvider.setTaggingSecretsIndexesAsRecipient( Object.entries(newLargestIndexMapToStore).map( @@ -584,47 +579,37 @@ export class PXEOracleInterface implements ExecutionDataProvider { recipient, ); } - return logsMap; } - /** - * Processes the tagged logs returned by syncTaggedLogs by decrypting them and storing them in the database. - * @param contractAddress - The address of the contract that the logs are tagged for. - * @param logs - The logs to process. - * @param recipient - The recipient of the logs. - */ - public async processTaggedLogs( + async #storePendingTaggedLogs( contractAddress: AztecAddress, - logs: TxScopedL2Log[], + capsuleArrayBaseSlot: Fr, recipient: AztecAddress, - simulator?: AcirSimulator, - ): Promise { - for (const scopedLog of logs) { - if (scopedLog.isFromPublic) { - throw new Error('Attempted to decrypt public log'); - } + logs: TxScopedL2Log[], + ) { + // Build all pending tagged logs upfront with their tx effects + const pendingTaggedLogs = await Promise.all( + logs.map(async scopedLog => { + // TODO(#9789): get these effects along with the log + const txEffect = await this.aztecNode.getTxEffect(scopedLog.txHash); + if (!txEffect) { + throw new Error(`Could not find tx effect for tx hash ${scopedLog.txHash}`); + } - // Log processing requires the note hashes in the tx in which the note was created. We are now assuming that the - // note was included in the same block in which the log was delivered - note that partial notes will not work this - // way. - const txEffect = await this.aztecNode.getTxEffect(scopedLog.txHash); - if (!txEffect) { - throw new Error(`Could not find tx effect for tx hash ${scopedLog.txHash}`); - } + const pendingTaggedLog = new PendingTaggedLog( + scopedLog.log.toFields(), + scopedLog.txHash.hash, + txEffect.data.noteHashes, + txEffect.data.nullifiers[0], + recipient, + scopedLog.logIndexInTx, + ); - // This will trigger calls to the deliverNote oracle - await this.callProcessLog( - contractAddress, - scopedLog.log.toFields(), - scopedLog.txHash, - txEffect.data.noteHashes, - txEffect.data.nullifiers[0], - scopedLog.logIndexInTx, - recipient, - simulator, - ); - } - return; + return pendingTaggedLog.toFields(); + }), + ); + + return this.capsuleDataProvider.appendToCapsuleArray(contractAddress, capsuleArrayBaseSlot, pendingTaggedLogs); } public async deliverNote( @@ -780,52 +765,6 @@ export class PXEOracleInterface implements ExecutionDataProvider { } } - async callProcessLog( - contractAddress: AztecAddress, - logCiphertext: Fr[], - txHash: TxHash, - noteHashes: Fr[], - firstNullifier: Fr, - logIndexInTx: number, - recipient: AztecAddress, - simulator?: AcirSimulator, - ) { - const artifact: FunctionArtifact | undefined = await this.contractDataProvider.getFunctionArtifactByName( - contractAddress, - 'process_log', - ); - if (!artifact) { - throw new Error( - `Mandatory implementation of "process_log" missing in noir contract ${contractAddress.toString()}.`, - ); - } - - const selector = await FunctionSelector.fromNameAndParameters(artifact); - const execRequest: FunctionCall = { - name: artifact.name, - to: contractAddress, - selector, - type: FunctionType.UNCONSTRAINED, - isStatic: artifact.isStatic, - args: encodeArguments(artifact, [ - toBoundedVec(logCiphertext, PRIVATE_LOG_SIZE_IN_FIELDS), - txHash.toString(), - toBoundedVec(noteHashes, MAX_NOTE_HASHES_PER_TX), - firstNullifier, - logIndexInTx, - recipient, - ]), - returnTypes: artifact.returnTypes, - }; - - await (simulator ?? new AcirSimulator(this, this.simulationProvider)).runUnconstrained( - execRequest, - contractAddress, - selector, - [], // empty scope as this call should not require access to private information - ); - } - storeCapsule(contractAddress: AztecAddress, slot: Fr, capsule: Fr[]): Promise { return this.capsuleDataProvider.storeCapsule(contractAddress, slot, capsule); } @@ -882,7 +821,3 @@ export class PXEOracleInterface implements ExecutionDataProvider { ); } } - -function toBoundedVec(array: Fr[], maxLength: number) { - return { storage: array.concat(Array(maxLength - array.length).fill(new Fr(0))), len: array.length }; -} diff --git a/yarn-project/pxe/src/pxe_service/pxe_service.ts b/yarn-project/pxe/src/pxe_service/pxe_service.ts index e4baf87f650a..e753b970dd6b 100644 --- a/yarn-project/pxe/src/pxe_service/pxe_service.ts +++ b/yarn-project/pxe/src/pxe_service/pxe_service.ts @@ -155,7 +155,6 @@ export class PXEService implements PXE { const pxeOracleInterface = new PXEOracleInterface( node, keyStore, - simulationProvider, contractDataProvider, noteDataProvider, capsuleDataProvider, diff --git a/yarn-project/pxe/src/storage/capsule_data_provider/capsule_data_provider.ts b/yarn-project/pxe/src/storage/capsule_data_provider/capsule_data_provider.ts index 0d987ee0fe1c..bb24ff5aa1cf 100644 --- a/yarn-project/pxe/src/storage/capsule_data_provider/capsule_data_provider.ts +++ b/yarn-project/pxe/src/storage/capsule_data_provider/capsule_data_provider.ts @@ -67,6 +67,32 @@ export class CapsuleDataProvider implements DataProvider { } } + /** + * Appends multiple capsules to a capsule array stored at the base slot. + * The array length is stored at the base slot, and elements are stored in consecutive slots after it. + * All operations are performed in a single transaction. + * @param contractAddress - The contract address that owns the capsule array + * @param baseSlot - The slot where the array length is stored + * @param capsules - Array of capsule data to append + */ + appendToCapsuleArray(contractAddress: AztecAddress, baseSlot: Fr, capsules: Fr[][]): Promise { + return this.#store.transactionAsync(async () => { + // Load current length, defaulting to 0 if not found + const lengthData = await this.loadCapsule(contractAddress, baseSlot); + const currentLength = lengthData ? lengthData[0].toBigInt() : 0n; + + // Store each capsule at consecutive slots after baseSlot + 1 + currentLength + for (let i = 0; i < capsules.length; i++) { + const nextSlot = baseSlot.add(new Fr(1)).add(new Fr(currentLength + BigInt(i))); + await this.storeCapsule(contractAddress, nextSlot, capsules[i]); + } + + // Update length to include all new capsules + const newLength = currentLength + BigInt(capsules.length); + await this.storeCapsule(contractAddress, baseSlot, [new Fr(newLength)]); + }); + } + public async getSize() { return (await toArray(this.#capsules.valuesAsync())).reduce( (sum, value) => sum + value.length * Fr.SIZE_IN_BYTES, diff --git a/yarn-project/pxe/src/storage/private_event_data_provider/private_event_data_provider.ts b/yarn-project/pxe/src/storage/private_event_data_provider/private_event_data_provider.ts index 2df4f8ee4106..ee27cbad224c 100644 --- a/yarn-project/pxe/src/storage/private_event_data_provider/private_event_data_provider.ts +++ b/yarn-project/pxe/src/storage/private_event_data_provider/private_event_data_provider.ts @@ -11,6 +11,7 @@ import type { DataProvider } from '../data_provider.js'; interface PrivateEventEntry { logContent: Buffer; blockNumber: number; + logIndexInTx: number; } /** @@ -72,7 +73,7 @@ export class PrivateEventDataProvider implements DataProvider { this.logger.verbose('storing private event log', { contractAddress, recipient, logContent, blockNumber }); const index = await this.#eventLogs.lengthAsync(); - await this.#eventLogs.push({ logContent: serializeToBuffer(logContent), blockNumber }); + await this.#eventLogs.push({ logContent: serializeToBuffer(logContent), blockNumber, logIndexInTx }); const existingIndices = (await this.#eventLogIndex.getAsync(key)) || []; await this.#eventLogIndex.set(key, [...existingIndices, index]); @@ -98,7 +99,7 @@ export class PrivateEventDataProvider implements DataProvider { recipients: AztecAddress[], eventSelector: EventSelector, ): Promise { - const events: Fr[][] = []; + const events: Array<{ logContent: Fr[]; blockNumber: number; logIndexInTx: number }> = []; for (const recipient of recipients) { const key = `${contractAddress.toString()}_${recipient.toString()}_${eventSelector.toString()}`; @@ -115,11 +116,19 @@ export class PrivateEventDataProvider implements DataProvider { const numFields = entry.logContent.length / Fr.SIZE_IN_BYTES; const logContent = reader.readArray(numFields, Fr); - events.push(logContent); + events.push({ logContent, blockNumber: entry.blockNumber, logIndexInTx: entry.logIndexInTx }); } } - return events; + // Sort by block number first, then by logIndexInTx (note that we currently don't order by txs within a block) + events.sort((a, b) => { + if (a.blockNumber !== b.blockNumber) { + return a.blockNumber - b.blockNumber; + } + return a.logIndexInTx - b.logIndexInTx; + }); + + return events.map(e => e.logContent); } getSize(): Promise { diff --git a/yarn-project/simulator/src/private/acvm/oracle/oracle.ts b/yarn-project/simulator/src/private/acvm/oracle/oracle.ts index 7715f95343f1..b229f9d34ba2 100644 --- a/yarn-project/simulator/src/private/acvm/oracle/oracle.ts +++ b/yarn-project/simulator/src/private/acvm/oracle/oracle.ts @@ -372,8 +372,8 @@ export class Oracle { return []; } - async syncNotes(): Promise { - await this.typedOracle.syncNotes(); + async syncNotes([pendingTaggedLogArrayBaseSlot]: ACVMField[]): Promise { + await this.typedOracle.syncNotes(Fr.fromString(pendingTaggedLogArrayBaseSlot)); return []; } diff --git a/yarn-project/simulator/src/private/acvm/oracle/typed_oracle.ts b/yarn-project/simulator/src/private/acvm/oracle/typed_oracle.ts index 4d949bc2d025..2e46854224c0 100644 --- a/yarn-project/simulator/src/private/acvm/oracle/typed_oracle.ts +++ b/yarn-project/simulator/src/private/acvm/oracle/typed_oracle.ts @@ -214,7 +214,7 @@ export abstract class TypedOracle { return Promise.reject(new OracleMethodNotAvailableError('incrementAppTaggingSecretIndexAsSender')); } - syncNotes(): Promise { + syncNotes(_pendingTaggedLogArrayBaseSlot: Fr): Promise { return Promise.reject(new OracleMethodNotAvailableError('syncNotes')); } diff --git a/yarn-project/simulator/src/private/execution_data_provider.ts b/yarn-project/simulator/src/private/execution_data_provider.ts index 1043ee9f637f..62e671d4fc97 100644 --- a/yarn-project/simulator/src/private/execution_data_provider.ts +++ b/yarn-project/simulator/src/private/execution_data_provider.ts @@ -9,7 +9,7 @@ import type { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { L2Block } from '@aztec/stdlib/block'; import type { CompleteAddress, ContractInstance } from '@aztec/stdlib/contract'; import type { KeyValidationRequest } from '@aztec/stdlib/kernel'; -import { IndexedTaggingSecret, LogWithTxData, TxScopedL2Log } from '@aztec/stdlib/logs'; +import { IndexedTaggingSecret, LogWithTxData } from '@aztec/stdlib/logs'; import type { NoteStatus } from '@aztec/stdlib/note'; import { type MerkleTreeId, type NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees'; import type { BlockHeader, TxHash } from '@aztec/stdlib/tx'; @@ -226,22 +226,18 @@ export interface ExecutionDataProvider extends CommitmentsDBInterface { ): Promise; /** - * Synchronizes the logs tagged with scoped addresses and all the senders in the address book. Returns the found logs - * and updates the indexes of the secrets used to tag them until there are no more logs to sync. - * @param contractAddress - The address of the contract that the logs are tagged for + * Synchronizes the logs tagged with scoped addresses and all the senders in the address book. Stores the found logs + * in CapsuleArray ready for a later retrieval in Aztec.nr. + * @param contractAddress - The address of the contract that the logs are tagged for. + * @param pendingTaggedLogArrayBaseSlot - The base slot of the pending tagged log capsule array in which found logs will be stored. * @param scopes - The scoped addresses to sync logs for. If not provided, all accounts in the address book will be * synced. - * @returns A map of recipient addresses to a list of encrypted logs. */ - syncTaggedLogs(contractAddress: AztecAddress, scopes?: AztecAddress[]): Promise>; - - /** - * Processes the tagged logs returned by syncTaggedLogs by decrypting them and storing them in the database. - * @param contractAddress - The address of the contract that emitted the logs. - * @param logs - The logs to process. - * @param recipient - The recipient of the logs. - */ - processTaggedLogs(contractAddress: AztecAddress, logs: TxScopedL2Log[], recipient: AztecAddress): Promise; + syncTaggedLogs( + contractAddress: AztecAddress, + pendingTaggedLogArrayBaseSlot: Fr, + scopes?: AztecAddress[], + ): Promise; /** * Delivers the preimage and metadata of a committed note so that it can be later requested via the `getNotes` diff --git a/yarn-project/simulator/src/private/private_execution.test.ts b/yarn-project/simulator/src/private/private_execution.test.ts index d4269be1e76b..25d4e5658c6f 100644 --- a/yarn-project/simulator/src/private/private_execution.test.ts +++ b/yarn-project/simulator/src/private/private_execution.test.ts @@ -46,7 +46,7 @@ import { } from '@aztec/stdlib/hash'; import { KeyValidationRequest, getNonEmptyItems } from '@aztec/stdlib/kernel'; import { computeAppNullifierSecretKey, deriveKeys } from '@aztec/stdlib/keys'; -import { IndexedTaggingSecret, TxScopedL2Log } from '@aztec/stdlib/logs'; +import { IndexedTaggingSecret } from '@aztec/stdlib/logs'; import type { L1ToL2Message } from '@aztec/stdlib/messaging'; import { Note } from '@aztec/stdlib/note'; import { makeHeader } from '@aztec/stdlib/testing'; @@ -298,9 +298,7 @@ describe('Private Execution test suite', () => { return Promise.resolve(artifact); }); - executionDataProvider.syncTaggedLogs.mockImplementation((_, __) => - Promise.resolve(new Map()), - ); + executionDataProvider.syncTaggedLogs.mockImplementation((_, __) => Promise.resolve()); executionDataProvider.loadCapsule.mockImplementation((_, __) => Promise.resolve(null)); executionDataProvider.getPublicStorageAt.mockImplementation( @@ -429,8 +427,7 @@ describe('Private Execution test suite', () => { buildNote(60n, ownerCompleteAddress.address, storageSlot, valueNoteTypeId), buildNote(80n, ownerCompleteAddress.address, storageSlot, valueNoteTypeId), ]); - executionDataProvider.syncTaggedLogs.mockResolvedValue(new Map()); - executionDataProvider.processTaggedLogs.mockResolvedValue(); + executionDataProvider.syncTaggedLogs.mockResolvedValue(); executionDataProvider.getNotes.mockResolvedValue(notes); const consumedNotes = await asyncMap(notes, async ({ note, nonce }) => { @@ -481,8 +478,7 @@ describe('Private Execution test suite', () => { const storageSlot = await deriveStorageSlotInMap(new Fr(1n), owner); const notes = await Promise.all([buildNote(balance, ownerCompleteAddress.address, storageSlot, valueNoteTypeId)]); - executionDataProvider.syncTaggedLogs.mockResolvedValue(new Map()); - executionDataProvider.processTaggedLogs.mockResolvedValue(); + executionDataProvider.syncTaggedLogs.mockResolvedValue(); executionDataProvider.getNotes.mockResolvedValue(notes); const consumedNotes = await asyncMap(notes, async ({ note, nonce }) => { @@ -943,8 +939,7 @@ describe('Private Execution test suite', () => { }); it('should be able to insert, read, and nullify pending note hashes in one call', async () => { - executionDataProvider.syncTaggedLogs.mockResolvedValue(new Map()); - executionDataProvider.processTaggedLogs.mockResolvedValue(); + executionDataProvider.syncTaggedLogs.mockResolvedValue(); executionDataProvider.getNotes.mockResolvedValue([]); const amountToTransfer = 100n; @@ -996,8 +991,7 @@ describe('Private Execution test suite', () => { }); it('should be able to insert, read, and nullify pending note hashes in nested calls', async () => { - executionDataProvider.syncTaggedLogs.mockResolvedValue(new Map()); - executionDataProvider.processTaggedLogs.mockResolvedValue(); + executionDataProvider.syncTaggedLogs.mockResolvedValue(); executionDataProvider.getNotes.mockResolvedValue([]); const amountToTransfer = 100n; @@ -1068,8 +1062,7 @@ describe('Private Execution test suite', () => { }); it('cant read a commitment that is inserted later in same call', async () => { - executionDataProvider.syncTaggedLogs.mockResolvedValue(new Map()); - executionDataProvider.processTaggedLogs.mockResolvedValue(); + executionDataProvider.syncTaggedLogs.mockResolvedValue(); executionDataProvider.getNotes.mockResolvedValue([]); const amountToTransfer = 100n; @@ -1107,8 +1100,7 @@ describe('Private Execution test suite', () => { describe('Get notes', () => { it('fails if returning no notes', async () => { const args = [2n, true]; - executionDataProvider.syncTaggedLogs.mockResolvedValue(new Map()); - executionDataProvider.processTaggedLogs.mockResolvedValue(); + executionDataProvider.syncTaggedLogs.mockResolvedValue(); executionDataProvider.getNotes.mockResolvedValue([]); await expect(() => diff --git a/yarn-project/simulator/src/private/private_execution_oracle.ts b/yarn-project/simulator/src/private/private_execution_oracle.ts index e467e3de0203..643f29370cac 100644 --- a/yarn-project/simulator/src/private/private_execution_oracle.ts +++ b/yarn-project/simulator/src/private/private_execution_oracle.ts @@ -500,15 +500,8 @@ export class PrivateExecutionOracle extends UnconstrainedExecutionOracle { await this.executionDataProvider.incrementAppTaggingSecretIndexAsSender(this.contractAddress, sender, recipient); } - public override async syncNotes() { - const taggedLogsByRecipient = await this.executionDataProvider.syncTaggedLogs(this.contractAddress, this.scopes); - for (const [recipient, taggedLogs] of taggedLogsByRecipient.entries()) { - await this.executionDataProvider.processTaggedLogs( - this.contractAddress, - taggedLogs, - AztecAddress.fromString(recipient), - ); - } + public override async syncNotes(pendingTaggedLogArrayBaseSlot: Fr) { + await this.executionDataProvider.syncTaggedLogs(this.contractAddress, pendingTaggedLogArrayBaseSlot, this.scopes); await this.executionDataProvider.removeNullifiedNotes(this.contractAddress); } diff --git a/yarn-project/simulator/src/private/unconstrained_execution.test.ts b/yarn-project/simulator/src/private/unconstrained_execution.test.ts index d93bbe84a69e..b962a93bf795 100644 --- a/yarn-project/simulator/src/private/unconstrained_execution.test.ts +++ b/yarn-project/simulator/src/private/unconstrained_execution.test.ts @@ -3,7 +3,6 @@ import { StatefulTestContractArtifact } from '@aztec/noir-contracts.js/StatefulT import { FunctionCall, FunctionSelector, FunctionType, encodeArguments } from '@aztec/stdlib/abi'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; import { CompleteAddress, type ContractInstance } from '@aztec/stdlib/contract'; -import type { TxScopedL2Log } from '@aztec/stdlib/logs'; import { Note } from '@aztec/stdlib/note'; import { BlockHeader } from '@aztec/stdlib/tx'; @@ -66,8 +65,7 @@ describe('Unconstrained Execution test suite', () => { currentContractClassId: new Fr(42), originalContractClassId: new Fr(42), } as ContractInstance); - executionDataProvider.syncTaggedLogs.mockResolvedValue(new Map()); - executionDataProvider.processTaggedLogs.mockResolvedValue(); + executionDataProvider.syncTaggedLogs.mockResolvedValue(); executionDataProvider.getBlockHeader.mockResolvedValue(BlockHeader.empty()); executionDataProvider.getNotes.mockResolvedValue( notes.map((note, index) => ({ @@ -82,9 +80,7 @@ describe('Unconstrained Execution test suite', () => { })), ); - executionDataProvider.syncTaggedLogs.mockImplementation((_, __) => - Promise.resolve(new Map()), - ); + executionDataProvider.syncTaggedLogs.mockImplementation((_, __) => Promise.resolve()); executionDataProvider.loadCapsule.mockImplementation((_, __) => Promise.resolve(null)); const execRequest: FunctionCall = { diff --git a/yarn-project/simulator/src/private/unconstrained_execution_oracle.ts b/yarn-project/simulator/src/private/unconstrained_execution_oracle.ts index ea16c4b7ff5f..51ef4fd87db9 100644 --- a/yarn-project/simulator/src/private/unconstrained_execution_oracle.ts +++ b/yarn-project/simulator/src/private/unconstrained_execution_oracle.ts @@ -278,16 +278,8 @@ export class UnconstrainedExecutionOracle extends TypedOracle { return await this.executionDataProvider.getIndexedTaggingSecretAsSender(this.contractAddress, sender, recipient); } - public override async syncNotes() { - const taggedLogsByRecipient = await this.executionDataProvider.syncTaggedLogs(this.contractAddress, this.scopes); - - for (const [recipient, taggedLogs] of taggedLogsByRecipient.entries()) { - await this.executionDataProvider.processTaggedLogs( - this.contractAddress, - taggedLogs, - AztecAddress.fromString(recipient), - ); - } + public override async syncNotes(pendingTaggedLogArrayBaseSlot: Fr) { + await this.executionDataProvider.syncTaggedLogs(this.contractAddress, pendingTaggedLogArrayBaseSlot, this.scopes); await this.executionDataProvider.removeNullifiedNotes(this.contractAddress); } diff --git a/yarn-project/stdlib/src/logs/index.ts b/yarn-project/stdlib/src/logs/index.ts index a44a9190502f..03951d5b31a3 100644 --- a/yarn-project/stdlib/src/logs/index.ts +++ b/yarn-project/stdlib/src/logs/index.ts @@ -3,6 +3,7 @@ export * from './indexed_tagging_secret.js'; export * from './contract_class_log.js'; export * from './public_log.js'; export * from './private_log.js'; +export * from './pending_tagged_log.js'; export * from './log_id.js'; export * from './log_filter.js'; export * from './extended_public_log.js'; diff --git a/yarn-project/stdlib/src/logs/pending_tagged_log.test.ts b/yarn-project/stdlib/src/logs/pending_tagged_log.test.ts new file mode 100644 index 000000000000..fff8901682a3 --- /dev/null +++ b/yarn-project/stdlib/src/logs/pending_tagged_log.test.ts @@ -0,0 +1,121 @@ +import { Fr } from '@aztec/foundation/fields'; +import { updateInlineTestData } from '@aztec/foundation/testing/files'; + +import { AztecAddress } from '../aztec-address/index.js'; +import { PendingTaggedLog } from './pending_tagged_log.js'; + +describe('PendingTaggedLog', () => { + it('serialization matches snapshots and output of Noir serialization', () => { + const log = [new Fr(1n), new Fr(2n), new Fr(3n)]; + const txHash = new Fr(123n); + const uniqueNoteHashes = [new Fr(4n), new Fr(5n)]; + const firstNullifier = new Fr(6n); + const recipient = AztecAddress.fromField(new Fr(789n)); + const logIndexInTx = 10; + + const pendingLog = new PendingTaggedLog(log, txHash, uniqueNoteHashes, firstNullifier, recipient, logIndexInTx); + const serialized = pendingLog.toFields(); + + // Test against snapshot + expect(serialized.map(f => f.toString())).toMatchInlineSnapshot(` + [ + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x0000000000000000000000000000000000000000000000000000000000000002", + "0x0000000000000000000000000000000000000000000000000000000000000003", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000003", + "0x000000000000000000000000000000000000000000000000000000000000007b", + "0x0000000000000000000000000000000000000000000000000000000000000004", + "0x0000000000000000000000000000000000000000000000000000000000000005", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000002", + "0x0000000000000000000000000000000000000000000000000000000000000006", + "0x0000000000000000000000000000000000000000000000000000000000000315", + "0x000000000000000000000000000000000000000000000000000000000000000a", + ] + `); + + // Run with AZTEC_GENERATE_TEST_DATA=1 to update noir test data + const fieldArrayStr = `[${serialized.map(f => f.toString()).join(',')}]`; + updateInlineTestData( + 'noir-projects/aztec-nr/aztec/src/discovery/pending_tagged_log.nr', + 'serialized_pending_tagged_log_from_typescript', + fieldArrayStr, + ); + }); +}); diff --git a/yarn-project/stdlib/src/logs/pending_tagged_log.ts b/yarn-project/stdlib/src/logs/pending_tagged_log.ts new file mode 100644 index 000000000000..fe9437034b6d --- /dev/null +++ b/yarn-project/stdlib/src/logs/pending_tagged_log.ts @@ -0,0 +1,43 @@ +import { MAX_NOTE_HASHES_PER_TX, PRIVATE_LOG_SIZE_IN_FIELDS } from '@aztec/constants'; +import { Fr } from '@aztec/foundation/fields'; + +import type { AztecAddress } from '../aztec-address/index.js'; + +/** + * Represents a pending tagged log as it is stored in the pending tagged log array to which the syncNotes oracle + * inserts found private logs. A TS version of `pending_tagged_log.nr`. + */ +export class PendingTaggedLog { + constructor( + public log: Fr[], + public txHash: Fr, + public uniqueNoteHashesInTx: Fr[], + public firstNullifierInTx: Fr, + public recipient: AztecAddress, + public logIndexInTx: number, + ) {} + + toFields(): Fr[] { + return [ + ...serializeBoundedVec(this.log, PRIVATE_LOG_SIZE_IN_FIELDS), + this.txHash, + ...serializeBoundedVec(this.uniqueNoteHashesInTx, MAX_NOTE_HASHES_PER_TX), + this.firstNullifierInTx, + this.recipient.toField(), + new Fr(this.logIndexInTx), + ]; + } +} + +/** + * Helper function to serialize a bounded vector according to Noir's BoundedVec format + * @param values - The values to serialize + * @param maxLength - The maximum length of the bounded vector + * @returns The serialized bounded vector as Fr[] + */ +function serializeBoundedVec(values: Fr[], maxLength: number): Fr[] { + const lengthDiff = maxLength - values.length; + const zeroPaddingArray = Array(lengthDiff).fill(Fr.ZERO); + const storage = values.concat(zeroPaddingArray); + return [...storage, new Fr(values.length)]; +} diff --git a/yarn-project/txe/src/oracle/txe_oracle.ts b/yarn-project/txe/src/oracle/txe_oracle.ts index 6525db849e29..1661d7936c93 100644 --- a/yarn-project/txe/src/oracle/txe_oracle.ts +++ b/yarn-project/txe/src/oracle/txe_oracle.ts @@ -170,7 +170,6 @@ export class TXE implements TypedOracle { this.pxeOracleInterface = new PXEOracleInterface( this.node, this.keyStore, - this.simulationProvider, this.contractDataProvider, this.noteDataProvider, this.capsuleDataProvider, @@ -1082,16 +1081,8 @@ export class TXE implements TypedOracle { return await this.pxeOracleInterface.getIndexedTaggingSecretAsSender(this.contractAddress, sender, recipient); } - async syncNotes() { - const taggedLogsByRecipient = await this.pxeOracleInterface.syncTaggedLogs(this.contractAddress, undefined); - - for (const [recipient, taggedLogs] of taggedLogsByRecipient.entries()) { - await this.pxeOracleInterface.processTaggedLogs( - this.contractAddress, - taggedLogs, - AztecAddress.fromString(recipient), - ); - } + async syncNotes(pendingTaggedLogArrayBaseSlot: Fr) { + await this.pxeOracleInterface.syncTaggedLogs(this.contractAddress, pendingTaggedLogArrayBaseSlot); await this.pxeOracleInterface.removeNullifiedNotes(this.contractAddress); diff --git a/yarn-project/txe/src/txe_service/txe_service.ts b/yarn-project/txe/src/txe_service/txe_service.ts index d164ef0877c0..c864a4f443fe 100644 --- a/yarn-project/txe/src/txe_service/txe_service.ts +++ b/yarn-project/txe/src/txe_service/txe_service.ts @@ -516,8 +516,8 @@ export class TXEService { return toForeignCallResult(secret.toFields().map(toSingle)); } - async syncNotes() { - await this.typedOracle.syncNotes(); + async syncNotes(pendingTaggedLogArrayBaseSlot: ForeignCallSingle) { + await this.typedOracle.syncNotes(fromSingle(pendingTaggedLogArrayBaseSlot)); return toForeignCallResult([]); } @@ -622,10 +622,15 @@ export class TXEService { return toForeignCallResult(arrayToBoundedVec(bufferToU8Array(plaintextBuffer), ciphertextBVecStorage.length)); } - async getSharedSecret(address: ForeignCallSingle, ephPk: ForeignCallArray) { + async getSharedSecret( + address: ForeignCallSingle, + ephPKField0: ForeignCallSingle, + ephPKField1: ForeignCallSingle, + ephPKField2: ForeignCallSingle, + ) { const secret = await this.typedOracle.getSharedSecret( AztecAddress.fromField(fromSingle(address)), - Point.fromFields(fromArray(ephPk)), + Point.fromFields([fromSingle(ephPKField0), fromSingle(ephPKField1), fromSingle(ephPKField2)]), ); return toForeignCallResult(secret.toFields().map(toSingle)); }