From 16abb5ae2e48ed7fcfe3dd2fbf9a111620f30b53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Bene=C5=A1?= Date: Mon, 30 Oct 2023 17:33:05 +0100 Subject: [PATCH] feat!: PXE.getNotes(...) + refactor of note types (#3051) Fixes #2908 Fixes [#2909](https://github.com/AztecProtocol/aztec-packages/issues/2909) Fixes [#2910](https://github.com/AztecProtocol/aztec-packages/issues/2910) Fixes #3061 **Note 1**: This PR became huge because it made sense to refactor `PXE.addNotes` here as well since I now use the ExtendedNote type there. **Note 2**: Since this PR is already too big I decided to finish renaming preimage as serialized_note in Noir in a separate PR. **Note 3**: Note processor now stores a `CompleteAddress` instead of just a public key because I need the address from `CompleteAddress` to construct the `ExtendedNote`. **Note 4**: This is a breaking change because of the `PXE.getNotes` change and the `PXE.getPrivateStorageAt` removal. --- cspell.json | 1 + .../acir-simulator/src/acvm/oracle/oracle.ts | 24 ++--- .../src/acvm/oracle/typed_oracle.ts | 10 +- .../src/client/client_execution_context.ts | 45 ++++---- .../acir-simulator/src/client/db_oracle.ts | 2 +- .../src/client/execution_result.ts | 10 +- .../src/client/pick_notes.test.ts | 13 +-- .../acir-simulator/src/client/pick_notes.ts | 21 ++-- .../src/client/private_execution.test.ts | 68 ++++++------ .../src/client/simulator.test.ts | 21 ++-- .../acir-simulator/src/client/simulator.ts | 68 ++++-------- .../client/unconstrained_execution.test.ts | 10 +- .../src/client/view_data_oracle.ts | 13 +-- .../aztec-sandbox/src/examples/token.ts | 8 +- yarn-project/aztec.js/src/contract/sent_tx.ts | 21 +++- yarn-project/aztec.js/src/index.ts | 5 +- yarn-project/aztec.js/src/pxe_client.ts | 6 +- .../aztec.js/src/utils/cheat_codes.ts | 7 +- .../aztec.js/src/wallet/base_wallet.ts | 24 ++--- .../token/src/tests/token.contract.test.ts | 8 +- .../circuits.js/src/utils/serialize.ts | 6 +- yarn-project/cli/src/index.ts | 11 +- .../end-to-end/src/e2e_2_pxes.test.ts | 9 +- .../src/e2e_escrow_contract.test.ts | 14 +-- .../src/e2e_lending_contract.test.ts | 7 +- .../e2e_multiple_accounts_1_enc_key.test.ts | 9 +- .../src/e2e_sandbox_example.test.ts | 13 ++- .../end-to-end/src/e2e_token_contract.test.ts | 14 ++- .../src/guides/dapp_testing.test.ts | 31 ++++-- .../end-to-end/src/guides/up_quick_start.sh | 2 +- .../writing_an_account_contract.test.ts | 9 +- .../end-to-end/src/sample-dapp/index.mjs | 8 +- .../end-to-end/src/sample-dapp/index.test.mjs | 7 +- yarn-project/end-to-end/src/shared/browser.ts | 16 ++- yarn-project/end-to-end/src/shared/cli.ts | 2 +- .../src/shared/cross_chain_test_harness.ts | 8 +- .../p2p/src/service/known_txs.test.ts | 11 +- .../p2p/src/service/tx_messages.test.ts | 11 +- yarn-project/pxe/src/database/database.ts | 45 +++----- yarn-project/pxe/src/database/index.ts | 1 - .../pxe/src/database/memory_db.test.ts | 36 ++++--- yarn-project/pxe/src/database/memory_db.ts | 56 ++++++---- .../pxe/src/database/note_dao.test.ts | 36 +++++++ yarn-project/pxe/src/database/note_dao.ts | 90 ++++++++++++++++ .../src/database/note_spending_info_dao.ts | 75 ------------- .../src/kernel_prover/kernel_prover.test.ts | 20 ++-- .../pxe/src/kernel_prover/kernel_prover.ts | 8 +- .../src/note_processor/note_processor.test.ts | 101 +++++++++--------- .../pxe/src/note_processor/note_processor.ts | 98 ++++++++--------- .../pxe/src/pxe_http/pxe_http_server.ts | 6 +- .../pxe/src/pxe_service/pxe_service.ts | 98 ++++++++--------- .../pxe/src/simulator_oracle/index.ts | 24 ++--- yarn-project/types/src/index.ts | 1 + yarn-project/types/src/interfaces/pxe.ts | 53 +++------ yarn-project/types/src/l2_block_context.ts | 2 +- yarn-project/types/src/logs/index.ts | 2 +- .../browserify-cipher.d.ts | 0 .../encrypt_buffer.test.ts | 0 .../encrypt_buffer.ts | 0 .../types/src/logs/l1_note_payload/index.ts | 3 + .../l1_note_payload/l1_note_payload.test.ts | 38 +++++++ .../logs/l1_note_payload/l1_note_payload.ts | 86 +++++++++++++++ .../src/logs/l1_note_payload/note.test.ts | 12 +++ .../types/src/logs/l1_note_payload/note.ts | 58 ++++++++++ .../src/logs/note_spending_info/index.ts | 3 - .../note_spending_info/note_preimage.test.ts | 12 --- .../logs/note_spending_info/note_preimage.ts | 55 ---------- .../note_spending_info.test.ts | 38 ------- .../note_spending_info/note_spending_info.ts | 85 --------------- yarn-project/types/src/mocks.ts | 18 +++- .../types/src/notes/extended_note.test.ts | 10 ++ yarn-project/types/src/notes/extended_note.ts | 50 +++++++++ yarn-project/types/src/notes/index.ts | 2 + yarn-project/types/src/notes/note_filter.ts | 18 ++++ yarn-project/types/src/tx/tx_receipt.ts | 6 +- 75 files changed, 985 insertions(+), 834 deletions(-) create mode 100644 yarn-project/pxe/src/database/note_dao.test.ts create mode 100644 yarn-project/pxe/src/database/note_dao.ts delete mode 100644 yarn-project/pxe/src/database/note_spending_info_dao.ts rename yarn-project/types/src/logs/{note_spending_info => l1_note_payload}/browserify-cipher.d.ts (100%) rename yarn-project/types/src/logs/{note_spending_info => l1_note_payload}/encrypt_buffer.test.ts (100%) rename yarn-project/types/src/logs/{note_spending_info => l1_note_payload}/encrypt_buffer.ts (100%) create mode 100644 yarn-project/types/src/logs/l1_note_payload/index.ts create mode 100644 yarn-project/types/src/logs/l1_note_payload/l1_note_payload.test.ts create mode 100644 yarn-project/types/src/logs/l1_note_payload/l1_note_payload.ts create mode 100644 yarn-project/types/src/logs/l1_note_payload/note.test.ts create mode 100644 yarn-project/types/src/logs/l1_note_payload/note.ts delete mode 100644 yarn-project/types/src/logs/note_spending_info/index.ts delete mode 100644 yarn-project/types/src/logs/note_spending_info/note_preimage.test.ts delete mode 100644 yarn-project/types/src/logs/note_spending_info/note_preimage.ts delete mode 100644 yarn-project/types/src/logs/note_spending_info/note_spending_info.test.ts delete mode 100644 yarn-project/types/src/logs/note_spending_info/note_spending_info.ts create mode 100644 yarn-project/types/src/notes/extended_note.test.ts create mode 100644 yarn-project/types/src/notes/extended_note.ts create mode 100644 yarn-project/types/src/notes/index.ts create mode 100644 yarn-project/types/src/notes/note_filter.ts diff --git a/cspell.json b/cspell.json index 00506c7af67..f549ebafcf7 100644 --- a/cspell.json +++ b/cspell.json @@ -14,6 +14,7 @@ "barretenberg", "bbfree", "bbmalloc", + "benesjan", "bodyparser", "bootnode", "Brillig", diff --git a/yarn-project/acir-simulator/src/acvm/oracle/oracle.ts b/yarn-project/acir-simulator/src/acvm/oracle/oracle.ts index 4e242ecc543..08944796c0c 100644 --- a/yarn-project/acir-simulator/src/acvm/oracle/oracle.ts +++ b/yarn-project/acir-simulator/src/acvm/oracle/oracle.ts @@ -64,7 +64,7 @@ export class Oracle { [offset]: ACVMField[], [returnSize]: ACVMField[], ): Promise { - const notes = await this.typedOracle.getNotes( + const noteDatas = await this.typedOracle.getNotes( fromACVMField(storageSlot), +numSelects, selectBy.map(s => +s), @@ -75,26 +75,26 @@ export class Oracle { +offset, ); - const preimageLength = notes?.[0]?.preimage.length ?? 0; - if (!notes.every(({ preimage }) => preimageLength === preimage.length)) { - throw new Error('Preimages for a particular note type should all be the same length.'); + const noteLength = noteDatas?.[0]?.note.items.length ?? 0; + if (!noteDatas.every(({ note }) => noteLength === note.items.length)) { + throw new Error('Notes should all be the same length.'); } - const contractAddress = notes[0]?.contractAddress ?? Fr.ZERO; + const contractAddress = noteDatas[0]?.contractAddress ?? Fr.ZERO; // Values indicates whether the note is settled or transient. const noteTypes = { isSettled: new Fr(0), isTransient: new Fr(1), }; - const flattenData = notes.flatMap(({ nonce, preimage, index }) => [ + const flattenData = noteDatas.flatMap(({ nonce, note, index }) => [ nonce, index === undefined ? noteTypes.isTransient : noteTypes.isSettled, - ...preimage, + ...note.items, ]); const returnFieldSize = +returnSize; - const returnData = [notes.length, contractAddress, ...flattenData].map(v => toACVMField(v)); + const returnData = [noteDatas.length, contractAddress, ...flattenData].map(v => toACVMField(v)); if (returnData.length > returnFieldSize) { throw new Error(`Return data size too big. Maximum ${returnFieldSize} fields. Got ${flattenData.length}.`); } @@ -103,10 +103,10 @@ export class Oracle { return returnData.concat(paddedZeros); } - notifyCreatedNote([storageSlot]: ACVMField[], preimage: ACVMField[], [innerNoteHash]: ACVMField[]): ACVMField { + notifyCreatedNote([storageSlot]: ACVMField[], note: ACVMField[], [innerNoteHash]: ACVMField[]): ACVMField { this.typedOracle.notifyCreatedNote( fromACVMField(storageSlot), - preimage.map(fromACVMField), + note.map(fromACVMField), fromACVMField(innerNoteHash), ); return toACVMField(0); @@ -148,14 +148,14 @@ export class Oracle { [storageSlot]: ACVMField[], [publicKeyX]: ACVMField[], [publicKeyY]: ACVMField[], - preimage: ACVMField[], + log: ACVMField[], ): ACVMField { const publicKey = new Point(fromACVMField(publicKeyX), fromACVMField(publicKeyY)); this.typedOracle.emitEncryptedLog( AztecAddress.fromString(contractAddress), Fr.fromString(storageSlot), publicKey, - preimage.map(fromACVMField), + log.map(fromACVMField), ); return toACVMField(0); } diff --git a/yarn-project/acir-simulator/src/acvm/oracle/typed_oracle.ts b/yarn-project/acir-simulator/src/acvm/oracle/typed_oracle.ts index a07f03e6ef2..a864471a722 100644 --- a/yarn-project/acir-simulator/src/acvm/oracle/typed_oracle.ts +++ b/yarn-project/acir-simulator/src/acvm/oracle/typed_oracle.ts @@ -3,20 +3,20 @@ import { FunctionSelector } from '@aztec/foundation/abi'; import { AztecAddress } from '@aztec/foundation/aztec-address'; import { EthAddress } from '@aztec/foundation/eth-address'; import { Fr, GrumpkinScalar } from '@aztec/foundation/fields'; -import { CompleteAddress, PublicKey, UnencryptedL2Log } from '@aztec/types'; +import { CompleteAddress, Note, PublicKey, UnencryptedL2Log } from '@aztec/types'; /** * Information about a note needed during execution. */ export interface NoteData { + /** The note. */ + note: Note; /** The contract address of the note. */ contractAddress: AztecAddress; /** The storage slot of the note. */ storageSlot: Fr; /** The nonce of the note. */ nonce: Fr; - /** The preimage of the note */ - preimage: Fr[]; /** The inner note hash of the note. */ innerNoteHash: Fr; /** The corresponding nullifier of the note. Undefined for pending notes. */ @@ -93,7 +93,7 @@ export abstract class TypedOracle { throw new Error('Not available.'); } - notifyCreatedNote(_storageSlot: Fr, _preimage: Fr[], _innerNoteHash: Fr): void { + notifyCreatedNote(_storageSlot: Fr, _note: Fr[], _innerNoteHash: Fr): void { throw new Error('Not available.'); } @@ -121,7 +121,7 @@ export abstract class TypedOracle { throw new Error('Not available.'); } - emitEncryptedLog(_contractAddress: AztecAddress, _storageSlot: Fr, _publicKey: PublicKey, _preimage: Fr[]): void { + emitEncryptedLog(_contractAddress: AztecAddress, _storageSlot: Fr, _publicKey: PublicKey, _log: Fr[]): void { throw new Error('Not available.'); } diff --git a/yarn-project/acir-simulator/src/client/client_execution_context.ts b/yarn-project/acir-simulator/src/client/client_execution_context.ts index 0b64bcfd332..5236304a716 100644 --- a/yarn-project/acir-simulator/src/client/client_execution_context.ts +++ b/yarn-project/acir-simulator/src/client/client_execution_context.ts @@ -15,7 +15,7 @@ import { FunctionAbi, FunctionArtifact, countArgumentsSize } from '@aztec/founda import { AztecAddress } from '@aztec/foundation/aztec-address'; import { Fr, Point } from '@aztec/foundation/fields'; import { createDebugLogger } from '@aztec/foundation/log'; -import { AuthWitness, FunctionL2Logs, NotePreimage, NoteSpendingInfo, UnencryptedL2Log } from '@aztec/types'; +import { AuthWitness, FunctionL2Logs, L1NotePayload, Note, UnencryptedL2Log } from '@aztec/types'; import { NoteData, @@ -28,7 +28,7 @@ import { SideEffectCounter } from '../common/index.js'; import { PackedArgsCache } from '../common/packed_args_cache.js'; import { DBOracle } from './db_oracle.js'; import { ExecutionNoteCache } from './execution_note_cache.js'; -import { ExecutionResult, NewNoteData } from './execution_result.js'; +import { ExecutionResult, NoteAndSlot } from './execution_result.js'; import { pickNotes } from './pick_notes.js'; import { executePrivateFunction } from './private_execution.js'; import { ViewDataOracle } from './view_data_oracle.js'; @@ -44,7 +44,7 @@ export class ClientExecutionContext extends ViewDataOracle { * This information is only for references (currently used for tests), and is not used for any sort of constrains. * Users can also use this to get a clearer idea of what's happened during a simulation. */ - private newNotes: NewNoteData[] = []; + private newNotes: NoteAndSlot[] = []; /** * Notes from previous transactions that are returned to the oracle call `getNotes` during this execution. * The mapping maps from the unique siloed note hash to the index for notes created in private executions. @@ -135,7 +135,7 @@ export class ClientExecutionContext extends ViewDataOracle { * Get the data for the newly created notes. * @param innerNoteHashes - Inner note hashes for the notes. */ - public getNewNotes(): NewNoteData[] { + public getNewNotes(): NoteAndSlot[] { return this.newNotes; } @@ -176,16 +176,13 @@ export class ClientExecutionContext extends ViewDataOracle { } /** - * Gets some notes for a contract address and storage slot. - * Returns a flattened array containing real-note-count and note preimages. + * Gets some notes for a storage slot. * * @remarks - * - * Check for pending notes with matching address/slot. + * Check for pending notes with matching slot. * Real notes coming from DB will have a leafIndex which * represents their index in the note hash tree. * - * @param contractAddress - The contract address. * @param storageSlot - The storage slot. * @param numSelects - The number of valid selects in selectBy and selectValues. * @param selectBy - An array of indices of the fields to selects. @@ -194,12 +191,7 @@ export class ClientExecutionContext extends ViewDataOracle { * @param sortOrder - The order of the corresponding index in sortBy. (1: DESC, 2: ASC, 0: Do nothing) * @param limit - The number of notes to retrieve per query. * @param offset - The starting index for pagination. - * @param returnSize - The return size. - * @returns Flattened array of ACVMFields (format expected by Noir/ACVM) containing: - * count - number of real (non-padding) notes retrieved, - * contractAddress - the contract address, - * preimages - the real note preimages retrieved, and - * paddedZeros - zeros to ensure an array with length returnSize expected by Noir circuit + * @returns Array of note data. */ public async getNotes( storageSlot: Fr, @@ -227,7 +219,7 @@ export class ClientExecutionContext extends ViewDataOracle { this.log( `Returning ${notes.length} notes for ${this.contractAddress} at ${storageSlot}: ${notes - .map(n => `${n.nonce.toString()}:[${n.preimage.map(i => i.toString()).join(',')}]`) + .map(n => `${n.nonce.toString()}:[${n.note.items.map(i => i.toString()).join(',')}]`) .join(', ')}`, ); @@ -251,22 +243,23 @@ export class ClientExecutionContext extends ViewDataOracle { * It can be used in subsequent calls (or transactions when chaining txs is possible). * @param contractAddress - The contract address. * @param storageSlot - The storage slot. - * @param preimage - The preimage of the new note. + * @param noteItems - The items to be included in a Note. * @param innerNoteHash - The inner note hash of the new note. * @returns */ - public notifyCreatedNote(storageSlot: Fr, preimage: Fr[], innerNoteHash: Fr) { + public notifyCreatedNote(storageSlot: Fr, noteItems: Fr[], innerNoteHash: Fr) { + const note = new Note(noteItems); this.noteCache.addNewNote({ contractAddress: this.contractAddress, storageSlot, nonce: Fr.ZERO, // Nonce cannot be known during private execution. - preimage, + note, siloedNullifier: undefined, // Siloed nullifier cannot be known for newly created note. innerNoteHash, }); this.newNotes.push({ storageSlot, - preimage, + note, }); } @@ -285,13 +278,13 @@ export class ClientExecutionContext extends ViewDataOracle { * @param contractAddress - The contract address of the note. * @param storageSlot - The storage slot the note is at. * @param publicKey - The public key of the account that can decrypt the log. - * @param preimage - The preimage of the note. + * @param log - The log contents. */ - public emitEncryptedLog(contractAddress: AztecAddress, storageSlot: Fr, publicKey: Point, preimage: Fr[]) { - const notePreimage = new NotePreimage(preimage); - const noteSpendingInfo = new NoteSpendingInfo(notePreimage, contractAddress, storageSlot); - const encryptedNotePreimage = noteSpendingInfo.toEncryptedBuffer(publicKey, this.curve); - this.encryptedLogs.push(encryptedNotePreimage); + public emitEncryptedLog(contractAddress: AztecAddress, storageSlot: Fr, publicKey: Point, log: Fr[]) { + const note = new Note(log); + const l1NotePayload = new L1NotePayload(note, contractAddress, storageSlot); + const encryptedNote = l1NotePayload.toEncryptedBuffer(publicKey, this.curve); + this.encryptedLogs.push(encryptedNote); } /** diff --git a/yarn-project/acir-simulator/src/client/db_oracle.ts b/yarn-project/acir-simulator/src/client/db_oracle.ts index 18559da2716..6dd86e95d45 100644 --- a/yarn-project/acir-simulator/src/client/db_oracle.ts +++ b/yarn-project/acir-simulator/src/client/db_oracle.ts @@ -50,7 +50,7 @@ export interface DBOracle extends CommitmentsDB { /** * Retrieves a set of notes stored in the database for a given contract address and storage slot. * The query result is paginated using 'limit' and 'offset' values. - * Returns an object containing an array of note data, including preimage, nonce, and index for each note. + * Returns an object containing an array of note data. * * @param contractAddress - The AztecAddress instance representing the contract address. * @param storageSlot - The Fr instance representing the storage slot of the notes. diff --git a/yarn-project/acir-simulator/src/client/execution_result.ts b/yarn-project/acir-simulator/src/client/execution_result.ts index bf2b738953e..8dc812ee963 100644 --- a/yarn-project/acir-simulator/src/client/execution_result.ts +++ b/yarn-project/acir-simulator/src/client/execution_result.ts @@ -1,16 +1,16 @@ import { PrivateCallStackItem, PublicCallRequest, ReadRequestMembershipWitness } from '@aztec/circuits.js'; import { DecodedReturn } from '@aztec/foundation/abi'; import { Fr } from '@aztec/foundation/fields'; -import { FunctionL2Logs } from '@aztec/types'; +import { FunctionL2Logs, Note } from '@aztec/types'; import { ACVMField } from '../acvm/index.js'; /** * The contents of a new note. */ -export interface NewNoteData { - /** The preimage of the note. */ - preimage: Fr[]; +export interface NoteAndSlot { + /** The note. */ + note: Note; /** The storage slot of the note. */ storageSlot: Fr; } @@ -33,7 +33,7 @@ export interface ExecutionResult { readRequestPartialWitnesses: ReadRequestMembershipWitness[]; // Needed when we enable chained txs. The new notes can be cached and used in a later transaction. /** The notes created in the executed function. */ - newNotes: NewNoteData[]; + newNotes: NoteAndSlot[]; /** The decoded return values of the executed function. */ returnValues: DecodedReturn; /** The nested executions. */ diff --git a/yarn-project/acir-simulator/src/client/pick_notes.test.ts b/yarn-project/acir-simulator/src/client/pick_notes.test.ts index f95d4d628e9..1d9a5ffeb22 100644 --- a/yarn-project/acir-simulator/src/client/pick_notes.test.ts +++ b/yarn-project/acir-simulator/src/client/pick_notes.test.ts @@ -1,26 +1,27 @@ import { Fr } from '@aztec/foundation/fields'; +import { Note } from '@aztec/types'; import { SortOrder, pickNotes } from './pick_notes.js'; describe('getNotes', () => { - const expectNotesFields = (notes: { preimage: Fr[] }[], ...expected: [number, bigint[]][]) => { + const expectNotesFields = (notes: { note: Note }[], ...expected: [number, bigint[]][]) => { expect(notes.length).toBe(expected[0][1].length); expected.forEach(([fieldIndex, fields]) => { for (let i = 0; i < notes.length; ++i) { - expect(notes[i].preimage[fieldIndex].value).toBe(fields[i]); + expect(notes[i].note.items[fieldIndex].value).toBe(fields[i]); } }); }; - const expectNotes = (notes: { preimage: Fr[] }[], expected: bigint[][]) => { + const expectNotes = (notes: { note: Note }[], expected: bigint[][]) => { expect(notes.length).toBe(expected.length); notes.forEach((note, i) => { - expect(note.preimage.map(p => p.value)).toEqual(expected[i]); + expect(note.note.items.map(p => p.value)).toEqual(expected[i]); }); }; - const createNote = (preimage: bigint[]) => ({ - preimage: preimage.map(f => new Fr(f)), + const createNote = (items: bigint[]) => ({ + note: new Note(items.map(f => new Fr(f))), }); it('should get sorted notes', () => { diff --git a/yarn-project/acir-simulator/src/client/pick_notes.ts b/yarn-project/acir-simulator/src/client/pick_notes.ts index 8f9fbddbff5..ca4bbaf1576 100644 --- a/yarn-project/acir-simulator/src/client/pick_notes.ts +++ b/yarn-project/acir-simulator/src/client/pick_notes.ts @@ -1,4 +1,5 @@ import { Fr } from '@aztec/foundation/fields'; +import { Note } from '@aztec/types'; /** * Configuration for selecting values. @@ -64,17 +65,17 @@ interface GetOptions { } /** - * Basic data needed from a note to perform sort. + * Data needed from to perform sort. */ -interface BasicNoteData { +interface ContainsNote { /** - * Preimage of a note. + * The note. */ - preimage: Fr[]; + note: Note; } -const selectNotes = (notes: T[], selects: Select[]): T[] => - notes.filter(note => selects.every(({ index, value }) => note.preimage[index]?.equals(value))); +const selectNotes = (noteDatas: T[], selects: Select[]): T[] => + noteDatas.filter(noteData => selects.every(({ index, value }) => noteData.note.items[index]?.equals(value))); const sortNotes = (a: Fr[], b: Fr[], sorts: Sort[], level = 0): number => { if (sorts[level] === undefined) return 0; @@ -93,11 +94,11 @@ const sortNotes = (a: Fr[], b: Fr[], sorts: Sort[], level = 0): number => { /** * Pick from a note array a number of notes that meet the criteria. */ -export function pickNotes( - notes: T[], +export function pickNotes( + noteDatas: T[], { selects = [], sorts = [], limit = 0, offset = 0 }: GetOptions, ): T[] { - return selectNotes(notes, selects) - .sort((a, b) => sortNotes(a.preimage, b.preimage, sorts)) + return selectNotes(noteDatas, selects) + .sort((a, b) => sortNotes(a.note.items, b.note.items, sorts)) .slice(offset, limit ? offset + limit : undefined); } diff --git a/yarn-project/acir-simulator/src/client/private_execution.test.ts b/yarn-project/acir-simulator/src/client/private_execution.test.ts index 4b089fa49b9..8938a242015 100644 --- a/yarn-project/acir-simulator/src/client/private_execution.test.ts +++ b/yarn-project/acir-simulator/src/client/private_execution.test.ts @@ -26,7 +26,6 @@ import { makeContractDeploymentData } from '@aztec/circuits.js/factories'; import { FunctionArtifact, FunctionSelector, encodeArguments } from '@aztec/foundation/abi'; import { asyncMap } from '@aztec/foundation/async-map'; import { AztecAddress } from '@aztec/foundation/aztec-address'; -import { toBufferBE } from '@aztec/foundation/bigint-buffer'; import { EthAddress } from '@aztec/foundation/eth-address'; import { Fr, GrumpkinScalar } from '@aztec/foundation/fields'; import { DebugLogger, createDebugLogger } from '@aztec/foundation/log'; @@ -40,7 +39,7 @@ import { TestContractArtifact, TokenContractArtifact, } from '@aztec/noir-contracts/artifacts'; -import { PackedArguments, TxExecutionRequest } from '@aztec/types'; +import { Note, PackedArguments, TxExecutionRequest } from '@aztec/types'; import { jest } from '@jest/globals'; import { MockProxy, mock } from 'jest-mock-extended'; @@ -144,7 +143,6 @@ describe('Private Execution test suite', () => { return trees[name]; }; - const hash = (data: Buffer[]) => pedersenHashInputs(circuitsWasm, data); const hashFields = (data: Fr[]) => Fr.fromBuffer( pedersenHashInputs( @@ -205,13 +203,13 @@ describe('Private Execution test suite', () => { // `hash(firstNullifier, noteHashIndex)` const noteHashIndex = Math.floor(Math.random()); // mock index in TX's final newNoteHashes array const nonce = computeCommitmentNonce(circuitsWasm, mockFirstNullifier, noteHashIndex); - const preimage = [new Fr(amount), owner.toField(), Fr.random()]; - const innerNoteHash = Fr.fromBuffer(hash(preimage.map(p => p.toBuffer()))); + const note = new Note([new Fr(amount), owner.toField(), Fr.random()]); + const innerNoteHash = hashFields(note.items); return { contractAddress, storageSlot, nonce, - preimage, + note, innerNoteHash, siloedNullifier: new Fr(0), index: currentNoteIndex++, @@ -244,7 +242,7 @@ describe('Private Execution test suite', () => { const [commitment] = newCommitments; expect(commitment).toEqual( - await acirSimulator.computeInnerNoteHash(contractAddress, newNote.storageSlot, newNote.preimage), + await acirSimulator.computeInnerNoteHash(contractAddress, newNote.storageSlot, newNote.note), ); }); @@ -262,7 +260,7 @@ describe('Private Execution test suite', () => { const [commitment] = newCommitments; expect(commitment).toEqual( - await acirSimulator.computeInnerNoteHash(contractAddress, newNote.storageSlot, newNote.preimage), + await acirSimulator.computeInnerNoteHash(contractAddress, newNote.storageSlot, newNote.note), ); }); @@ -276,8 +274,8 @@ describe('Private Execution test suite', () => { const notes = [buildNote(60n, owner, storageSlot), buildNote(80n, owner, storageSlot)]; oracle.getNotes.mockResolvedValue(notes); - const consumedNotes = await asyncMap(notes, ({ nonce, preimage }) => - acirSimulator.computeNoteHashAndNullifier(contractAddress, nonce, storageSlot, preimage), + const consumedNotes = await asyncMap(notes, ({ nonce, note }) => + acirSimulator.computeNoteHashAndNullifier(contractAddress, nonce, storageSlot, note), ); await insertLeaves(consumedNotes.map(n => n.siloedNoteHash)); @@ -298,14 +296,14 @@ describe('Private Execution test suite', () => { const [changeNoteCommitment, recipientNoteCommitment] = newCommitments; expect(recipientNoteCommitment).toEqual( - await acirSimulator.computeInnerNoteHash(contractAddress, recipientStorageSlot, recipientNote.preimage), + await acirSimulator.computeInnerNoteHash(contractAddress, recipientStorageSlot, recipientNote.note), ); expect(changeNoteCommitment).toEqual( - await acirSimulator.computeInnerNoteHash(contractAddress, storageSlot, changeNote.preimage), + await acirSimulator.computeInnerNoteHash(contractAddress, storageSlot, changeNote.note), ); - expect(recipientNote.preimage[0]).toEqual(new Fr(amountToTransfer)); - expect(changeNote.preimage[0]).toEqual(new Fr(40n)); + expect(recipientNote.note.items[0]).toEqual(new Fr(amountToTransfer)); + expect(changeNote.note.items[0]).toEqual(new Fr(40n)); const readRequests = result.callStackItem.publicInputs.readRequests.filter(field => !field.equals(Fr.ZERO)); expect(readRequests).toHaveLength(consumedNotes.length); @@ -322,8 +320,8 @@ describe('Private Execution test suite', () => { const notes = [buildNote(balance, owner, storageSlot)]; oracle.getNotes.mockResolvedValue(notes); - const consumedNotes = await asyncMap(notes, ({ nonce, preimage }) => - acirSimulator.computeNoteHashAndNullifier(contractAddress, nonce, storageSlot, preimage), + const consumedNotes = await asyncMap(notes, ({ nonce, note }) => + acirSimulator.computeNoteHashAndNullifier(contractAddress, nonce, storageSlot, note), ); await insertLeaves(consumedNotes.map(n => n.siloedNoteHash)); @@ -335,8 +333,8 @@ describe('Private Execution test suite', () => { expect(result.newNotes).toHaveLength(2); const [changeNote, recipientNote] = result.newNotes; - expect(recipientNote.preimage[0]).toEqual(new Fr(amountToTransfer)); - expect(changeNote.preimage[0]).toEqual(new Fr(balance - amountToTransfer)); + expect(recipientNote.note.items[0]).toEqual(new Fr(amountToTransfer)); + expect(changeNote.note.items[0]).toEqual(new Fr(balance - amountToTransfer)); }); }); @@ -482,17 +480,17 @@ describe('Private Execution test suite', () => { const wasm = await CircuitsWasm.get(); const secret = new Fr(1n); const secretHash = computeSecretMessageHash(wasm, secret); - const preimage = [toBufferBE(amount, 32), secretHash.toBuffer()]; - const noteHash = Fr.fromBuffer(hash(preimage)); + const note = new Note([new Fr(amount), secretHash]); + const noteHash = hashFields(note.items); const storageSlot = new Fr(5); - const innerNoteHash = Fr.fromBuffer(hash([storageSlot.toBuffer(), noteHash.toBuffer()])); + const innerNoteHash = hashFields([storageSlot, noteHash]); const siloedNoteHash = siloCommitment(wasm, contractAddress, innerNoteHash); oracle.getNotes.mockResolvedValue([ { contractAddress, storageSlot, nonce: Fr.ZERO, - preimage: preimage.map(p => Fr.fromBuffer(p)), + note, innerNoteHash: new Fr(EMPTY_NULLIFIED_COMMITMENT), siloedNullifier: Fr.random(), index: 1n, @@ -605,17 +603,17 @@ describe('Private Execution test suite', () => { }); expect(result.newNotes).toHaveLength(1); - const note = result.newNotes[0]; - expect(note.storageSlot).toEqual(computeSlotForMapping(new Fr(1n), owner.toField(), circuitsWasm)); + const noteAndSlot = result.newNotes[0]; + expect(noteAndSlot.storageSlot).toEqual(computeSlotForMapping(new Fr(1n), owner.toField(), circuitsWasm)); - expect(note.preimage[0]).toEqual(new Fr(amountToTransfer)); + expect(noteAndSlot.note.items[0]).toEqual(new Fr(amountToTransfer)); const newCommitments = result.callStackItem.publicInputs.newCommitments.filter(field => !field.equals(Fr.ZERO)); expect(newCommitments).toHaveLength(1); const commitment = newCommitments[0]; const storageSlot = computeSlotForMapping(new Fr(1n), owner.toField(), circuitsWasm); - const innerNoteHash = await acirSimulator.computeInnerNoteHash(contractAddress, storageSlot, note.preimage); + const innerNoteHash = await acirSimulator.computeInnerNoteHash(contractAddress, storageSlot, noteAndSlot.note); expect(commitment).toEqual(innerNoteHash); // read request should match innerNoteHash for pending notes (there is no nonce, so can't compute "unique" hash) @@ -676,10 +674,10 @@ describe('Private Execution test suite', () => { const getNotesAfterNullify = result.nestedExecutions[2]; expect(execInsert.newNotes).toHaveLength(1); - const note = execInsert.newNotes[0]; - expect(note.storageSlot).toEqual(computeSlotForMapping(new Fr(1n), owner.toField(), circuitsWasm)); + const noteAndSlot = execInsert.newNotes[0]; + expect(noteAndSlot.storageSlot).toEqual(computeSlotForMapping(new Fr(1n), owner.toField(), circuitsWasm)); - expect(note.preimage[0]).toEqual(new Fr(amountToTransfer)); + expect(noteAndSlot.note.items[0]).toEqual(new Fr(amountToTransfer)); const newCommitments = execInsert.callStackItem.publicInputs.newCommitments.filter( field => !field.equals(Fr.ZERO), @@ -688,7 +686,7 @@ describe('Private Execution test suite', () => { const commitment = newCommitments[0]; const storageSlot = computeSlotForMapping(new Fr(1n), owner.toField(), circuitsWasm); - const innerNoteHash = await acirSimulator.computeInnerNoteHash(contractAddress, storageSlot, note.preimage); + const innerNoteHash = await acirSimulator.computeInnerNoteHash(contractAddress, storageSlot, noteAndSlot.note); expect(commitment).toEqual(innerNoteHash); // read request should match innerNoteHash for pending notes (there is no nonce, so can't compute "unique" hash) @@ -724,17 +722,19 @@ describe('Private Execution test suite', () => { }); expect(result.newNotes).toHaveLength(1); - const note = result.newNotes[0]; - expect(note.storageSlot).toEqual(computeSlotForMapping(new Fr(1n), owner.toField(), circuitsWasm)); + const noteAndSlot = result.newNotes[0]; + expect(noteAndSlot.storageSlot).toEqual(computeSlotForMapping(new Fr(1n), owner.toField(), circuitsWasm)); - expect(note.preimage[0]).toEqual(new Fr(amountToTransfer)); + expect(noteAndSlot.note.items[0]).toEqual(new Fr(amountToTransfer)); const newCommitments = result.callStackItem.publicInputs.newCommitments.filter(field => !field.equals(Fr.ZERO)); expect(newCommitments).toHaveLength(1); const commitment = newCommitments[0]; const storageSlot = computeSlotForMapping(new Fr(1n), owner.toField(), circuitsWasm); - expect(commitment).toEqual(await acirSimulator.computeInnerNoteHash(contractAddress, storageSlot, note.preimage)); + expect(commitment).toEqual( + await acirSimulator.computeInnerNoteHash(contractAddress, storageSlot, noteAndSlot.note), + ); // read requests should be empty const readRequest = result.callStackItem.publicInputs.readRequests[0].value; diff --git a/yarn-project/acir-simulator/src/client/simulator.test.ts b/yarn-project/acir-simulator/src/client/simulator.test.ts index e21c118b719..d1b5cade92f 100644 --- a/yarn-project/acir-simulator/src/client/simulator.test.ts +++ b/yarn-project/acir-simulator/src/client/simulator.test.ts @@ -5,6 +5,7 @@ import { ABIParameterVisibility } from '@aztec/foundation/abi'; import { AztecAddress } from '@aztec/foundation/aztec-address'; import { Fr, GrumpkinScalar } from '@aztec/foundation/fields'; import { TokenContractArtifact } from '@aztec/noir-contracts/artifacts'; +import { Note } from '@aztec/types'; import { MockProxy, mock } from 'jest-mock-extended'; @@ -49,19 +50,19 @@ describe('Simulator', () => { const nonce = Fr.random(); const storageSlot = Fr.random(); - const createPreimage = (amount = 123n) => [new Fr(amount), owner.toField(), Fr.random()]; + const createNote = (amount = 123n) => new Note([new Fr(amount), owner.toField(), Fr.random()]); it('should compute note hashes and nullifier', async () => { oracle.getFunctionArtifactByName.mockResolvedValue(artifact); - const preimage = createPreimage(); - const valueNoteHash = hashFields(preimage); + const note = createNote(); + const valueNoteHash = hashFields(note.items); const innerNoteHash = hashFields([storageSlot, valueNoteHash]); const siloedNoteHash = siloCommitment(circuitsWasm, contractAddress, innerNoteHash); const uniqueSiloedNoteHash = computeUniqueCommitment(circuitsWasm, nonce, siloedNoteHash); const innerNullifier = hashFields([uniqueSiloedNoteHash, ownerPk.low, ownerPk.high]); - const result = await simulator.computeNoteHashAndNullifier(contractAddress, nonce, storageSlot, preimage); + const result = await simulator.computeNoteHashAndNullifier(contractAddress, nonce, storageSlot, note); expect(result).toEqual({ innerNoteHash, @@ -74,22 +75,22 @@ describe('Simulator', () => { it('throw if the contract does not implement "compute_note_hash_and_nullifier"', async () => { oracle.getFunctionArtifactByName.mockResolvedValue(undefined); - const preimage = createPreimage(); + const note = createNote(); await expect( - simulator.computeNoteHashAndNullifier(contractAddress, nonce, storageSlot, preimage), + simulator.computeNoteHashAndNullifier(contractAddress, nonce, storageSlot, note), ).rejects.toThrowError(/Mandatory implementation of "compute_note_hash_and_nullifier" missing/); }); it('throw if a note has more fields than "compute_note_hash_and_nullifier" can process', async () => { - const preimage = createPreimage(); - const wrongPreimageLength = preimage.length - 1; + const note = createNote(); + const wrongPreimageLength = note.length - 1; const modifiedArtifact: FunctionArtifactWithDebugMetadata = { ...artifact, parameters: [ ...artifact.parameters.slice(0, -1), { - name: 'preimage', + name: 'note', type: { kind: 'array', length: wrongPreimageLength, @@ -104,7 +105,7 @@ describe('Simulator', () => { oracle.getFunctionArtifactByName.mockResolvedValue(modifiedArtifact); await expect( - simulator.computeNoteHashAndNullifier(contractAddress, nonce, storageSlot, preimage), + simulator.computeNoteHashAndNullifier(contractAddress, nonce, storageSlot, note), ).rejects.toThrowError( new RegExp(`"compute_note_hash_and_nullifier" can only handle a maximum of ${wrongPreimageLength} fields`), ); diff --git a/yarn-project/acir-simulator/src/client/simulator.ts b/yarn-project/acir-simulator/src/client/simulator.ts index 9367a5fe6d9..092fa49b735 100644 --- a/yarn-project/acir-simulator/src/client/simulator.ts +++ b/yarn-project/acir-simulator/src/client/simulator.ts @@ -5,7 +5,7 @@ import { AztecAddress } from '@aztec/foundation/aztec-address'; import { EthAddress } from '@aztec/foundation/eth-address'; import { Fr } from '@aztec/foundation/fields'; import { DebugLogger, createDebugLogger } from '@aztec/foundation/log'; -import { AztecNode, FunctionCall, TxExecutionRequest } from '@aztec/types'; +import { AztecNode, FunctionCall, Note, TxExecutionRequest } from '@aztec/types'; import { WasmBlackBoxFunctionSolver, createBlackBoxSolver } from '@noir-lang/acvm_js'; @@ -152,15 +152,10 @@ export class AcirSimulator { * @param contractAddress - The address of the contract. * @param nonce - The nonce of the note hash. * @param storageSlot - The storage slot. - * @param notePreimage - The note preimage. + * @param note - The note. * @returns The nullifier. */ - public async computeNoteHashAndNullifier( - contractAddress: AztecAddress, - nonce: Fr, - storageSlot: Fr, - notePreimage: Fr[], - ) { + public async computeNoteHashAndNullifier(contractAddress: AztecAddress, nonce: Fr, storageSlot: Fr, note: Note) { const artifact: FunctionArtifactWithDebugMetadata | undefined = await this.db.getFunctionArtifactByName( contractAddress, 'compute_note_hash_and_nullifier', @@ -172,18 +167,18 @@ export class AcirSimulator { } const maxNoteFields = (artifact.parameters[artifact.parameters.length - 1].type as ArrayType).length; - if (maxNoteFields < notePreimage.length) { + if (maxNoteFields < note.items.length) { throw new Error( - `The note being processed has ${notePreimage.length} fields, while "compute_note_hash_and_nullifier" can only handle a maximum of ${maxNoteFields} fields. Please consider increasing the allowed field size to accommodate all notes generated from the contract.`, + `The note being processed has ${note.items.length} fields, while "compute_note_hash_and_nullifier" can only handle a maximum of ${maxNoteFields} fields. Please consider increasing the allowed field size to accommodate all notes generated from the contract.`, ); } - const extendedPreimage = notePreimage.concat(Array(maxNoteFields - notePreimage.length).fill(Fr.ZERO)); + const extendedNoteItems = note.items.concat(Array(maxNoteFields - note.items.length).fill(Fr.ZERO)); const execRequest: FunctionCall = { to: AztecAddress.ZERO, functionData: FunctionData.empty(), - args: encodeArguments(artifact, [contractAddress, nonce, storageSlot, extendedPreimage]), + args: encodeArguments(artifact, [contractAddress, nonce, storageSlot, extendedNoteItems]), }; const [innerNoteHash, siloedNoteHash, uniqueSiloedNoteHash, innerNullifier] = (await this.runUnconstrained( @@ -204,16 +199,11 @@ export class AcirSimulator { * Computes the inner note hash of a note, which contains storage slot and the custom note hash. * @param contractAddress - The address of the contract. * @param storageSlot - The storage slot. - * @param notePreimage - The note preimage. + * @param note - The note. * @returns The note hash. */ - public async computeInnerNoteHash(contractAddress: AztecAddress, storageSlot: Fr, notePreimage: Fr[]) { - const { innerNoteHash } = await this.computeNoteHashAndNullifier( - contractAddress, - Fr.ZERO, - storageSlot, - notePreimage, - ); + public async computeInnerNoteHash(contractAddress: AztecAddress, storageSlot: Fr, note: Note) { + const { innerNoteHash } = await this.computeNoteHashAndNullifier(contractAddress, Fr.ZERO, storageSlot, note); return innerNoteHash; } @@ -222,21 +212,11 @@ export class AcirSimulator { * @param contractAddress - The address of the contract. * @param nonce - The nonce of the note hash. * @param storageSlot - The storage slot. - * @param notePreimage - The note preimage. + * @param note - The note. * @returns The note hash. */ - public async computeUniqueSiloedNoteHash( - contractAddress: AztecAddress, - nonce: Fr, - storageSlot: Fr, - notePreimage: Fr[], - ) { - const { uniqueSiloedNoteHash } = await this.computeNoteHashAndNullifier( - contractAddress, - nonce, - storageSlot, - notePreimage, - ); + public async computeUniqueSiloedNoteHash(contractAddress: AztecAddress, nonce: Fr, storageSlot: Fr, note: Note) { + const { uniqueSiloedNoteHash } = await this.computeNoteHashAndNullifier(contractAddress, nonce, storageSlot, note); return uniqueSiloedNoteHash; } @@ -245,16 +225,11 @@ export class AcirSimulator { * @param contractAddress - The address of the contract. * @param nonce - The nonce of the note hash. * @param storageSlot - The storage slot. - * @param notePreimage - The note preimage. + * @param note - The note. * @returns The note hash. */ - public async computeSiloedNoteHash(contractAddress: AztecAddress, nonce: Fr, storageSlot: Fr, notePreimage: Fr[]) { - const { siloedNoteHash } = await this.computeNoteHashAndNullifier( - contractAddress, - nonce, - storageSlot, - notePreimage, - ); + public async computeSiloedNoteHash(contractAddress: AztecAddress, nonce: Fr, storageSlot: Fr, note: Note) { + const { siloedNoteHash } = await this.computeNoteHashAndNullifier(contractAddress, nonce, storageSlot, note); return siloedNoteHash; } @@ -263,16 +238,11 @@ export class AcirSimulator { * @param contractAddress - The address of the contract. * @param nonce - The nonce of the unique note hash. * @param storageSlot - The storage slot. - * @param notePreimage - The note preimage. + * @param note - The note. * @returns The note hash. */ - public async computeInnerNullifier(contractAddress: AztecAddress, nonce: Fr, storageSlot: Fr, notePreimage: Fr[]) { - const { innerNullifier } = await this.computeNoteHashAndNullifier( - contractAddress, - nonce, - storageSlot, - notePreimage, - ); + public async computeInnerNullifier(contractAddress: AztecAddress, nonce: Fr, storageSlot: Fr, note: Note) { + const { innerNullifier } = await this.computeNoteHashAndNullifier(contractAddress, nonce, storageSlot, note); return innerNullifier; } } diff --git a/yarn-project/acir-simulator/src/client/unconstrained_execution.test.ts b/yarn-project/acir-simulator/src/client/unconstrained_execution.test.ts index 34a4a9ced6e..8cfdf278a2b 100644 --- a/yarn-project/acir-simulator/src/client/unconstrained_execution.test.ts +++ b/yarn-project/acir-simulator/src/client/unconstrained_execution.test.ts @@ -3,7 +3,7 @@ import { FunctionSelector, encodeArguments } from '@aztec/foundation/abi'; import { AztecAddress } from '@aztec/foundation/aztec-address'; import { Fr, GrumpkinScalar } from '@aztec/foundation/fields'; import { StatefulTestContractArtifact } from '@aztec/noir-contracts/artifacts'; -import { FunctionCall } from '@aztec/types'; +import { FunctionCall, Note } from '@aztec/types'; import { mock } from 'jest-mock-extended'; @@ -25,7 +25,7 @@ describe('Unconstrained Execution test suite', () => { let owner: AztecAddress; const buildNote = (amount: bigint, owner: AztecAddress) => { - return [new Fr(amount), owner, Fr.random()]; + return new Note([new Fr(amount), owner.toField(), Fr.random()]); }; beforeEach(async () => { @@ -42,16 +42,16 @@ describe('Unconstrained Execution test suite', () => { const contractAddress = AztecAddress.random(); const artifact = StatefulTestContractArtifact.functions.find(f => f.name === 'summed_values')!; - const preimages = [...Array(5).fill(buildNote(1n, owner)), ...Array(2).fill(buildNote(2n, owner))]; + const notes: Note[] = [...Array(5).fill(buildNote(1n, owner)), ...Array(2).fill(buildNote(2n, owner))]; oracle.getHistoricBlockData.mockResolvedValue(HistoricBlockData.empty()); oracle.getNotes.mockResolvedValue( - preimages.map((preimage, index) => ({ + notes.map((note, index) => ({ contractAddress, storageSlot: Fr.random(), nonce: Fr.random(), isSome: new Fr(1), - preimage, + note, innerNoteHash: Fr.random(), siloedNullifier: Fr.random(), index: BigInt(index), diff --git a/yarn-project/acir-simulator/src/client/view_data_oracle.ts b/yarn-project/acir-simulator/src/client/view_data_oracle.ts index 55e1aff0206..53a47c04ff0 100644 --- a/yarn-project/acir-simulator/src/client/view_data_oracle.ts +++ b/yarn-project/acir-simulator/src/client/view_data_oracle.ts @@ -57,16 +57,13 @@ export class ViewDataOracle extends TypedOracle { } /** - * Gets some notes for a contract address and storage slot. - * Returns a flattened array containing real-note-count and note preimages. + * Gets some notes for a storage slot. * * @remarks - * - * Check for pending notes with matching address/slot. + * Check for pending notes with matching slot. * Real notes coming from DB will have a leafIndex which * represents their index in the note hash tree. * - * @param contractAddress - The contract address. * @param storageSlot - The storage slot. * @param numSelects - The number of valid selects in selectBy and selectValues. * @param selectBy - An array of indices of the fields to selects. @@ -75,11 +72,7 @@ export class ViewDataOracle extends TypedOracle { * @param sortOrder - The order of the corresponding index in sortBy. (1: DESC, 2: ASC, 0: Do nothing) * @param limit - The number of notes to retrieve per query. * @param offset - The starting index for pagination. - * @returns Flattened array of ACVMFields (format expected by Noir/ACVM) containing: - * count - number of real (non-padding) notes retrieved, - * contractAddress - the contract address, - * preimages - the real note preimages retrieved, and - * paddedZeros - zeros to ensure an array with length returnSize expected by Noir circuit + * @returns Array of note data. */ public async getNotes( storageSlot: Fr, diff --git a/yarn-project/aztec-sandbox/src/examples/token.ts b/yarn-project/aztec-sandbox/src/examples/token.ts index 82f85e8cd45..2d35a5011b7 100644 --- a/yarn-project/aztec-sandbox/src/examples/token.ts +++ b/yarn-project/aztec-sandbox/src/examples/token.ts @@ -2,13 +2,14 @@ import { AccountWallet, Fr, GrumpkinScalar, - NotePreimage, + Note, computeMessageSecretHash, createPXEClient, getUnsafeSchnorrAccount, } from '@aztec/aztec.js'; import { createDebugLogger } from '@aztec/foundation/log'; import { TokenContract } from '@aztec/noir-contracts/types'; +import { ExtendedNote } from '@aztec/types'; const logger = createDebugLogger('aztec:http-rpc-client'); @@ -56,8 +57,9 @@ async function main() { // Add the newly created "pending shield" note to PXE const pendingShieldsStorageSlot = new Fr(5); // The storage slot of `pending_shields` is 5. - const preimage = new NotePreimage([new Fr(ALICE_MINT_BALANCE), aliceSecretHash]); - await pxe.addNote(alice.address, token.address, pendingShieldsStorageSlot, preimage, receipt.txHash); + const note = new Note([new Fr(ALICE_MINT_BALANCE), aliceSecretHash]); + const extendedNote = new ExtendedNote(note, alice.address, token.address, pendingShieldsStorageSlot, receipt.txHash); + await pxe.addNote(extendedNote); // Make the tokens spendable by redeeming them using the secret (converts the "pending shield note" created above // to a "token note") diff --git a/yarn-project/aztec.js/src/contract/sent_tx.ts b/yarn-project/aztec.js/src/contract/sent_tx.ts index f61f2d669fa..5b84b1cd660 100644 --- a/yarn-project/aztec.js/src/contract/sent_tx.ts +++ b/yarn-project/aztec.js/src/contract/sent_tx.ts @@ -1,6 +1,6 @@ import { FieldsOf } from '@aztec/circuits.js'; import { retryUntil } from '@aztec/foundation/retry'; -import { GetUnencryptedLogsResponse, PXE, TxHash, TxReceipt, TxStatus } from '@aztec/types'; +import { ExtendedNote, GetUnencryptedLogsResponse, PXE, TxHash, TxReceipt, TxStatus } from '@aztec/types'; import every from 'lodash.every'; @@ -15,12 +15,15 @@ export type WaitOpts = { * If false, then any queries that depend on state set by this transaction may return stale data. Defaults to true. **/ waitForNotesSync?: boolean; + /** Whether newly created notes should be included in the receipt. */ + getNotes?: boolean; }; const DefaultWaitOpts: WaitOpts = { timeout: 60, interval: 1, waitForNotesSync: true, + getNotes: false, }; /** @@ -58,9 +61,15 @@ export class SentTx { * @returns The transaction receipt. */ public async wait(opts?: WaitOpts): Promise> { + if (opts?.getNotes && opts.waitForNotesSync === false) { + throw new Error('Cannot set getNotes to true if waitForNotesSync is false'); + } const receipt = await this.waitForReceipt(opts); if (receipt.status !== TxStatus.MINED) throw new Error(`Transaction ${await this.getTxHash()} was ${receipt.status}`); + if (opts?.getNotes) { + receipt.notes = await this.pxe.getNotes({ txHash: await this.getTxHash() }); + } return receipt; } @@ -74,6 +83,16 @@ export class SentTx { return this.pxe.getUnencryptedLogs({ txHash: await this.getTxHash() }); } + /** + * Gets notes created in this tx. + * @remarks This function will wait for the tx to be mined if it hasn't been already. + * @returns The requested notes. + */ + public async getNotes(): Promise { + await this.wait(); + return this.pxe.getNotes({ txHash: await this.getTxHash() }); + } + protected async waitForReceipt(opts?: WaitOpts): Promise { const txHash = await this.getTxHash(); return await retryUntil( diff --git a/yarn-project/aztec.js/src/index.ts b/yarn-project/aztec.js/src/index.ts index b84e1383946..4f315d036fb 100644 --- a/yarn-project/aztec.js/src/index.ts +++ b/yarn-project/aztec.js/src/index.ts @@ -13,13 +13,14 @@ export { AztecAddress, EthAddress, Point, Fr, FunctionSelector, GrumpkinScalar } export { ContractData, DeployedContract, - ExtendedContractData as ExtendedContractData, + ExtendedContractData, + ExtendedNote, FunctionCall, GrumpkinPrivateKey, L2BlockL2Logs, LogFilter, NodeInfo, - NotePreimage, + Note, PackedArguments, PublicKey, PXE, diff --git a/yarn-project/aztec.js/src/pxe_client.ts b/yarn-project/aztec.js/src/pxe_client.ts index 4e03af4ffbc..c50cdc848de 100644 --- a/yarn-project/aztec.js/src/pxe_client.ts +++ b/yarn-project/aztec.js/src/pxe_client.ts @@ -12,11 +12,12 @@ import { AuthWitness, ContractData, ExtendedContractData, + ExtendedNote, ExtendedUnencryptedL2Log, L2BlockL2Logs, L2Tx, LogId, - NotePreimage, + Note, PXE, Tx, TxExecutionRequest, @@ -42,7 +43,8 @@ export const createPXEClient = (url: string, fetch = makeFetch([1, 2, 3], true)) Point, Fr, GrumpkinScalar, - NotePreimage, + Note, + ExtendedNote, AuthWitness, L2Tx, LogId, diff --git a/yarn-project/aztec.js/src/utils/cheat_codes.ts b/yarn-project/aztec.js/src/utils/cheat_codes.ts index 8abc6ba5ee9..23f814d8f73 100644 --- a/yarn-project/aztec.js/src/utils/cheat_codes.ts +++ b/yarn-project/aztec.js/src/utils/cheat_codes.ts @@ -3,7 +3,7 @@ import { pedersenHashInputs } from '@aztec/circuits.js/barretenberg'; import { toBigIntBE, toHex } from '@aztec/foundation/bigint-buffer'; import { keccak } from '@aztec/foundation/crypto'; import { createDebugLogger } from '@aztec/foundation/log'; -import { NotePreimage, PXE } from '@aztec/types'; +import { Note, PXE } from '@aztec/types'; import fs from 'fs'; @@ -287,7 +287,8 @@ export class AztecCheatCodes { * @param slot - The storage slot to lookup * @returns The notes stored at the given slot */ - public loadPrivate(owner: AztecAddress, contract: AztecAddress, slot: Fr | bigint): Promise { - return this.pxe.getPrivateStorageAt(owner, contract, new Fr(slot)); + public async loadPrivate(owner: AztecAddress, contract: AztecAddress, slot: Fr | bigint): Promise { + const extendedNotes = await this.pxe.getNotes({ owner, contractAddress: contract, storageSlot: new Fr(slot) }); + return extendedNotes.map(extendedNote => extendedNote.note); } } diff --git a/yarn-project/aztec.js/src/wallet/base_wallet.ts b/yarn-project/aztec.js/src/wallet/base_wallet.ts index 6b215ee3ea6..6003bdb2496 100644 --- a/yarn-project/aztec.js/src/wallet/base_wallet.ts +++ b/yarn-project/aztec.js/src/wallet/base_wallet.ts @@ -4,12 +4,13 @@ import { ContractData, DeployedContract, ExtendedContractData, + ExtendedNote, FunctionCall, GetUnencryptedLogsResponse, L2Tx, LogFilter, NodeInfo, - NotePreimage, + NoteFilter, PXE, SyncStatus, Tx, @@ -69,24 +70,17 @@ export abstract class BaseWallet implements Wallet { getTxReceipt(txHash: TxHash): Promise { return this.pxe.getTxReceipt(txHash); } - getPrivateStorageAt(owner: AztecAddress, contract: AztecAddress, storageSlot: Fr): Promise { - return this.pxe.getPrivateStorageAt(owner, contract, storageSlot); + getNotes(filter: NoteFilter): Promise { + return this.pxe.getNotes(filter); } getPublicStorageAt(contract: AztecAddress, storageSlot: Fr): Promise { return this.pxe.getPublicStorageAt(contract, storageSlot); } - addNote( - account: AztecAddress, - contract: AztecAddress, - storageSlot: Fr, - preimage: NotePreimage, - txHash: TxHash, - nonce?: Fr, - ): Promise { - return this.pxe.addNote(account, contract, storageSlot, preimage, txHash, nonce); - } - getNoteNonces(contract: AztecAddress, storageSlot: Fr, preimage: NotePreimage, txHash: TxHash): Promise { - return this.pxe.getNoteNonces(contract, storageSlot, preimage, txHash); + addNote(note: ExtendedNote): Promise { + return this.pxe.addNote(note); + } + getNoteNonces(note: ExtendedNote): Promise { + return this.pxe.getNoteNonces(note); } viewTx(functionName: string, args: any[], to: AztecAddress, from?: AztecAddress | undefined): Promise { return this.pxe.viewTx(functionName, args, to, from); diff --git a/yarn-project/boxes/token/src/tests/token.contract.test.ts b/yarn-project/boxes/token/src/tests/token.contract.test.ts index e0bda34fbfd..27d8bb5fef8 100644 --- a/yarn-project/boxes/token/src/tests/token.contract.test.ts +++ b/yarn-project/boxes/token/src/tests/token.contract.test.ts @@ -3,7 +3,7 @@ import { TokenSimulator } from './token_simulator.js'; import { AccountWallet, Fr, - NotePreimage, + Note, PXE, TxHash, TxStatus, @@ -15,6 +15,7 @@ import { } from '@aztec/aztec.js'; import { CompleteAddress } from '@aztec/circuits.js'; import { DebugLogger, createDebugLogger } from '@aztec/foundation/log'; +import { ExtendedNote } from '@aztec/types'; import { afterEach, beforeAll, expect, jest } from '@jest/globals'; // assumes sandbox is running locally, which this script does not trigger @@ -42,8 +43,9 @@ describe('e2e_token_contract', () => { const addPendingShieldNoteToPXE = async (accountIndex: number, amount: bigint, secretHash: Fr, txHash: TxHash) => { const storageSlot = new Fr(5); // The storage slot of `pending_shields` is 5. - const preimage = new NotePreimage([new Fr(amount), secretHash]); - await wallets[accountIndex].addNote(accounts[0].address, asset.address, storageSlot, preimage, txHash); + const note = new Note([new Fr(amount), secretHash]); + const extendedNote = new ExtendedNote(note, accounts[accountIndex].address, asset.address, storageSlot, txHash); + await wallets[accountIndex].addNote(extendedNote); }; beforeAll(async () => { diff --git a/yarn-project/circuits.js/src/utils/serialize.ts b/yarn-project/circuits.js/src/utils/serialize.ts index c8846079e29..c1c79ba0d30 100644 --- a/yarn-project/circuits.js/src/utils/serialize.ts +++ b/yarn-project/circuits.js/src/utils/serialize.ts @@ -1,7 +1,7 @@ /** * For serializing an array of fixed length buffers. * TODO move to foundation pkg. - * @param arr - Array of bufffers. + * @param arr - Array of buffers. * @returns The serialized buffers. */ export function serializeBufferArrayToVector(arr: Buffer[]): Buffer { @@ -29,7 +29,7 @@ type DeserializeFn = ( /** * Deserializes an array from a vector on an element-by-element basis. - * @param deserialize - A function used to deserialize each element of the vecotr. + * @param deserialize - A function used to deserialize each element of the vector. * @param vector - The vector to deserialize. * @param offset - The position in the vector to start deserializing from. * @returns Deserialized array and how many bytes we advanced by. @@ -174,7 +174,7 @@ export function serializeToBufferArray(...objs: Bufferable[]): Buffer[] { ret.push(boolToBuffer(obj)); } else if (typeof obj === 'number') { // Note: barretenberg assumes everything is big-endian - ret.push(numToUInt32BE(obj)); // TODO: Are we always passsing numbers as UInt32? + ret.push(numToUInt32BE(obj)); // TODO: Are we always passing numbers as UInt32? } else if (typeof obj === 'string') { ret.push(numToUInt32BE(obj.length)); ret.push(Buffer.from(obj)); diff --git a/yarn-project/cli/src/index.ts b/yarn-project/cli/src/index.ts index fa72b0dc1b6..2dd5ae41739 100644 --- a/yarn-project/cli/src/index.ts +++ b/yarn-project/cli/src/index.ts @@ -5,7 +5,7 @@ import { EthAddress, Fr, GrumpkinScalar, - NotePreimage, + Note, generatePublicKey, getSchnorrAccount, isContractDeployed, @@ -21,7 +21,7 @@ import { DebugLogger, LogFn } from '@aztec/foundation/log'; import { sleep } from '@aztec/foundation/sleep'; import { fileURLToPath } from '@aztec/foundation/url'; import { compileContract, generateNoirInterface, generateTypescriptInterface } from '@aztec/noir-compiler/cli'; -import { CompleteAddress, ContractData, LogFilter } from '@aztec/types'; +import { CompleteAddress, ContractData, ExtendedNote, LogFilter } from '@aztec/types'; import { createSecp256k1PeerId } from '@libp2p/peer-id-factory'; import { Command, Option } from 'commander'; @@ -585,12 +585,13 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command { .argument('', 'Aztec address of the contract.', parseAztecAddress) .argument('', 'The storage slot of the note.', parseField) .argument('', 'The tx hash of the tx containing the note.', parseTxHash) - .requiredOption('-p, --preimage [notePreimage...]', 'Note preimage.', []) + .requiredOption('-n, --note [note...]', 'The members of a Note serialized as hex strings.', []) .addOption(pxeOption) .action(async (address, contractAddress, storageSlot, txHash, options) => { - const preimage = new NotePreimage(parseFields(options.preimage)); + const note = new Note(parseFields(options.note)); + const extendedNote = new ExtendedNote(note, address, contractAddress, storageSlot, txHash); const client = await createCompatibleClient(options.rpcUrl, debugLogger); - await client.addNote(address, contractAddress, storageSlot, preimage, txHash); + await client.addNote(extendedNote); }); // Helper for users to decode hex strings into structs if needed. diff --git a/yarn-project/end-to-end/src/e2e_2_pxes.test.ts b/yarn-project/end-to-end/src/e2e_2_pxes.test.ts index ae644c66170..8039eeb8fc3 100644 --- a/yarn-project/end-to-end/src/e2e_2_pxes.test.ts +++ b/yarn-project/end-to-end/src/e2e_2_pxes.test.ts @@ -1,10 +1,10 @@ -import { AztecAddress, NotePreimage, Wallet, computeMessageSecretHash } from '@aztec/aztec.js'; +import { AztecAddress, Note, Wallet, computeMessageSecretHash } from '@aztec/aztec.js'; import { DebugLogger } from '@aztec/foundation/log'; import { retryUntil } from '@aztec/foundation/retry'; import { toBigInt } from '@aztec/foundation/serialize'; import { ChildContract, TokenContract } from '@aztec/noir-contracts/types'; import { EthAddress, Fr, PXEService } from '@aztec/pxe'; -import { AztecNode, CompleteAddress, PXE, TxStatus } from '@aztec/types'; +import { AztecNode, CompleteAddress, ExtendedNote, PXE, TxStatus } from '@aztec/types'; import { jest } from '@jest/globals'; @@ -97,8 +97,9 @@ describe('e2e_2_pxes', () => { expect(receipt.status).toEqual(TxStatus.MINED); const storageSlot = new Fr(5); - const preimage = new NotePreimage([new Fr(balance), secretHash]); - await pxe.addNote(recipient, contract.address, storageSlot, preimage, receipt.txHash); + const note = new Note([new Fr(balance), secretHash]); + const extendedNote = new ExtendedNote(note, recipient, contract.address, storageSlot, receipt.txHash); + await pxe.addNote(extendedNote); expect((await contract.methods.redeem_shield(recipient, balance, secret).send().wait()).status).toEqual( TxStatus.MINED, diff --git a/yarn-project/end-to-end/src/e2e_escrow_contract.test.ts b/yarn-project/end-to-end/src/e2e_escrow_contract.test.ts index f693a074c82..968a8a4b775 100644 --- a/yarn-project/end-to-end/src/e2e_escrow_contract.test.ts +++ b/yarn-project/end-to-end/src/e2e_escrow_contract.test.ts @@ -2,7 +2,7 @@ import { AccountWallet, AztecAddress, BatchCall, - NotePreimage, + Note, computeMessageSecretHash, generatePublicKey, } from '@aztec/aztec.js'; @@ -10,7 +10,7 @@ import { CompleteAddress, Fr, GrumpkinPrivateKey, GrumpkinScalar, getContractDep import { DebugLogger } from '@aztec/foundation/log'; import { EscrowContractArtifact } from '@aztec/noir-contracts/artifacts'; import { EscrowContract, TokenContract } from '@aztec/noir-contracts/types'; -import { PXE, PublicKey, TxStatus } from '@aztec/types'; +import { ExtendedNote, PXE, PublicKey, TxStatus } from '@aztec/types'; import { setup } from './fixtures/utils.js'; @@ -66,8 +66,9 @@ describe('e2e_escrow_contract', () => { const receipt = await token.methods.mint_private(mintAmount, secretHash).send().wait(); expect(receipt.status).toEqual(TxStatus.MINED); - const preimage = new NotePreimage([new Fr(mintAmount), secretHash]); - await pxe.addNote(escrowContract.address, token.address, pendingShieldsStorageSlot, preimage, receipt.txHash); + const note = new Note([new Fr(mintAmount), secretHash]); + const extendedNote = new ExtendedNote(note, owner, token.address, pendingShieldsStorageSlot, receipt.txHash); + await pxe.addNote(extendedNote); expect( (await token.methods.redeem_shield(escrowContract.address, mintAmount, secret).send().wait()).status, @@ -112,8 +113,9 @@ describe('e2e_escrow_contract', () => { const receipt = await token.methods.mint_private(mintAmount, secretHash).send().wait(); expect(receipt.status).toEqual(TxStatus.MINED); - const preimage = new NotePreimage([new Fr(mintAmount), secretHash]); - await pxe.addNote(owner, token.address, pendingShieldsStorageSlot, preimage, receipt.txHash); + const note = new Note([new Fr(mintAmount), secretHash]); + const extendedNote = new ExtendedNote(note, owner, token.address, pendingShieldsStorageSlot, receipt.txHash); + await pxe.addNote(extendedNote); expect((await token.methods.redeem_shield(owner, mintAmount, secret).send().wait()).status).toEqual(TxStatus.MINED); diff --git a/yarn-project/end-to-end/src/e2e_lending_contract.test.ts b/yarn-project/end-to-end/src/e2e_lending_contract.test.ts index 07a09a14558..9fe151d549a 100644 --- a/yarn-project/end-to-end/src/e2e_lending_contract.test.ts +++ b/yarn-project/end-to-end/src/e2e_lending_contract.test.ts @@ -9,7 +9,7 @@ import { import { CompleteAddress } from '@aztec/circuits.js'; import { DebugLogger } from '@aztec/foundation/log'; import { LendingContract, PriceFeedContract, TokenContract } from '@aztec/noir-contracts/types'; -import { NotePreimage, TxStatus } from '@aztec/types'; +import { ExtendedNote, Note, TxStatus } from '@aztec/types'; import { jest } from '@jest/globals'; @@ -120,9 +120,10 @@ describe('e2e_lending_contract', () => { await Promise.all([a, b].map(waitForSuccess)); const storageSlot = new Fr(5); - const preimage = new NotePreimage([new Fr(mintAmount), secretHash]); + const note = new Note([new Fr(mintAmount), secretHash]); const txHash = await b.getTxHash(); - await wallet.addNote(accounts[0].address, asset.address, storageSlot, preimage, txHash); + const extendedNote = new ExtendedNote(note, accounts[0].address, asset.address, storageSlot, txHash); + await wallet.addNote(extendedNote); await waitForSuccess(asset.methods.redeem_shield(lendingAccount.address, mintAmount, secret).send()); } diff --git a/yarn-project/end-to-end/src/e2e_multiple_accounts_1_enc_key.test.ts b/yarn-project/end-to-end/src/e2e_multiple_accounts_1_enc_key.test.ts index 85cca3baeb0..1222245d6ea 100644 --- a/yarn-project/end-to-end/src/e2e_multiple_accounts_1_enc_key.test.ts +++ b/yarn-project/end-to-end/src/e2e_multiple_accounts_1_enc_key.test.ts @@ -1,6 +1,6 @@ import { AztecAddress, - NotePreimage, + Note, Wallet, computeMessageSecretHash, generatePublicKey, @@ -10,7 +10,7 @@ import { import { Fr, GrumpkinScalar } from '@aztec/foundation/fields'; import { DebugLogger } from '@aztec/foundation/log'; import { TokenContract } from '@aztec/noir-contracts/types'; -import { AztecNode, CompleteAddress, PXE, TxStatus } from '@aztec/types'; +import { AztecNode, CompleteAddress, ExtendedNote, PXE, TxStatus } from '@aztec/types'; import { expectsNumOfEncryptedLogsInTheLastBlockToBe, setup } from './fixtures/utils.js'; @@ -75,8 +75,9 @@ describe('e2e_multiple_accounts_1_enc_key', () => { expect(receipt.status).toEqual(TxStatus.MINED); const storageSlot = new Fr(5); - const preimage = new NotePreimage([new Fr(initialBalance), secretHash]); - await pxe.addNote(accounts[0], token.address, storageSlot, preimage, receipt.txHash); + const note = new Note([new Fr(initialBalance), secretHash]); + const extendedNote = new ExtendedNote(note, accounts[0], token.address, storageSlot, receipt.txHash); + await pxe.addNote(extendedNote); expect((await token.methods.redeem_shield(accounts[0], initialBalance, secret).send().wait()).status).toEqual( TxStatus.MINED, diff --git a/yarn-project/end-to-end/src/e2e_sandbox_example.test.ts b/yarn-project/end-to-end/src/e2e_sandbox_example.test.ts index 76a3e1d14f4..0d1cb02d933 100644 --- a/yarn-project/end-to-end/src/e2e_sandbox_example.test.ts +++ b/yarn-project/end-to-end/src/e2e_sandbox_example.test.ts @@ -1,7 +1,7 @@ // docs:start:imports import { Fr, - NotePreimage, + Note, PXE, computeMessageSecretHash, createDebugLogger, @@ -12,6 +12,7 @@ import { } from '@aztec/aztec.js'; import { GrumpkinScalar } from '@aztec/circuits.js'; import { TokenContract } from '@aztec/noir-contracts/types'; +import { ExtendedNote } from '@aztec/types'; import { format } from 'util'; @@ -76,8 +77,8 @@ describe('e2e_sandbox_example', () => { // Add the newly created "pending shield" note to PXE const pendingShieldsStorageSlot = new Fr(5); // The storage slot of `pending_shields` is 5. - const preimage = new NotePreimage([new Fr(initialSupply), aliceSecretHash]); - await pxe.addNote(alice, contract.address, pendingShieldsStorageSlot, preimage, receipt.txHash); + const note = new Note([new Fr(initialSupply), aliceSecretHash]); + await pxe.addNote(new ExtendedNote(note, alice, contract.address, pendingShieldsStorageSlot, receipt.txHash)); // Make the tokens spendable by redeeming them using the secret (converts the "pending shield note" created above // to a "token note") @@ -142,8 +143,10 @@ describe('e2e_sandbox_example', () => { logger(`Minting ${mintQuantity} tokens to Bob...`); const mintPrivateReceipt = await tokenContractBob.methods.mint_private(mintQuantity, bobSecretHash).send().wait(); - const bobPendingShield = new NotePreimage([new Fr(mintQuantity), bobSecretHash]); - await pxe.addNote(bob, contract.address, pendingShieldsStorageSlot, bobPendingShield, mintPrivateReceipt.txHash); + const bobPendingShield = new Note([new Fr(mintQuantity), bobSecretHash]); + await pxe.addNote( + new ExtendedNote(bobPendingShield, bob, contract.address, pendingShieldsStorageSlot, mintPrivateReceipt.txHash), + ); await tokenContractBob.methods.redeem_shield(bob, mintQuantity, bobSecret).send().wait(); diff --git a/yarn-project/end-to-end/src/e2e_token_contract.test.ts b/yarn-project/end-to-end/src/e2e_token_contract.test.ts index 9fa8c8ce8ea..0d072e48969 100644 --- a/yarn-project/end-to-end/src/e2e_token_contract.test.ts +++ b/yarn-project/end-to-end/src/e2e_token_contract.test.ts @@ -1,6 +1,6 @@ import { AccountWallet, - NotePreimage, + Note, TxHash, TxStatus, computeAuthWitMessageHash, @@ -9,6 +9,7 @@ import { import { CompleteAddress, Fr, FunctionSelector } from '@aztec/circuits.js'; import { DebugLogger } from '@aztec/foundation/log'; import { TokenContract } from '@aztec/noir-contracts/types'; +import { ExtendedNote } from '@aztec/types'; import { jest } from '@jest/globals'; @@ -31,8 +32,9 @@ describe('e2e_token_contract', () => { const addPendingShieldNoteToPXE = async (accountIndex: number, amount: bigint, secretHash: Fr, txHash: TxHash) => { const storageSlot = new Fr(5); // The storage slot of `pending_shields` is 5. - const preimage = new NotePreimage([new Fr(amount), secretHash]); - await wallets[accountIndex].addNote(accounts[0].address, asset.address, storageSlot, preimage, txHash); + const note = new Note([new Fr(amount), secretHash]); + const extendedNote = new ExtendedNote(note, accounts[accountIndex].address, asset.address, storageSlot, txHash); + await wallets[accountIndex].addNote(extendedNote); }; beforeAll(async () => { @@ -168,9 +170,13 @@ describe('e2e_token_contract', () => { it('redeem as recipient', async () => { await addPendingShieldNoteToPXE(0, amount, secretHash, txHash); const txClaim = asset.methods.redeem_shield(accounts[0].address, amount, secret).send(); - const receiptClaim = await txClaim.wait(); + const receiptClaim = await txClaim.wait({ getNotes: true }); expect(receiptClaim.status).toBe(TxStatus.MINED); tokenSim.redeemShield(accounts[0].address, amount); + // 1 note should be created containing `amount` of tokens + const notes = receiptClaim.notes!; + expect(notes.length).toBe(1); + expect(notes[0].note.items[0].toBigInt()).toBe(amount); }); }); diff --git a/yarn-project/end-to-end/src/guides/dapp_testing.test.ts b/yarn-project/end-to-end/src/guides/dapp_testing.test.ts index b4a5c583b85..d4b87f6d81f 100644 --- a/yarn-project/end-to-end/src/guides/dapp_testing.test.ts +++ b/yarn-project/end-to-end/src/guides/dapp_testing.test.ts @@ -3,7 +3,7 @@ import { AccountWallet, CheatCodes, Fr, - NotePreimage, + Note, PXE, computeMessageSecretHash, createAccount, @@ -13,6 +13,7 @@ import { } from '@aztec/aztec.js'; import { toBigIntBE } from '@aztec/foundation/bigint-buffer'; import { TestContract, TokenContract } from '@aztec/noir-contracts/types'; +import { ExtendedNote } from '@aztec/types'; const { PXE_URL = 'http://localhost:8080', ETHEREUM_HOST = 'http://localhost:8545' } = process.env; @@ -47,8 +48,9 @@ describe('guides/dapp/testing', () => { const receipt = await token.methods.mint_private(mintAmount, secretHash).send().wait(); const storageSlot = new Fr(5); // The storage slot of `pending_shields` is 5. - const preimage = new NotePreimage([new Fr(mintAmount), secretHash]); - await pxe.addNote(recipientAddress, token.address, storageSlot, preimage, receipt.txHash); + const note = new Note([new Fr(mintAmount), secretHash]); + const extendedNote = new ExtendedNote(note, recipientAddress, token.address, storageSlot, receipt.txHash); + await pxe.addNote(extendedNote); await token.methods.redeem_shield(recipientAddress, mintAmount, secret).send().wait(); expect(await token.methods.balance_of_private(recipientAddress).view()).toEqual(20n); @@ -79,8 +81,9 @@ describe('guides/dapp/testing', () => { const receipt = await token.methods.mint_private(mintAmount, secretHash).send().wait(); const storageSlot = new Fr(5); - const preimage = new NotePreimage([new Fr(mintAmount), secretHash]); - await pxe.addNote(recipientAddress, token.address, storageSlot, preimage, receipt.txHash); + const note = new Note([new Fr(mintAmount), secretHash]); + const extendedNote = new ExtendedNote(note, recipientAddress, token.address, storageSlot, receipt.txHash); + await pxe.addNote(extendedNote); await token.methods.redeem_shield(recipientAddress, mintAmount, secret).send().wait(); expect(await token.methods.balance_of_private(recipientAddress).view()).toEqual(20n); @@ -132,8 +135,9 @@ describe('guides/dapp/testing', () => { const receipt = await token.methods.mint_private(100n, secretHash).send().wait(); const storageSlot = new Fr(5); - const preimage = new NotePreimage([new Fr(mintAmount), secretHash]); - await pxe.addNote(ownerAddress, token.address, storageSlot, preimage, receipt.txHash); + const note = new Note([new Fr(mintAmount), secretHash]); + const extendedNote = new ExtendedNote(note, ownerAddress, token.address, storageSlot, receipt.txHash); + await pxe.addNote(extendedNote); await token.methods.redeem_shield(ownerAddress, 100n, secret).send().wait(); @@ -146,8 +150,12 @@ describe('guides/dapp/testing', () => { it('checks private storage', async () => { // docs:start:private-storage - const notes = await pxe.getPrivateStorageAt(owner.getAddress(), token.address, ownerSlot); - const values = notes.map(note => note.items[0]); + const notes = await pxe.getNotes({ + owner: owner.getAddress(), + contractAddress: token.address, + storageSlot: ownerSlot, + }); + const values = notes.map(note => note.note.items[0]); const balance = values.reduce((sum, current) => sum + current.toBigInt(), 0n); expect(balance).toEqual(100n); // docs:end:private-storage @@ -249,8 +257,9 @@ describe('guides/dapp/testing', () => { const receipt = await token.methods.mint_private(mintAmount, secretHash).send().wait(); const storageSlot = new Fr(5); - const preimage = new NotePreimage([new Fr(mintAmount), secretHash]); - await pxe.addNote(recipientAddress, token.address, storageSlot, preimage, receipt.txHash); + const note = new Note([new Fr(mintAmount), secretHash]); + const extendedNote = new ExtendedNote(note, recipientAddress, token.address, storageSlot, receipt.txHash); + await pxe.addNote(extendedNote); await token.methods.redeem_shield(recipientAddress, mintAmount, secret).send().wait(); expect(await token.methods.balance_of_private(recipientAddress).view()).toEqual(20n); diff --git a/yarn-project/end-to-end/src/guides/up_quick_start.sh b/yarn-project/end-to-end/src/guides/up_quick_start.sh index 4f4d1926bc5..61d11961684 100755 --- a/yarn-project/end-to-end/src/guides/up_quick_start.sh +++ b/yarn-project/end-to-end/src/guides/up_quick_start.sh @@ -34,7 +34,7 @@ MINT_PRIVATE_TX_HASH=$(echo "$MINT_PRIVATE_OUTPUT" | grep "Transaction hash:" | aztec-cli add-note \ $ALICE $CONTRACT 5 $MINT_PRIVATE_TX_HASH \ - --preimage 1000 $SECRET_HASH + --note 1000 $SECRET_HASH aztec-cli send redeem_shield \ --args $ALICE 1000 $SECRET \ diff --git a/yarn-project/end-to-end/src/guides/writing_an_account_contract.test.ts b/yarn-project/end-to-end/src/guides/writing_an_account_contract.test.ts index f7c7c2a7ca1..e87c7af4970 100644 --- a/yarn-project/end-to-end/src/guides/writing_an_account_contract.test.ts +++ b/yarn-project/end-to-end/src/guides/writing_an_account_contract.test.ts @@ -4,13 +4,13 @@ import { BaseAccountContract, CompleteAddress, Fr, - NotePreimage, + Note, computeMessageSecretHash, } from '@aztec/aztec.js'; import { GrumpkinPrivateKey, GrumpkinScalar } from '@aztec/circuits.js'; import { Schnorr } from '@aztec/circuits.js/barretenberg'; import { SchnorrHardcodedAccountContractArtifact, TokenContract } from '@aztec/noir-contracts/types'; -import { AuthWitness } from '@aztec/types'; +import { AuthWitness, ExtendedNote } from '@aztec/types'; import { setup } from '../fixtures/utils.js'; @@ -71,8 +71,9 @@ describe('guides/writing_an_account_contract', () => { const receipt = await token.methods.mint_private(mintAmount, secretHash).send().wait(); const storageSlot = new Fr(5); - const preimage = new NotePreimage([new Fr(mintAmount), secretHash]); - await pxe.addNote(address, token.address, storageSlot, preimage, receipt.txHash); + const note = new Note([new Fr(mintAmount), secretHash]); + const extendedNote = new ExtendedNote(note, address, token.address, storageSlot, receipt.txHash); + await pxe.addNote(extendedNote); await token.methods.redeem_shield({ address }, mintAmount, secret).send().wait(); diff --git a/yarn-project/end-to-end/src/sample-dapp/index.mjs b/yarn-project/end-to-end/src/sample-dapp/index.mjs index 3aad3e254b9..de98301f581 100644 --- a/yarn-project/end-to-end/src/sample-dapp/index.mjs +++ b/yarn-project/end-to-end/src/sample-dapp/index.mjs @@ -1,7 +1,8 @@ import { + ExtendedNote, Fr, L2BlockL2Logs, - NotePreimage, + Note, computeMessageSecretHash, createPXEClient, getSandboxAccountsWallets, @@ -44,8 +45,9 @@ async function mintPrivateFunds(pxe) { const receipt = await token.methods.mint_private(mintAmount, secretHash).send().wait(); const storageSlot = new Fr(5); - const preimage = new NotePreimage([new Fr(mintAmount), secretHash]); - await pxe.addNote(owner.getAddress(), token.address, storageSlot, preimage, receipt.txHash); + const note = new Note([new Fr(mintAmount), secretHash]); + const extendedNote = new ExtendedNote(note, owner.getAddress(), token.address, storageSlot, receipt.txHash); + await pxe.addNote(extendedNote); await token.methods.redeem_shield(owner.getAddress(), mintAmount, secret).send().wait(); diff --git a/yarn-project/end-to-end/src/sample-dapp/index.test.mjs b/yarn-project/end-to-end/src/sample-dapp/index.test.mjs index 76b8f3019de..20bb40ad4b4 100644 --- a/yarn-project/end-to-end/src/sample-dapp/index.test.mjs +++ b/yarn-project/end-to-end/src/sample-dapp/index.test.mjs @@ -1,5 +1,5 @@ import { createSandbox } from '@aztec/aztec-sandbox'; -import { Contract, Fr, NotePreimage, computeMessageSecretHash, createAccount } from '@aztec/aztec.js'; +import { Contract, ExtendedNote, Fr, Note, computeMessageSecretHash, createAccount } from '@aztec/aztec.js'; import { TokenContractArtifact } from '@aztec/noir-contracts/artifacts'; describe('token', () => { @@ -18,8 +18,9 @@ describe('token', () => { const receipt = await token.methods.mint_private(initialBalance, secretHash).send().wait(); const storageSlot = new Fr(5); - const preimage = new NotePreimage([new Fr(initialBalance), secretHash]); - await pxe.addNote(owner.getAddress(), token.address, storageSlot, preimage, receipt.txHash); + const note = new Note([new Fr(initialBalance), secretHash]); + const extendedNote = new ExtendedNote(note, owner.getAddress(), token.address, storageSlot, receipt.txHash); + await pxe.addNote(extendedNote); await token.methods.redeem_shield({ address: owner.getAddress() }, initialBalance, secret).send().wait(); }, 120_000); diff --git a/yarn-project/end-to-end/src/shared/browser.ts b/yarn-project/end-to-end/src/shared/browser.ts index e876272feb7..95ded0f40c5 100644 --- a/yarn-project/end-to-end/src/shared/browser.ts +++ b/yarn-project/end-to-end/src/shared/browser.ts @@ -171,11 +171,12 @@ export const browserTestSuite = (setup: () => Server, pageLogger: AztecJs.DebugL const { GrumpkinScalar, DeployMethod, - createPXEClient: createPXEClient, + createPXEClient, getUnsafeSchnorrAccount, Contract, Fr, - NotePreimage, + ExtendedNote, + Note, computeMessageSecretHash, getSandboxAccountsWallets, } = window.AztecJs; @@ -202,8 +203,15 @@ export const browserTestSuite = (setup: () => Server, pageLogger: AztecJs.DebugL const mintPrivateReceipt = await token.methods.mint_private(initialBalance, secretHash).send().wait(); const storageSlot = new Fr(5); - const preimage = new NotePreimage([new Fr(initialBalance), secretHash]); - await pxe.addNote(ownerAddress, token.address, storageSlot, preimage, mintPrivateReceipt.txHash); + const note = new Note([new Fr(initialBalance), secretHash]); + const extendedNote = new ExtendedNote( + note, + ownerAddress, + token.address, + storageSlot, + mintPrivateReceipt.txHash, + ); + await pxe.addNote(extendedNote); await token.methods.redeem_shield(ownerAddress, initialBalance, secret).send().wait(); diff --git a/yarn-project/end-to-end/src/shared/cli.ts b/yarn-project/end-to-end/src/shared/cli.ts index 529cb8130b2..bcc500edc9f 100644 --- a/yarn-project/end-to-end/src/shared/cli.ts +++ b/yarn-project/end-to-end/src/shared/cli.ts @@ -142,7 +142,7 @@ export const cliTestSuite = ( const txHashes = findMultipleInLogs(/Transaction Hash: ([0-9a-f]{64})/i); const mintPrivateTxHash = txHashes[txHashes.length - 1][1]; await run( - `add-note ${ownerAddress} ${contractAddress} 5 ${mintPrivateTxHash} --preimage ${INITIAL_BALANCE} ${secretHash}`, + `add-note ${ownerAddress} ${contractAddress} 5 ${mintPrivateTxHash} --note ${INITIAL_BALANCE} ${secretHash}`, ); debug('Redeem tokens.'); diff --git a/yarn-project/end-to-end/src/shared/cross_chain_test_harness.ts b/yarn-project/end-to-end/src/shared/cross_chain_test_harness.ts index e76d58caab9..e2461928511 100644 --- a/yarn-project/end-to-end/src/shared/cross_chain_test_harness.ts +++ b/yarn-project/end-to-end/src/shared/cross_chain_test_harness.ts @@ -4,7 +4,7 @@ import { DebugLogger, EthAddress, Fr, - NotePreimage, + Note, PXE, TxHash, TxStatus, @@ -21,6 +21,7 @@ import { TokenPortalBytecode, } from '@aztec/l1-artifacts'; import { TokenBridgeContract, TokenContract } from '@aztec/noir-contracts/types'; +import { ExtendedNote } from '@aztec/types'; import { Account, Chain, HttpTransport, PublicClient, WalletClient, getContract, getFunctionSelector } from 'viem'; @@ -411,8 +412,9 @@ export class CrossChainTestHarness { async addPendingShieldNoteToPXE(shieldAmount: bigint, secretHash: Fr, txHash: TxHash) { this.logger('Adding note to PXE'); const storageSlot = new Fr(5); - const preimage = new NotePreimage([new Fr(shieldAmount), secretHash]); - await this.pxeService.addNote(this.ownerAddress, this.l2Token.address, storageSlot, preimage, txHash); + const note = new Note([new Fr(shieldAmount), secretHash]); + const extendedNote = new ExtendedNote(note, this.ownerAddress, this.l2Token.address, storageSlot, txHash); + await this.pxeService.addNote(extendedNote); } async redeemShieldPrivatelyOnL2(shieldAmount: bigint, secret: Fr) { diff --git a/yarn-project/p2p/src/service/known_txs.test.ts b/yarn-project/p2p/src/service/known_txs.test.ts index 1b9de9d570a..1f8345751f5 100644 --- a/yarn-project/p2p/src/service/known_txs.test.ts +++ b/yarn-project/p2p/src/service/known_txs.test.ts @@ -1,5 +1,4 @@ -import { randomBytes } from '@aztec/foundation/crypto'; -import { TxHash } from '@aztec/types'; +import { randomTxHash } from '@aztec/types'; import { expect } from '@jest/globals'; import { Ed25519PeerId, PeerId } from '@libp2p/interface-peer-id'; @@ -13,16 +12,12 @@ const createMockPeerId = (peerId: string): PeerId => { }); }; -const createTxHash = () => { - return new TxHash(randomBytes(32)); -}; - describe('Known Txs', () => { it('Returns false when a peer has not seen a tx', () => { const knownTxs = new KnownTxLookup(); const peer = createMockPeerId('Peer 1'); - const txHash = createTxHash(); + const txHash = randomTxHash(); expect(knownTxs.hasPeerSeenTx(peer, txHash.toString())).toEqual(false); }); @@ -32,7 +27,7 @@ describe('Known Txs', () => { const peer = createMockPeerId('Peer 1'); const peer2 = createMockPeerId('Peer 2'); - const txHash = createTxHash(); + const txHash = randomTxHash(); knownTxs.addPeerForTx(peer, txHash.toString()); diff --git a/yarn-project/p2p/src/service/tx_messages.test.ts b/yarn-project/p2p/src/service/tx_messages.test.ts index cdbc0dc6353..3d112a902d1 100644 --- a/yarn-project/p2p/src/service/tx_messages.test.ts +++ b/yarn-project/p2p/src/service/tx_messages.test.ts @@ -1,7 +1,6 @@ -import { Tx, TxHash, mockTx } from '@aztec/types'; +import { Tx, mockTx, randomTxHash } from '@aztec/types'; import { expect } from '@jest/globals'; -import { randomBytes } from 'crypto'; import { Messages, @@ -17,10 +16,6 @@ import { toTxMessage, } from './tx_messages.js'; -const makeTxHash = () => { - return new TxHash(randomBytes(32)); -}; - const verifyTx = (actual: Tx, expected: Tx) => { expect(actual.data!.toBuffer()).toEqual(expected.data?.toBuffer()); expect(actual.proof!.toBuffer()).toEqual(expected.proof!.toBuffer()); @@ -50,7 +45,7 @@ describe('Messages', () => { }); it('Correctly serializes and deserializes transaction hashes message', () => { - const txHashes = [makeTxHash(), makeTxHash(), makeTxHash()]; + const txHashes = [randomTxHash(), randomTxHash(), randomTxHash()]; const message = createTransactionHashesMessage(txHashes); expect(decodeMessageType(message)).toEqual(Messages.POOLED_TRANSACTION_HASHES); const decodedHashes = decodeTransactionHashesMessage(getEncodedMessage(message)); @@ -58,7 +53,7 @@ describe('Messages', () => { }); it('Correctly serializes and deserializes get transactions message', () => { - const txHashes = [makeTxHash(), makeTxHash(), makeTxHash()]; + const txHashes = [randomTxHash(), randomTxHash(), randomTxHash()]; const message = createGetTransactionsRequestMessage(txHashes); expect(decodeMessageType(message)).toEqual(Messages.GET_TRANSACTIONS); const decodedHashes = decodeGetTransactionsRequestMessage(getEncodedMessage(message)); diff --git a/yarn-project/pxe/src/database/database.ts b/yarn-project/pxe/src/database/database.ts index 8696d035c1c..dc99768ffcc 100644 --- a/yarn-project/pxe/src/database/database.ts +++ b/yarn-project/pxe/src/database/database.ts @@ -1,9 +1,9 @@ -import { CompleteAddress, HistoricBlockData } from '@aztec/circuits.js'; +import { CompleteAddress, HistoricBlockData, PublicKey } from '@aztec/circuits.js'; import { AztecAddress } from '@aztec/foundation/aztec-address'; import { Fr } from '@aztec/foundation/fields'; -import { ContractDatabase, MerkleTreeId, PublicKey } from '@aztec/types'; +import { ContractDatabase, MerkleTreeId, NoteFilter } from '@aztec/types'; -import { NoteSpendingInfoDao } from './note_spending_info_dao.js'; +import { NoteDao } from './note_dao.js'; /** * A database interface that provides methods for retrieving, adding, and removing transactional data related to Aztec @@ -25,46 +25,35 @@ export interface Database extends ContractDatabase { getAuthWitness(messageHash: Fr): Promise; /** - * Get auxiliary transaction data based on contract address and storage slot. - * It searches for matching NoteSpendingInfoDao objects in the MemoryDB's noteSpendingInfoTable - * where both the contractAddress and storageSlot properties match the given inputs. - * - * @param contract - The contract address. - * @param storageSlot - A Fr object representing the storage slot to search for in the auxiliary data. - * @returns An array of NoteSpendingInfoDao objects that fulfill the contract address and storage slot criteria. + * Gets notes based on the provided filter. + * @param filter - The filter to apply to the notes. + * @returns The requested notes. */ - getNoteSpendingInfo(contract: AztecAddress, storageSlot: Fr): Promise; + getNotes(filter: NoteFilter): Promise; /** - * Add a NoteSpendingInfoDao instance to the noteSpendingInfoTable. - * This function is used to store auxiliary data related to a transaction, - * such as contract address and storage slot, in the database. - * - * @param noteSpendingInfoDao - The NoteSpendingInfoDao instance containing the auxiliary data of a transaction. - * @returns A promise that resolves when the auxiliary data is added to the database. + * Adds a note to DB. + * @param note - The note to add. */ - addNoteSpendingInfo(noteSpendingInfoDao: NoteSpendingInfoDao): Promise; + addNote(note: NoteDao): Promise; /** - * Adds an array of NoteSpendingInfoDaos to the noteSpendingInfoTable. - * This function is used to insert multiple transaction auxiliary data objects into the database at once, + * Adds an array of notes to DB. + * This function is used to insert multiple notes to the database at once, * which can improve performance when dealing with large numbers of transactions. * - * @param noteSpendingInfoDaos - An array of NoteSpendingInfoDao instances representing the auxiliary data of transactions. - * @returns A Promise that resolves when all NoteSpendingInfoDaos have been successfully added to the noteSpendingInfoTable. + * @param notes - An array of notes. */ - addNoteSpendingInfoBatch(noteSpendingInfoDaos: NoteSpendingInfoDao[]): Promise; + addNotes(notes: NoteDao[]): Promise; /** - * Remove nullified transaction auxiliary data records associated with the given account and nullifiers. - * The function filters the records based on matching account and nullifier values, and updates the - * noteSpendingInfoTable with the remaining records. It returns an array of removed NoteSpendingInfoDao instances. + * Remove nullified notes associated with the given account and nullifiers. * * @param nullifiers - An array of Fr instances representing nullifiers to be matched. * @param account - A PublicKey instance representing the account for which the records are being removed. - * @returns A Promise resolved with an array of removed NoteSpendingInfoDao instances. + * @returns Removed notes. */ - removeNullifiedNoteSpendingInfo(nullifiers: Fr[], account: PublicKey): Promise; + removeNullifiedNotes(nullifiers: Fr[], account: PublicKey): Promise; /** * Retrieve the stored Merkle tree roots from the database. diff --git a/yarn-project/pxe/src/database/index.ts b/yarn-project/pxe/src/database/index.ts index 0e59d6da7cd..d1306dbafe0 100644 --- a/yarn-project/pxe/src/database/index.ts +++ b/yarn-project/pxe/src/database/index.ts @@ -1,3 +1,2 @@ export * from './database.js'; export * from './memory_db.js'; -export * from './note_spending_info_dao.js'; diff --git a/yarn-project/pxe/src/database/memory_db.test.ts b/yarn-project/pxe/src/database/memory_db.test.ts index d2536329eb6..077f705167a 100644 --- a/yarn-project/pxe/src/database/memory_db.test.ts +++ b/yarn-project/pxe/src/database/memory_db.test.ts @@ -1,7 +1,7 @@ import { AztecAddress, Fr } from '@aztec/circuits.js'; import { MemoryDB } from './memory_db.js'; -import { NoteSpendingInfoDao, createRandomNoteSpendingInfoDao } from './note_spending_info_dao.js'; +import { randomNoteDao } from './note_dao.test.js'; describe('Memory DB', () => { let db: MemoryDB; @@ -10,49 +10,53 @@ describe('Memory DB', () => { db = new MemoryDB(); }); - describe('NoteSpendingInfo', () => { + describe('NoteDao', () => { const contractAddress = AztecAddress.random(); const storageSlot = Fr.random(); - const createNote = (attributes: Partial = {}, sameStorage = true) => - createRandomNoteSpendingInfoDao({ - ...attributes, - contractAddress: sameStorage ? contractAddress : AztecAddress.random(), - storageSlot: sameStorage ? storageSlot : Fr.random(), - }); - const createNotes = (numberOfNotes: number, sameStorage = true) => Array(numberOfNotes) .fill(0) - .map(() => createNote({}, sameStorage)); + .map(() => + randomNoteDao({ + contractAddress: sameStorage ? contractAddress : AztecAddress.random(), + storageSlot: sameStorage ? storageSlot : Fr.random(), + }), + ); it('should add and get notes', async () => { const notes = createNotes(3, false); for (let i = 0; i < notes.length; ++i) { - await db.addNoteSpendingInfo(notes[i]); + await db.addNote(notes[i]); } for (let i = 0; i < notes.length; ++i) { - const result = await db.getNoteSpendingInfo(notes[i].contractAddress, notes[i].storageSlot); + const result = await db.getNotes({ + contractAddress: notes[i].contractAddress, + storageSlot: notes[i].storageSlot, + }); expect(result).toEqual([notes[i]]); } }); it('should batch add notes', async () => { const notes = createNotes(3, false); - await db.addNoteSpendingInfoBatch(notes); + await db.addNotes(notes); for (let i = 0; i < notes.length; ++i) { - const result = await db.getNoteSpendingInfo(notes[i].contractAddress, notes[i].storageSlot); + const result = await db.getNotes({ + contractAddress: notes[i].contractAddress, + storageSlot: notes[i].storageSlot, + }); expect(result).toEqual([notes[i]]); } }); it('should get all notes with the same contract storage slot', async () => { const notes = createNotes(3); - await db.addNoteSpendingInfoBatch(notes); + await db.addNotes(notes); - const result = await db.getNoteSpendingInfo(contractAddress, storageSlot); + const result = await db.getNotes({ contractAddress, storageSlot }); expect(result.length).toBe(notes.length); expect(result).toEqual(expect.objectContaining(notes)); }); diff --git a/yarn-project/pxe/src/database/memory_db.ts b/yarn-project/pxe/src/database/memory_db.ts index 6058ac8cc53..bd604249757 100644 --- a/yarn-project/pxe/src/database/memory_db.ts +++ b/yarn-project/pxe/src/database/memory_db.ts @@ -1,12 +1,12 @@ -import { CompleteAddress, HistoricBlockData } from '@aztec/circuits.js'; +import { CompleteAddress, HistoricBlockData, PublicKey } from '@aztec/circuits.js'; import { AztecAddress } from '@aztec/foundation/aztec-address'; import { Fr } from '@aztec/foundation/fields'; import { createDebugLogger } from '@aztec/foundation/log'; -import { MerkleTreeId, PublicKey } from '@aztec/types'; +import { MerkleTreeId, NoteFilter } from '@aztec/types'; import { MemoryContractDatabase } from '../contract_database/index.js'; import { Database } from './database.js'; -import { NoteSpendingInfoDao, getNoteSpendingInfoDaoSize } from './note_spending_info_dao.js'; +import { NoteDao } from './note_dao.js'; /** * The MemoryDB class provides an in-memory implementation of a database to manage transactions and auxiliary data. @@ -15,7 +15,7 @@ import { NoteSpendingInfoDao, getNoteSpendingInfoDaoSize } from './note_spending * As an in-memory database, the stored data will not persist beyond the life of the application instance. */ export class MemoryDB extends MemoryContractDatabase implements Database { - private noteSpendingInfoTable: NoteSpendingInfoDao[] = []; + private notesTable: NoteDao[] = []; private treeRoots: Record | undefined; private globalVariablesHash: Fr | undefined; private addresses: CompleteAddress[] = []; @@ -44,41 +44,51 @@ export class MemoryDB extends MemoryContractDatabase implements Database { return Promise.resolve(this.authWitnesses[messageHash.toString()]); } - public addNoteSpendingInfo(noteSpendingInfoDao: NoteSpendingInfoDao) { - this.noteSpendingInfoTable.push(noteSpendingInfoDao); + public addNote(note: NoteDao) { + this.notesTable.push(note); return Promise.resolve(); } - public addNoteSpendingInfoBatch(noteSpendingInfoDaos: NoteSpendingInfoDao[]) { - this.noteSpendingInfoTable.push(...noteSpendingInfoDaos); + public addNotes(notes: NoteDao[]) { + this.notesTable.push(...notes); return Promise.resolve(); } - public getNoteSpendingInfo(contract: AztecAddress, storageSlot: Fr) { - const res = this.noteSpendingInfoTable.filter( - noteSpendingInfo => - noteSpendingInfo.contractAddress.equals(contract) && - noteSpendingInfo.storageSlot.toBuffer().equals(storageSlot.toBuffer()), + public async getNotes(filter: NoteFilter): Promise { + let ownerPublicKey: PublicKey | undefined; + if (filter.owner !== undefined) { + const ownerCompleteAddress = await this.getCompleteAddress(filter.owner); + if (ownerCompleteAddress === undefined) { + throw new Error(`Owner ${filter.owner.toString()} not found in memory database`); + } + ownerPublicKey = ownerCompleteAddress.publicKey; + } + + return this.notesTable.filter( + note => + (filter.contractAddress == undefined || note.contractAddress.equals(filter.contractAddress)) && + (filter.txHash == undefined || note.txHash.equals(filter.txHash)) && + (filter.storageSlot == undefined || note.storageSlot.equals(filter.storageSlot!)) && + (ownerPublicKey == undefined || note.publicKey.equals(ownerPublicKey!)), ); - return Promise.resolve(res); } - public removeNullifiedNoteSpendingInfo(nullifiers: Fr[], account: PublicKey) { + public removeNullifiedNotes(nullifiers: Fr[], account: PublicKey) { const nullifierSet = new Set(nullifiers.map(nullifier => nullifier.toString())); - const [remaining, removed] = this.noteSpendingInfoTable.reduce( - (acc: [NoteSpendingInfoDao[], NoteSpendingInfoDao[]], noteSpendingInfo) => { - const nullifier = noteSpendingInfo.siloedNullifier.toString(); - if (noteSpendingInfo.publicKey.equals(account) && nullifierSet.has(nullifier)) { - acc[1].push(noteSpendingInfo); + const [remaining, removed] = this.notesTable.reduce( + (acc: [NoteDao[], NoteDao[]], note) => { + const nullifier = note.siloedNullifier.toString(); + if (note.publicKey.equals(account) && nullifierSet.has(nullifier)) { + acc[1].push(note); } else { - acc[0].push(noteSpendingInfo); + acc[0].push(note); } return acc; }, [[], []], ); - this.noteSpendingInfoTable = remaining; + this.notesTable = remaining; return Promise.resolve(removed); } @@ -146,7 +156,7 @@ export class MemoryDB extends MemoryContractDatabase implements Database { } public estimateSize() { - const notesSize = this.noteSpendingInfoTable.reduce((sum, note) => sum + getNoteSpendingInfoDaoSize(note), 0); + const notesSize = this.notesTable.reduce((sum, note) => sum + note.getSize(), 0); const treeRootsSize = this.treeRoots ? Object.entries(this.treeRoots).length * Fr.SIZE_IN_BYTES : 0; const authWits = Object.entries(this.authWitnesses); const authWitsSize = authWits.reduce((sum, [key, value]) => sum + key.length + value.length * Fr.SIZE_IN_BYTES, 0); diff --git a/yarn-project/pxe/src/database/note_dao.test.ts b/yarn-project/pxe/src/database/note_dao.test.ts new file mode 100644 index 00000000000..f4c22304a3f --- /dev/null +++ b/yarn-project/pxe/src/database/note_dao.test.ts @@ -0,0 +1,36 @@ +import { AztecAddress, Fr, Point } from '@aztec/circuits.js'; +import { Note, randomTxHash } from '@aztec/types'; + +import { NoteDao } from './note_dao.js'; + +export const randomNoteDao = ({ + note = Note.random(), + contractAddress = AztecAddress.random(), + txHash = randomTxHash(), + storageSlot = Fr.random(), + nonce = Fr.random(), + innerNoteHash = Fr.random(), + siloedNullifier = Fr.random(), + index = BigInt(0), + publicKey = Point.random(), +}: Partial = {}) => { + return new NoteDao( + note, + contractAddress, + storageSlot, + txHash, + nonce, + innerNoteHash, + siloedNullifier, + index, + publicKey, + ); +}; + +describe('Note DAO', () => { + it('convert to and from buffer', () => { + const note = randomNoteDao(); + const buf = note.toBuffer(); + expect(NoteDao.fromBuffer(buf)).toEqual(note); + }); +}); diff --git a/yarn-project/pxe/src/database/note_dao.ts b/yarn-project/pxe/src/database/note_dao.ts new file mode 100644 index 00000000000..9155e16536a --- /dev/null +++ b/yarn-project/pxe/src/database/note_dao.ts @@ -0,0 +1,90 @@ +import { AztecAddress, Fr, Point, PublicKey } from '@aztec/circuits.js'; +import { toBigIntBE, toBufferBE } from '@aztec/foundation/bigint-buffer'; +import { BufferReader, Note, TxHash } from '@aztec/types'; + +/** + * A note with contextual data. + */ +export class NoteDao { + constructor( + /** The note as emitted from the Noir contract. */ + public note: Note, + /** The contract address this note is created in. */ + public contractAddress: AztecAddress, + /** The specific storage location of the note on the contract. */ + public storageSlot: Fr, + /** The hash of the tx the note was created in. */ + public txHash: TxHash, + /** The nonce of the note. */ + public nonce: Fr, + /** + * Inner note hash of the note. This is customizable by the app circuit. + * We can use this value to compute siloedNoteHash and uniqueSiloedNoteHash. + */ + public innerNoteHash: Fr, + /** The nullifier of the note (siloed by contract address). */ + public siloedNullifier: Fr, + /** The location of the relevant note in the note hash tree. */ + public index: bigint, + /** The public key with which the note was encrypted. */ + public publicKey: PublicKey, + ) {} + + toBuffer(): Buffer { + return Buffer.concat([ + this.note.toBuffer(), + this.contractAddress.toBuffer(), + this.storageSlot.toBuffer(), + this.txHash.buffer, + this.nonce.toBuffer(), + this.innerNoteHash.toBuffer(), + this.siloedNullifier.toBuffer(), + toBufferBE(this.index, 32), + this.publicKey.toBuffer(), + ]); + } + static fromBuffer(buffer: Buffer | BufferReader) { + const reader = BufferReader.asReader(buffer); + + const note = Note.fromBuffer(reader); + const contractAddress = AztecAddress.fromBuffer(reader); + const storageSlot = Fr.fromBuffer(reader); + const txHash = new TxHash(reader.readBytes(TxHash.SIZE)); + const nonce = Fr.fromBuffer(reader); + const innerNoteHash = Fr.fromBuffer(reader); + const siloedNullifier = Fr.fromBuffer(reader); + const index = toBigIntBE(reader.readBytes(32)); + const publicKey = Point.fromBuffer(reader); + + return new NoteDao( + note, + contractAddress, + storageSlot, + txHash, + nonce, + innerNoteHash, + siloedNullifier, + index, + publicKey, + ); + } + + toString() { + return '0x' + this.toBuffer().toString('hex'); + } + + static fromString(str: string) { + const hex = str.replace(/^0x/, ''); + return NoteDao.fromBuffer(Buffer.from(hex, 'hex')); + } + + /** + * Returns the size in bytes of the Note Dao. + * @returns - Its size in bytes. + */ + public getSize() { + const indexSize = Math.ceil(Math.log2(Number(this.index))); + const noteSize = 4 + this.note.items.length * Fr.SIZE_IN_BYTES; + return noteSize + AztecAddress.SIZE_IN_BYTES + Fr.SIZE_IN_BYTES * 4 + TxHash.SIZE + Point.SIZE_IN_BYTES + indexSize; + } +} diff --git a/yarn-project/pxe/src/database/note_spending_info_dao.ts b/yarn-project/pxe/src/database/note_spending_info_dao.ts deleted file mode 100644 index daac960c7e6..00000000000 --- a/yarn-project/pxe/src/database/note_spending_info_dao.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { AztecAddress, Fr, PublicKey } from '@aztec/circuits.js'; -import { Point } from '@aztec/foundation/fields'; -import { NotePreimage } from '@aztec/types'; - -/** - * Represents the data access object for auxiliary transaction data. - * Contains properties from the decrypted note, computed properties, and information about - * the public key used for encryption, as well as the location of the data in the tree. - */ -export interface NoteSpendingInfoDao { - /** - * The contract address this note is created in. - */ - contractAddress: AztecAddress; - /** - * The nonce of the note. - */ - nonce: Fr; - /** - * The specific storage location of the note on the contract. - */ - storageSlot: Fr; - /** - * The preimage of the note, containing essential information about the note. - */ - notePreimage: NotePreimage; - /** - * Inner note hash of the note. This is customizable by the app circuit. - * We can use this value to compute siloedNoteHash and uniqueSiloedNoteHash. - */ - innerNoteHash: Fr; - /** - * The nullifier of the note (siloed by contract address). - */ - siloedNullifier: Fr; - /** - * The location of the relevant note in the note hash tree. - */ - index: bigint; - /** - * The public key that was used to encrypt the data. - */ - publicKey: PublicKey; -} - -export const createRandomNoteSpendingInfoDao = ({ - contractAddress = AztecAddress.random(), - nonce = Fr.random(), - storageSlot = Fr.random(), - notePreimage = NotePreimage.random(), - innerNoteHash = Fr.random(), - siloedNullifier = Fr.random(), - index = Fr.random().value, - publicKey = Point.random(), -}: Partial = {}): NoteSpendingInfoDao => ({ - contractAddress, - nonce, - storageSlot, - notePreimage, - innerNoteHash, - siloedNullifier, - index, - publicKey, -}); - -/** - * Returns the size in bytes of a note spending info dao. - * @param note - The note. - * @returns - Its size in bytes. - */ -export function getNoteSpendingInfoDaoSize(note: NoteSpendingInfoDao) { - // 7 fields + 1 bigint + 1 buffer size (4 bytes) + 1 buffer - const indexSize = Math.ceil(Math.log2(Number(note.index))); - return 7 * Fr.SIZE_IN_BYTES + indexSize + 4 + note.notePreimage.items.length * Fr.SIZE_IN_BYTES; -} diff --git a/yarn-project/pxe/src/kernel_prover/kernel_prover.test.ts b/yarn-project/pxe/src/kernel_prover/kernel_prover.test.ts index e44ea01cc78..7f1497b09f7 100644 --- a/yarn-project/pxe/src/kernel_prover/kernel_prover.test.ts +++ b/yarn-project/pxe/src/kernel_prover/kernel_prover.test.ts @@ -1,4 +1,4 @@ -import { ExecutionResult, NewNoteData } from '@aztec/acir-simulator'; +import { ExecutionResult, NoteAndSlot } from '@aztec/acir-simulator'; import { KernelCircuitPublicInputs, MAX_NEW_COMMITMENTS_PER_CALL, @@ -19,7 +19,7 @@ import { makeTxRequest } from '@aztec/circuits.js/factories'; import { AztecAddress } from '@aztec/foundation/aztec-address'; import { Fr } from '@aztec/foundation/fields'; import { Tuple } from '@aztec/foundation/serialize'; -import { FunctionL2Logs } from '@aztec/types'; +import { FunctionL2Logs, Note } from '@aztec/types'; import { mock } from 'jest-mock-extended'; @@ -34,23 +34,23 @@ describe('Kernel Prover', () => { let prover: KernelProver; let dependencies: { [name: string]: string[] } = {}; - const notes: NewNoteData[] = Array(10) + const notesAndSlots: NoteAndSlot[] = Array(10) .fill(null) .map(() => ({ - preimage: [Fr.random(), Fr.random(), Fr.random()], + note: new Note([Fr.random(), Fr.random(), Fr.random()]), storageSlot: Fr.random(), owner: { x: Fr.random(), y: Fr.random() }, })); const createFakeSiloedCommitment = (commitment: Fr) => new Fr(commitment.value + 1n); - const generateFakeCommitment = (note: NewNoteData) => note.preimage[0]; - const generateFakeSiloedCommitment = (note: NewNoteData) => createFakeSiloedCommitment(generateFakeCommitment(note)); + const generateFakeCommitment = (noteAndSlot: NoteAndSlot) => noteAndSlot.note.items[0]; + const generateFakeSiloedCommitment = (note: NoteAndSlot) => createFakeSiloedCommitment(generateFakeCommitment(note)); const createExecutionResult = (fnName: string, newNoteIndices: number[] = []): ExecutionResult => { const publicInputs = PrivateCircuitPublicInputs.empty(); publicInputs.newCommitments = makeTuple( MAX_NEW_COMMITMENTS_PER_CALL, - i => (i < newNoteIndices.length ? generateFakeCommitment(notes[newNoteIndices[i]]) : Fr.ZERO), + i => (i < newNoteIndices.length ? generateFakeCommitment(notesAndSlots[newNoteIndices[i]]) : Fr.ZERO), 0, ); return { @@ -58,7 +58,7 @@ describe('Kernel Prover', () => { callStackItem: new PrivateCallStackItem(AztecAddress.ZERO, fnName as any, publicInputs, false), nestedExecutions: (dependencies[fnName] || []).map(name => createExecutionResult(name)), vk: VerificationKey.makeFake().toBuffer(), - newNotes: newNoteIndices.map(idx => notes[idx]), + newNotes: newNoteIndices.map(idx => notesAndSlots[idx]), // TODO(dbanks12): should test kernel prover with non-transient reads. // This will be necessary once kernel actually checks (attempts to match) transient reads. readRequestPartialWitnesses: Array.from({ length: MAX_READ_REQUESTS_PER_CALL }, () => @@ -76,7 +76,7 @@ describe('Kernel Prover', () => { const createProofOutput = (newNoteIndices: number[]) => { const publicInputs = KernelCircuitPublicInputs.empty(); - const commitments = newNoteIndices.map(idx => generateFakeSiloedCommitment(notes[idx])); + const commitments = newNoteIndices.map(idx => generateFakeSiloedCommitment(notesAndSlots[idx])); // TODO(AD) FIXME(AD) This cast is bad. Why is this not the correct length when this is called? publicInputs.end.newCommitments = commitments as Tuple; return { @@ -103,7 +103,7 @@ describe('Kernel Prover', () => { const expectOutputNotes = (outputNotes: OutputNoteData[], expectedNoteIndices: number[]) => { expect(outputNotes.length).toBe(expectedNoteIndices.length); outputNotes.forEach((n, i) => { - expect(n.data).toEqual(notes[expectedNoteIndices[i]]); + expect(n.data).toEqual(notesAndSlots[expectedNoteIndices[i]]); }); }; diff --git a/yarn-project/pxe/src/kernel_prover/kernel_prover.ts b/yarn-project/pxe/src/kernel_prover/kernel_prover.ts index 2cb54b41071..96e2d49e47c 100644 --- a/yarn-project/pxe/src/kernel_prover/kernel_prover.ts +++ b/yarn-project/pxe/src/kernel_prover/kernel_prover.ts @@ -1,4 +1,4 @@ -import { ExecutionResult, NewNoteData } from '@aztec/acir-simulator'; +import { ExecutionResult, NoteAndSlot } from '@aztec/acir-simulator'; import { AztecAddress, CONTRACT_TREE_HEIGHT, @@ -42,7 +42,7 @@ export interface OutputNoteData { /** * The encrypted note data for an output note. */ - data: NewNoteData; + data: NoteAndSlot; /** * The unique value representing the note. */ @@ -231,11 +231,11 @@ export class KernelProver { /** * Retrieves the new output notes for a given execution result. - * The function maps over the new note preimages and associates them with their corresponding + * The function maps over the new notes and associates them with their corresponding * commitments in the public inputs of the execution result. It also includes the contract address * from the call context of the public inputs. * - * @param executionResult - The execution result object containing note preimages and public inputs. + * @param executionResult - The execution result object containing notes and public inputs. * @returns An array of OutputNoteData objects, each representing an output note with its associated data. */ private async getNewNotes(executionResult: ExecutionResult): Promise { diff --git a/yarn-project/pxe/src/note_processor/note_processor.test.ts b/yarn-project/pxe/src/note_processor/note_processor.test.ts index 00e1018b37c..ea13498b4f0 100644 --- a/yarn-project/pxe/src/note_processor/note_processor.test.ts +++ b/yarn-project/pxe/src/note_processor/note_processor.test.ts @@ -9,17 +9,19 @@ import { INITIAL_L2_BLOCK_NUM, KeyPair, KeyStore, + L1NotePayload, L2Block, L2BlockContext, L2BlockL2Logs, - NoteSpendingInfo, + Note, TxL2Logs, } from '@aztec/types'; import { jest } from '@jest/globals'; import { MockProxy, mock } from 'jest-mock-extended'; -import { Database, MemoryDB, NoteSpendingInfoDao } from '../database/index.js'; +import { Database, MemoryDB } from '../database/index.js'; +import { NoteDao } from '../database/note_dao.js'; import { NoteProcessor } from './note_processor.js'; const TXS_PER_BLOCK = 4; @@ -29,7 +31,7 @@ describe('Note Processor', () => { let grumpkin: Grumpkin; let database: Database; let aztecNode: ReturnType>; - let addNoteSpendingInfoBatchSpy: any; + let addNotesSpy: any; let noteProcessor: NoteProcessor; let owner: KeyPair; let keyStore: MockProxy; @@ -38,18 +40,18 @@ describe('Note Processor', () => { const numCommitmentsPerBlock = TXS_PER_BLOCK * MAX_NEW_COMMITMENTS_PER_TX; const firstBlockDataStartIndex = (firstBlockNum - 1) * numCommitmentsPerBlock; - const computeMockNoteHash = (preimage: Fr[]) => + const computeMockNoteHash = (note: Note) => Fr.fromBuffer( pedersenHashInputs( wasm, - preimage.map(p => p.toBuffer()), + note.items.map(i => i.toBuffer()), ), ); // ownedData: [tx1, tx2, ...], the numbers in each tx represents the indices of the note hashes the account owns. - const createEncryptedLogsAndOwnedNoteSpendingInfo = (ownedData: number[][], ownedNotes: NoteSpendingInfo[]) => { - const newNotes: NoteSpendingInfo[] = []; - const ownedNoteSpendingInfo: NoteSpendingInfo[] = []; + const createEncryptedLogsAndOwnedL1NotePayloads = (ownedData: number[][], ownedNotes: L1NotePayload[]) => { + const newNotes: L1NotePayload[] = []; + const ownedL1NotePayloads: L1NotePayload[] = []; const txLogs: TxL2Logs[] = []; let usedOwnedNote = 0; for (let i = 0; i < TXS_PER_BLOCK; ++i) { @@ -62,11 +64,11 @@ describe('Note Processor', () => { for (let noteIndex = 0; noteIndex < MAX_NEW_COMMITMENTS_PER_TX; ++noteIndex) { const isOwner = ownedDataIndices.includes(noteIndex); const publicKey = isOwner ? owner.getPublicKey() : Point.random(); - const note = (isOwner && ownedNotes[usedOwnedNote]) || NoteSpendingInfo.random(); + const note = (isOwner && ownedNotes[usedOwnedNote]) || L1NotePayload.random(); usedOwnedNote += note === ownedNotes[usedOwnedNote] ? 1 : 0; newNotes.push(note); if (isOwner) { - ownedNoteSpendingInfo.push(note); + ownedL1NotePayloads.push(note); } const log = note.toEncryptedBuffer(publicKey, grumpkin); // 1 tx containing 1 function invocation containing 1 log @@ -76,14 +78,14 @@ describe('Note Processor', () => { } const encryptedLogs = new L2BlockL2Logs(txLogs); - return { newNotes, ownedNoteSpendingInfo, encryptedLogs }; + return { newNotes, ownedL1NotePayloads, encryptedLogs }; }; const mockData = ( ownedData: number[][], prependedBlocks = 0, appendedBlocks = 0, - ownedNotes: NoteSpendingInfo[] = [], + ownedNotes: L1NotePayload[] = [], ) => { if (ownedData.length > TXS_PER_BLOCK) { throw new Error(`Tx size should be less than ${TXS_PER_BLOCK}.`); @@ -91,25 +93,26 @@ describe('Note Processor', () => { const blockContexts: L2BlockContext[] = []; const encryptedLogsArr: L2BlockL2Logs[] = []; - const ownedNoteSpendingInfos: NoteSpendingInfo[] = []; + const ownedL1NotePayloads: L1NotePayload[] = []; const numberOfBlocks = prependedBlocks + appendedBlocks + 1; for (let i = 0; i < numberOfBlocks; ++i) { const block = L2Block.random(firstBlockNum + i, TXS_PER_BLOCK); block.startNoteHashTreeSnapshot.nextAvailableLeafIndex = firstBlockDataStartIndex + i * numCommitmentsPerBlock; const isTargetBlock = i === prependedBlocks; - const { newNotes, encryptedLogs, ownedNoteSpendingInfo } = createEncryptedLogsAndOwnedNoteSpendingInfo( - isTargetBlock ? ownedData : [], - isTargetBlock ? ownedNotes : [], - ); + const { + newNotes, + encryptedLogs, + ownedL1NotePayloads: payloads, + } = createEncryptedLogsAndOwnedL1NotePayloads(isTargetBlock ? ownedData : [], isTargetBlock ? ownedNotes : []); encryptedLogsArr.push(encryptedLogs); - ownedNoteSpendingInfos.push(...ownedNoteSpendingInfo); - block.newCommitments = newNotes.map(n => computeMockNoteHash(n.notePreimage.items)); + ownedL1NotePayloads.push(...payloads); + block.newCommitments = newNotes.map(n => computeMockNoteHash(n.note)); const randomBlockContext = new L2BlockContext(block); blockContexts.push(randomBlockContext); } - return { blockContexts, encryptedLogsArr, ownedNoteSpendingInfos }; + return { blockContexts, encryptedLogsArr, ownedL1NotePayloads }; }; beforeAll(async () => { @@ -120,7 +123,7 @@ describe('Note Processor', () => { beforeEach(() => { database = new MemoryDB(); - addNoteSpendingInfoBatchSpy = jest.spyOn(database, 'addNoteSpendingInfoBatch'); + addNotesSpy = jest.spyOn(database, 'addNotes'); aztecNode = mock(); keyStore = mock(); @@ -146,17 +149,17 @@ describe('Note Processor', () => { }); afterEach(() => { - addNoteSpendingInfoBatchSpy.mockReset(); + addNotesSpy.mockReset(); }); it('should store a note that belongs to us', async () => { - const { blockContexts, encryptedLogsArr, ownedNoteSpendingInfos } = mockData([[2]]); + const { blockContexts, encryptedLogsArr, ownedL1NotePayloads } = mockData([[2]]); await noteProcessor.process(blockContexts, encryptedLogsArr); - expect(addNoteSpendingInfoBatchSpy).toHaveBeenCalledTimes(1); - expect(addNoteSpendingInfoBatchSpy).toHaveBeenCalledWith([ + expect(addNotesSpy).toHaveBeenCalledTimes(1); + expect(addNotesSpy).toHaveBeenCalledWith([ expect.objectContaining({ - ...ownedNoteSpendingInfos[0], + ...ownedL1NotePayloads[0], index: BigInt(firstBlockDataStartIndex + 2), }), ]); @@ -167,27 +170,27 @@ describe('Note Processor', () => { const appendedBlocks = 1; const thisBlockDataStartIndex = firstBlockDataStartIndex + prependedBlocks * numCommitmentsPerBlock; - const { blockContexts, encryptedLogsArr, ownedNoteSpendingInfos } = mockData( + const { blockContexts, encryptedLogsArr, ownedL1NotePayloads } = mockData( [[], [1], [], [0, 2]], prependedBlocks, appendedBlocks, ); await noteProcessor.process(blockContexts, encryptedLogsArr); - expect(addNoteSpendingInfoBatchSpy).toHaveBeenCalledTimes(1); - expect(addNoteSpendingInfoBatchSpy).toHaveBeenCalledWith([ + expect(addNotesSpy).toHaveBeenCalledTimes(1); + expect(addNotesSpy).toHaveBeenCalledWith([ expect.objectContaining({ - ...ownedNoteSpendingInfos[0], + ...ownedL1NotePayloads[0], // Index 1 log in the 2nd tx. index: BigInt(thisBlockDataStartIndex + MAX_NEW_COMMITMENTS_PER_TX * (2 - 1) + 1), }), expect.objectContaining({ - ...ownedNoteSpendingInfos[1], + ...ownedL1NotePayloads[1], // Index 0 log in the 4th tx. index: BigInt(thisBlockDataStartIndex + MAX_NEW_COMMITMENTS_PER_TX * (4 - 1) + 0), }), expect.objectContaining({ - ...ownedNoteSpendingInfos[2], + ...ownedL1NotePayloads[2], // Index 2 log in the 4th tx. index: BigInt(thisBlockDataStartIndex + MAX_NEW_COMMITMENTS_PER_TX * (4 - 1) + 2), }), @@ -199,30 +202,30 @@ describe('Note Processor', () => { await noteProcessor.process(blockContexts, encryptedLogsArr); }); - it('should be able to recover two notes with the same preimage', async () => { - const note = NoteSpendingInfo.random(); - const note2 = NoteSpendingInfo.random(); - // All notes expect one have the same contract address, storage slot, and preimage. + it('should be able to recover two note payloads with containing the same note', async () => { + const note = L1NotePayload.random(); + const note2 = L1NotePayload.random(); + // All note payloads except one have the same contract address, storage slot, and the actual note. const notes = [note, note, note, note2, note]; - const { blockContexts, encryptedLogsArr, ownedNoteSpendingInfos } = mockData([[0, 2], [], [0, 1, 3]], 0, 0, notes); + const { blockContexts, encryptedLogsArr, ownedL1NotePayloads } = mockData([[0, 2], [], [0, 1, 3]], 0, 0, notes); await noteProcessor.process(blockContexts, encryptedLogsArr); - const addedInfos: NoteSpendingInfoDao[] = addNoteSpendingInfoBatchSpy.mock.calls[0][0]; - expect(addedInfos).toEqual([ - expect.objectContaining({ ...ownedNoteSpendingInfos[0] }), - expect.objectContaining({ ...ownedNoteSpendingInfos[1] }), - expect.objectContaining({ ...ownedNoteSpendingInfos[2] }), - expect.objectContaining({ ...ownedNoteSpendingInfos[3] }), - expect.objectContaining({ ...ownedNoteSpendingInfos[4] }), + const addedNoteDaos: NoteDao[] = addNotesSpy.mock.calls[0][0]; + expect(addedNoteDaos.map(dao => dao)).toEqual([ + expect.objectContaining({ ...ownedL1NotePayloads[0] }), + expect.objectContaining({ ...ownedL1NotePayloads[1] }), + expect.objectContaining({ ...ownedL1NotePayloads[2] }), + expect.objectContaining({ ...ownedL1NotePayloads[3] }), + expect.objectContaining({ ...ownedL1NotePayloads[4] }), ]); - expect(ownedNoteSpendingInfos[0]).toEqual(ownedNoteSpendingInfos[1]); - expect(ownedNoteSpendingInfos[1]).toEqual(ownedNoteSpendingInfos[2]); - expect(ownedNoteSpendingInfos[2]).toEqual(ownedNoteSpendingInfos[4]); - expect(ownedNoteSpendingInfos[3]).not.toEqual(ownedNoteSpendingInfos[4]); + expect(ownedL1NotePayloads[0]).toEqual(ownedL1NotePayloads[1]); + expect(ownedL1NotePayloads[1]).toEqual(ownedL1NotePayloads[2]); + expect(ownedL1NotePayloads[2]).toEqual(ownedL1NotePayloads[4]); + expect(ownedL1NotePayloads[3]).not.toEqual(ownedL1NotePayloads[4]); // Check that every note has a different nonce. const nonceSet = new Set(); - addedInfos.forEach(info => nonceSet.add(info.nonce.value)); + addedNoteDaos.forEach(info => nonceSet.add(info.nonce.value)); expect(nonceSet.size).toBe(notes.length); }); }); diff --git a/yarn-project/pxe/src/note_processor/note_processor.ts b/yarn-project/pxe/src/note_processor/note_processor.ts index 823be99e7d3..44bb4c89a92 100644 --- a/yarn-project/pxe/src/note_processor/note_processor.ts +++ b/yarn-project/pxe/src/note_processor/note_processor.ts @@ -1,13 +1,14 @@ -import { CircuitsWasm, MAX_NEW_COMMITMENTS_PER_TX, MAX_NEW_NULLIFIERS_PER_TX } from '@aztec/circuits.js'; +import { CircuitsWasm, MAX_NEW_COMMITMENTS_PER_TX, MAX_NEW_NULLIFIERS_PER_TX, PublicKey } from '@aztec/circuits.js'; import { computeCommitmentNonce, siloNullifier } from '@aztec/circuits.js/abis'; import { Grumpkin } from '@aztec/circuits.js/barretenberg'; import { Fr } from '@aztec/foundation/fields'; import { createDebugLogger } from '@aztec/foundation/log'; import { Timer } from '@aztec/foundation/timer'; -import { AztecNode, KeyStore, L2BlockContext, L2BlockL2Logs, NoteSpendingInfo, PublicKey } from '@aztec/types'; +import { AztecNode, KeyStore, L1NotePayload, L2BlockContext, L2BlockL2Logs } from '@aztec/types'; import { NoteProcessorStats } from '@aztec/types/stats'; -import { Database, NoteSpendingInfoDao } from '../database/index.js'; +import { Database } from '../database/index.js'; +import { NoteDao } from '../database/note_dao.js'; import { getAcirSimulator } from '../simulator/index.js'; /** @@ -19,9 +20,9 @@ interface ProcessedData { */ blockContext: L2BlockContext; /** - * A collection of data access objects for note spending info. + * DAOs of processed notes. */ - noteSpendingInfoDaos: NoteSpendingInfoDao[]; + noteDaos: NoteDao[]; } /** @@ -92,22 +93,23 @@ export class NoteProcessor { return; } - const blocksAndNoteSpendingInfo: ProcessedData[] = []; + const blocksAndNotes: ProcessedData[] = []; const curve = await Grumpkin.new(); // Iterate over both blocks and encrypted logs. for (let blockIndex = 0; blockIndex < encryptedL2BlockLogs.length; ++blockIndex) { this.stats.blocks++; const { txLogs } = encryptedL2BlockLogs[blockIndex]; - const block = l2BlockContexts[blockIndex].block; + const blockContext = l2BlockContexts[blockIndex]; + const block = blockContext.block; const dataStartIndexForBlock = block.startNoteHashTreeSnapshot.nextAvailableLeafIndex; // We are using set for `userPertainingTxIndices` to avoid duplicates. This would happen in case there were // multiple encrypted logs in a tx pertaining to a user. - const noteSpendingInfoDaos: NoteSpendingInfoDao[] = []; + const noteDaos: NoteDao[] = []; const privateKey = await this.keyStore.getAccountPrivateKey(this.publicKey); - // Iterate over all the encrypted logs and try decrypting them. If successful, store the note spending info. + // Iterate over all the encrypted logs and try decrypting them. If successful, store the note. for (let indexOfTxInABlock = 0; indexOfTxInABlock < txLogs.length; ++indexOfTxInABlock) { this.stats.txs++; const dataStartIndexForTx = dataStartIndexForBlock + indexOfTxInABlock * MAX_NEW_COMMITMENTS_PER_TX; @@ -126,26 +128,31 @@ export class NoteProcessor { for (const functionLogs of txFunctionLogs) { for (const logs of functionLogs.logs) { this.stats.seen++; - const noteSpendingInfo = NoteSpendingInfo.fromEncryptedBuffer(logs, privateKey, curve); - if (noteSpendingInfo) { + const payload = L1NotePayload.fromEncryptedBuffer(logs, privateKey, curve); + if (payload) { // We have successfully decrypted the data. try { const { commitmentIndex, nonce, innerNoteHash, siloedNullifier } = await this.findNoteIndexAndNullifier( newCommitments, newNullifiers[0], - noteSpendingInfo, + payload, excludedIndices, ); const index = BigInt(dataStartIndexForTx + commitmentIndex); excludedIndices.add(commitmentIndex); - noteSpendingInfoDaos.push({ - ...noteSpendingInfo, - nonce, - innerNoteHash, - siloedNullifier, - index, - publicKey: this.publicKey, - }); + noteDaos.push( + new NoteDao( + payload.note, + payload.contractAddress, + payload.storageSlot, + blockContext.getTxHash(indexOfTxInABlock), + nonce, + innerNoteHash, + siloedNullifier, + index, + this.publicKey, + ), + ); this.stats.decrypted++; } catch (e) { this.stats.failed++; @@ -156,13 +163,13 @@ export class NoteProcessor { } } - blocksAndNoteSpendingInfo.push({ + blocksAndNotes.push({ blockContext: l2BlockContexts[blockIndex], - noteSpendingInfoDaos, + noteDaos, }); } - await this.processBlocksAndNoteSpendingInfo(blocksAndNoteSpendingInfo); + await this.processBlocksAndNotes(blocksAndNotes); this.syncedToBlock = l2BlockContexts[l2BlockContexts.length - 1].block.number; this.log(`Synched block ${this.syncedToBlock}`); @@ -173,20 +180,20 @@ export class NoteProcessor { * commitment for the current tx matches this value. * Compute the nullifier for a given transaction auxiliary data. * The nullifier is calculated using the private key of the account, - * contract address, and note preimage associated with the noteSpendingInfo. + * contract address, and the note associated with the l1NotePayload. * This method assists in identifying spent commitments in the private state. * @param commitments - Commitments in the tx. One of them should be the note's commitment. * @param firstNullifier - First nullifier in the tx. - * @param noteSpendingInfo - An instance of NoteSpendingInfo containing transaction details. + * @param l1NotePayload - An instance of l1NotePayload containing transaction details. * @param excludedIndices - Indices that have been assigned a note in the same tx. Notes in a tx can have the same - * NoteSpendingInfo. We need to find a different index for each replicate. + * l1NotePayload. We need to find a different index for each replicate. * @returns Information for a decrypted note, including the index of its commitment, nonce, inner note * hash, and the siloed nullifier. Throw if cannot find the nonce for the note. */ private async findNoteIndexAndNullifier( commitments: Fr[], firstNullifier: Fr, - { contractAddress, storageSlot, notePreimage }: NoteSpendingInfo, + { contractAddress, storageSlot, note }: L1NotePayload, excludedIndices: Set, ) { const wasm = await CircuitsWasm.get(); @@ -204,12 +211,7 @@ export class NoteProcessor { const expectedNonce = computeCommitmentNonce(wasm, firstNullifier, commitmentIndex); ({ innerNoteHash, siloedNoteHash, uniqueSiloedNoteHash, innerNullifier } = - await this.simulator.computeNoteHashAndNullifier( - contractAddress, - expectedNonce, - storageSlot, - notePreimage.items, - )); + await this.simulator.computeNoteHashAndNullifier(contractAddress, expectedNonce, storageSlot, note)); if (commitment.equals(uniqueSiloedNoteHash)) { nonce = expectedNonce; break; @@ -253,28 +255,28 @@ https://github.com/AztecProtocol/aztec-packages/issues/1641`; * transaction auxiliary data from the database. This function keeps track of new nullifiers * and ensures all other transactions are updated with newly settled block information. * - * @param blocksAndNoteSpendingInfo - Array of objects containing L2BlockContexts, user-pertaining transaction indices, and NoteSpendingInfoDaos. + * @param blocksAndNotes - Array of objects containing L2BlockContexts, user-pertaining transaction indices, and NoteDaos. */ - private async processBlocksAndNoteSpendingInfo(blocksAndNoteSpendingInfo: ProcessedData[]) { - const noteSpendingInfoDaosBatch = blocksAndNoteSpendingInfo.flatMap(b => b.noteSpendingInfoDaos); - if (noteSpendingInfoDaosBatch.length) { - await this.db.addNoteSpendingInfoBatch(noteSpendingInfoDaosBatch); - noteSpendingInfoDaosBatch.forEach(noteSpendingInfo => { + private async processBlocksAndNotes(blocksAndNotes: ProcessedData[]) { + const noteDaos = blocksAndNotes.flatMap(b => b.noteDaos); + if (noteDaos.length) { + await this.db.addNotes(noteDaos); + noteDaos.forEach(noteDao => { this.log( - `Added note spending info for contract ${noteSpendingInfo.contractAddress} at slot ${ - noteSpendingInfo.storageSlot - } with nullifier ${noteSpendingInfo.siloedNullifier.toString()}`, + `Added note for contract ${noteDao.contractAddress} at slot ${ + noteDao.storageSlot + } with nullifier ${noteDao.siloedNullifier.toString()}`, ); }); } - const newNullifiers: Fr[] = blocksAndNoteSpendingInfo.flatMap(b => b.blockContext.block.newNullifiers); - const removedNoteSpendingInfo = await this.db.removeNullifiedNoteSpendingInfo(newNullifiers, this.publicKey); - removedNoteSpendingInfo.forEach(noteSpendingInfo => { + const newNullifiers: Fr[] = blocksAndNotes.flatMap(b => b.blockContext.block.newNullifiers); + const removedNotes = await this.db.removeNullifiedNotes(newNullifiers, this.publicKey); + removedNotes.forEach(noteDao => { this.log( - `Removed note spending info for contract ${noteSpendingInfo.contractAddress} at slot ${ - noteSpendingInfo.storageSlot - } with nullifier ${noteSpendingInfo.siloedNullifier.toString()}`, + `Removed note for contract ${noteDao.contractAddress} at slot ${ + noteDao.storageSlot + } with nullifier ${noteDao.siloedNullifier.toString()}`, ); }); } diff --git a/yarn-project/pxe/src/pxe_http/pxe_http_server.ts b/yarn-project/pxe/src/pxe_http/pxe_http_server.ts index 64eb01c1f56..665fd47b648 100644 --- a/yarn-project/pxe/src/pxe_http/pxe_http_server.ts +++ b/yarn-project/pxe/src/pxe_http/pxe_http_server.ts @@ -7,12 +7,13 @@ import { CompleteAddress, ContractData, ExtendedContractData, + ExtendedNote, ExtendedUnencryptedL2Log, L2Block, L2BlockL2Logs, L2Tx, LogId, - NotePreimage, + Note, PXE, Tx, TxExecutionRequest, @@ -47,7 +48,8 @@ export function createPXERpcServer(pxeService: PXE): JsonRpcServer { Point, Fr, GrumpkinScalar, - NotePreimage, + Note, + ExtendedNote, AuthWitness, L2Block, L2Tx, diff --git a/yarn-project/pxe/src/pxe_service/pxe_service.ts b/yarn-project/pxe/src/pxe_service/pxe_service.ts index 16a7349a920..4938f6c02b4 100644 --- a/yarn-project/pxe/src/pxe_service/pxe_service.ts +++ b/yarn-project/pxe/src/pxe_service/pxe_service.ts @@ -30,6 +30,7 @@ import { ContractData, DeployedContract, ExtendedContractData, + ExtendedNote, FunctionCall, GetUnencryptedLogsResponse, KeyStore, @@ -38,7 +39,7 @@ import { LogFilter, MerkleTreeId, NodeInfo, - NotePreimage, + NoteFilter, PXE, SimulationError, Tx, @@ -55,6 +56,7 @@ import { import { PXEServiceConfig, getPackageInfo } from '../config/index.js'; import { ContractDataOracle } from '../contract_data_oracle/index.js'; import { Database } from '../database/index.js'; +import { NoteDao } from '../database/note_dao.js'; import { KernelOracle } from '../kernel_oracle/index.js'; import { KernelProver } from '../kernel_prover/kernel_prover.js'; import { getAcirSimulator } from '../simulator/index.js'; @@ -192,40 +194,40 @@ export class PXEService implements PXE { return await this.node.getPublicStorageAt(contract, storageSlot.value); } - public async getPrivateStorageAt(owner: AztecAddress, contract: AztecAddress, storageSlot: Fr) { - if ((await this.getContractData(contract)) === undefined) { - throw new Error(`Contract ${contract.toString()} is not deployed`); - } - const notes = await this.db.getNoteSpendingInfo(contract, storageSlot); - const ownerCompleteAddress = await this.db.getCompleteAddress(owner); - if (!ownerCompleteAddress) throw new Error(`Owner ${owner} not registered in PXE`); - const { publicKey: ownerPublicKey } = ownerCompleteAddress; - const ownerNotes = notes.filter(n => n.publicKey.equals(ownerPublicKey)); - return ownerNotes.map(n => n.notePreimage); - } - - public async addNote( - account: AztecAddress, - contractAddress: AztecAddress, - storageSlot: Fr, - preimage: NotePreimage, - txHash: TxHash, - nonce?: Fr, - ) { - const { publicKey } = (await this.db.getCompleteAddress(account)) ?? {}; + public async getNotes(filter: NoteFilter): Promise { + const noteDaos = await this.db.getNotes(filter); + + // TODO(benesjan): Refactor --> This type conversion is ugly but I decided to keep it this way for now because + // key derivation will affect all this + const extendedNotes = noteDaos.map(async dao => { + let owner = filter.owner; + if (owner === undefined) { + const completeAddresses = (await this.db.getCompleteAddresses()).find(address => + address.publicKey.equals(dao.publicKey), + ); + if (completeAddresses === undefined) { + throw new Error(`Cannot find complete address for public key ${dao.publicKey.toString()}`); + } + owner = completeAddresses.address; + } + return new ExtendedNote(dao.note, owner, dao.contractAddress, dao.storageSlot, dao.txHash); + }); + return Promise.all(extendedNotes); + } + + public async addNote(note: ExtendedNote) { + const { publicKey } = (await this.db.getCompleteAddress(note.owner)) ?? {}; if (!publicKey) { throw new Error('Unknown account.'); } + const [nonce] = await this.getNoteNonces(note); if (!nonce) { - [nonce] = await this.getNoteNonces(contractAddress, storageSlot, preimage, txHash); - } - if (!nonce) { - throw new Error(`Cannot find the note in tx: ${txHash}.`); + throw new Error(`Cannot find the note in tx: ${note.txHash}.`); } const { innerNoteHash, siloedNoteHash, uniqueSiloedNoteHash, innerNullifier } = - await this.simulator.computeNoteHashAndNullifier(contractAddress, nonce, storageSlot, preimage.items); + await this.simulator.computeNoteHashAndNullifier(note.contractAddress, nonce, note.storageSlot, note.note); // TODO(https://github.com/AztecProtocol/aztec-packages/issues/1386) // This can always be `uniqueSiloedNoteHash` once notes added from public also include nonces. @@ -236,33 +238,31 @@ export class PXEService implements PXE { } const wasm = await CircuitsWasm.get(); - const siloedNullifier = siloNullifier(wasm, contractAddress, innerNullifier!); + const siloedNullifier = siloNullifier(wasm, note.contractAddress, innerNullifier!); const nullifierIndex = await this.node.findLeafIndex(MerkleTreeId.NULLIFIER_TREE, siloedNullifier.toBuffer()); if (nullifierIndex !== undefined) { throw new Error('The note has been destroyed.'); } - await this.db.addNoteSpendingInfo({ - contractAddress, - storageSlot, - notePreimage: preimage, - nonce, - innerNoteHash, - siloedNullifier, - index, - publicKey, - }); + await this.db.addNote( + new NoteDao( + note.note, + note.contractAddress, + note.storageSlot, + note.txHash, + nonce, + innerNoteHash, + siloedNullifier, + index, + publicKey, + ), + ); } - public async getNoteNonces( - contractAddress: AztecAddress, - storageSlot: Fr, - preimage: NotePreimage, - txHash: TxHash, - ): Promise { - const tx = await this.node.getTx(txHash); + public async getNoteNonces(note: ExtendedNote): Promise { + const tx = await this.node.getTx(note.txHash); if (!tx) { - throw new Error(`Unknown tx: ${txHash}`); + throw new Error(`Unknown tx: ${note.txHash}`); } const wasm = await CircuitsWasm.get(); @@ -276,10 +276,10 @@ export class PXEService implements PXE { const nonce = computeCommitmentNonce(wasm, firstNullifier, i); const { siloedNoteHash, uniqueSiloedNoteHash } = await this.simulator.computeNoteHashAndNullifier( - contractAddress, + note.contractAddress, nonce, - storageSlot, - preimage.items, + note.storageSlot, + note.note, ); // TODO(https://github.com/AztecProtocol/aztec-packages/issues/1386) // Remove this once notes added from public also include nonces. diff --git a/yarn-project/pxe/src/simulator_oracle/index.ts b/yarn-project/pxe/src/simulator_oracle/index.ts index 415215ca964..fb2580b9cbe 100644 --- a/yarn-project/pxe/src/simulator_oracle/index.ts +++ b/yarn-project/pxe/src/simulator_oracle/index.ts @@ -45,19 +45,17 @@ export class SimulatorOracle implements DBOracle { } async getNotes(contractAddress: AztecAddress, storageSlot: Fr) { - const noteDaos = await this.db.getNoteSpendingInfo(contractAddress, storageSlot); - return noteDaos.map( - ({ contractAddress, storageSlot, nonce, notePreimage, innerNoteHash, siloedNullifier, index }) => ({ - contractAddress, - storageSlot, - nonce, - preimage: notePreimage.items, - innerNoteHash, - siloedNullifier, - // PXE can use this index to get full MembershipWitness - index, - }), - ); + const noteDaos = await this.db.getNotes({ contractAddress, storageSlot }); + return noteDaos.map(({ contractAddress, storageSlot, nonce, note, innerNoteHash, siloedNullifier, index }) => ({ + contractAddress, + storageSlot, + nonce, + note, + innerNoteHash, + siloedNullifier, + // PXE can use this index to get full MembershipWitness + index, + })); } async getFunctionArtifact( diff --git a/yarn-project/types/src/index.ts b/yarn-project/types/src/index.ts index 9d41e4dcb67..3ffc8125103 100644 --- a/yarn-project/types/src/index.ts +++ b/yarn-project/types/src/index.ts @@ -4,6 +4,7 @@ export * from './contract_database.js'; export * from './contract_data.js'; export * from './function_call.js'; export * from './keys/index.js'; +export * from './notes/index.js'; export * from './l1_to_l2_message.js'; export * from './l2_block.js'; export * from './l2_block_context.js'; diff --git a/yarn-project/types/src/interfaces/pxe.ts b/yarn-project/types/src/interfaces/pxe.ts index 24ef850105e..f52eb27273a 100644 --- a/yarn-project/types/src/interfaces/pxe.ts +++ b/yarn-project/types/src/interfaces/pxe.ts @@ -4,16 +4,17 @@ import { CompleteAddress, ContractData, ExtendedContractData, + ExtendedNote, GetUnencryptedLogsResponse, L2Tx, LogFilter, - NotePreimage, Tx, TxExecutionRequest, TxHash, TxReceipt, } from '@aztec/types'; +import { NoteFilter } from '../notes/note_filter.js'; import { DeployedContract } from './deployed-contract.js'; import { NodeInfo } from './node-info.js'; import { SyncStatus } from './sync-status.js'; @@ -148,19 +149,6 @@ export interface PXE { */ getTx(txHash: TxHash): Promise; - /** - * Retrieves the private storage data at a specified contract address and storage slot. Returns only data - * encrypted for the specified owner that has been already decrypted by the PXE Service. Note that there - * may be multiple notes for a user in a single slot. - * - * @param owner - The address for whom the private data is encrypted. - * @param contract - The AztecAddress of the target contract. - * @param storageSlot - The storage slot to be fetched. - * @returns A set of note preimages for the owner in that contract and slot. - * @throws If the contract is not deployed. - */ - getPrivateStorageAt(owner: AztecAddress, contract: AztecAddress, storageSlot: Fr): Promise; - /** * Retrieves the public storage data at a specified contract address and storage slot. * @@ -172,33 +160,26 @@ export interface PXE { getPublicStorageAt(contract: AztecAddress, storageSlot: Fr): Promise; /** - * Adds a note to the database. Throw if the note hash of the note doesn't exist in the tree. - * @param account - The account the note is associated with. - * @param contract - The contract address of the note. - * @param storageSlot - The storage slot of the note. - * @param preimage - The note preimage. - * @param txHash - The tx hash of the tx containing the note. - * @param nonce - The nonce of the note. If undefined, will look for the first index that matches the preimage. + * Gets notes based on the provided filter. + * @param filter - The filter to apply to the notes. + * @returns The requested notes. + */ + getNotes(filter: NoteFilter): Promise; + + /** + * Adds a note to the database. + * @throws If the note hash of the note doesn't exist in the tree. + * @param note - The note to add. */ - addNote( - account: AztecAddress, - contract: AztecAddress, - storageSlot: Fr, - preimage: NotePreimage, - txHash: TxHash, - nonce?: Fr, - ): Promise; + addNote(note: ExtendedNote): Promise; /** - * Finds the nonce(s) for a note in a tx with given preimage at a specified contract address and storage slot. - * @param contract - The contract address of the note. - * @param storageSlot - The storage slot of the note. - * @param preimage - The note preimage. - * @param txHash - The tx hash of the tx containing the note. + * Finds the nonce(s) for a given note. + * @param note - The note to find the nonces for. * @returns The nonces of the note. - * @remarks More than single nonce may be returned since there might be more than one note with the same preimage. + * @remarks More than a single nonce may be returned since there might be more than one nonce for a given note. */ - getNoteNonces(contract: AztecAddress, storageSlot: Fr, preimage: NotePreimage, txHash: TxHash): Promise; + getNoteNonces(note: ExtendedNote): Promise; /** * Simulate the execution of a view (read-only) function on a deployed contract without actually modifying state. diff --git a/yarn-project/types/src/l2_block_context.ts b/yarn-project/types/src/l2_block_context.ts index 0b0ab30b090..338d8102275 100644 --- a/yarn-project/types/src/l2_block_context.ts +++ b/yarn-project/types/src/l2_block_context.ts @@ -16,7 +16,7 @@ export class L2BlockContext { /** * Returns the tx hash of the tx in the block at the given index. - * @param txIndex - The index of the tx. + * @param txIndex - The index of the tx in the block. * @returns The tx's hash. */ public getTxHash(txIndex: number): TxHash { diff --git a/yarn-project/types/src/logs/index.ts b/yarn-project/types/src/logs/index.ts index a495dfb6a7b..98a84492c18 100644 --- a/yarn-project/types/src/logs/index.ts +++ b/yarn-project/types/src/logs/index.ts @@ -5,7 +5,7 @@ export * from './l2_logs_source.js'; export * from './log_id.js'; export * from './log_type.js'; export * from './log_filter.js'; -export * from './note_spending_info/index.js'; +export * from './l1_note_payload/index.js'; export * from './tx_l2_logs.js'; export * from './unencrypted_l2_log.js'; export * from './extended_unencrypted_l2_log.js'; diff --git a/yarn-project/types/src/logs/note_spending_info/browserify-cipher.d.ts b/yarn-project/types/src/logs/l1_note_payload/browserify-cipher.d.ts similarity index 100% rename from yarn-project/types/src/logs/note_spending_info/browserify-cipher.d.ts rename to yarn-project/types/src/logs/l1_note_payload/browserify-cipher.d.ts diff --git a/yarn-project/types/src/logs/note_spending_info/encrypt_buffer.test.ts b/yarn-project/types/src/logs/l1_note_payload/encrypt_buffer.test.ts similarity index 100% rename from yarn-project/types/src/logs/note_spending_info/encrypt_buffer.test.ts rename to yarn-project/types/src/logs/l1_note_payload/encrypt_buffer.test.ts diff --git a/yarn-project/types/src/logs/note_spending_info/encrypt_buffer.ts b/yarn-project/types/src/logs/l1_note_payload/encrypt_buffer.ts similarity index 100% rename from yarn-project/types/src/logs/note_spending_info/encrypt_buffer.ts rename to yarn-project/types/src/logs/l1_note_payload/encrypt_buffer.ts diff --git a/yarn-project/types/src/logs/l1_note_payload/index.ts b/yarn-project/types/src/logs/l1_note_payload/index.ts new file mode 100644 index 00000000000..48f97940993 --- /dev/null +++ b/yarn-project/types/src/logs/l1_note_payload/index.ts @@ -0,0 +1,3 @@ +export * from './encrypt_buffer.js'; +export * from './note.js'; +export * from './l1_note_payload.js'; diff --git a/yarn-project/types/src/logs/l1_note_payload/l1_note_payload.test.ts b/yarn-project/types/src/logs/l1_note_payload/l1_note_payload.test.ts new file mode 100644 index 00000000000..0c06f70eadf --- /dev/null +++ b/yarn-project/types/src/logs/l1_note_payload/l1_note_payload.test.ts @@ -0,0 +1,38 @@ +import { CircuitsWasm } from '@aztec/circuits.js'; +import { Grumpkin } from '@aztec/circuits.js/barretenberg'; +import { GrumpkinScalar, Point } from '@aztec/foundation/fields'; + +import { L1NotePayload } from './l1_note_payload.js'; + +describe('L1 Note Payload', () => { + let grumpkin: Grumpkin; + + beforeAll(async () => { + grumpkin = new Grumpkin(await CircuitsWasm.get()); + }); + + it('convert to and from buffer', () => { + const payload = L1NotePayload.random(); + const buf = payload.toBuffer(); + expect(L1NotePayload.fromBuffer(buf)).toEqual(payload); + }); + + it('convert to and from encrypted buffer', () => { + const payload = L1NotePayload.random(); + const ownerPrivKey = GrumpkinScalar.random(); + const ownerPubKey = grumpkin.mul(Grumpkin.generator, ownerPrivKey); + const encrypted = payload.toEncryptedBuffer(ownerPubKey, grumpkin); + const decrypted = L1NotePayload.fromEncryptedBuffer(encrypted, ownerPrivKey, grumpkin); + expect(decrypted).not.toBeUndefined(); + expect(decrypted).toEqual(payload); + }); + + it('return undefined if unable to decrypt the encrypted buffer', () => { + const payload = L1NotePayload.random(); + const ownerPubKey = Point.random(); + const encrypted = payload.toEncryptedBuffer(ownerPubKey, grumpkin); + const randomPrivKey = GrumpkinScalar.random(); + const decrypted = L1NotePayload.fromEncryptedBuffer(encrypted, randomPrivKey, grumpkin); + expect(decrypted).toBeUndefined(); + }); +}); diff --git a/yarn-project/types/src/logs/l1_note_payload/l1_note_payload.ts b/yarn-project/types/src/logs/l1_note_payload/l1_note_payload.ts new file mode 100644 index 00000000000..f543fc0cfc6 --- /dev/null +++ b/yarn-project/types/src/logs/l1_note_payload/l1_note_payload.ts @@ -0,0 +1,86 @@ +import { AztecAddress, GrumpkinPrivateKey, PublicKey } from '@aztec/circuits.js'; +import { Grumpkin } from '@aztec/circuits.js/barretenberg'; +import { serializeToBuffer } from '@aztec/circuits.js/utils'; +import { Fr, GrumpkinScalar } from '@aztec/foundation/fields'; +import { BufferReader } from '@aztec/foundation/serialize'; + +import { decryptBuffer, encryptBuffer } from './encrypt_buffer.js'; +import { Note } from './note.js'; + +/** + * A class which wraps note data which is pushed on L1. + * @remarks This data is required to compute a nullifier/to spend a note. Along with that this class contains + * the necessary functionality to encrypt and decrypt the data. + */ +export class L1NotePayload { + constructor( + /** + * A note as emitted from Noir contract. Can be used along with private key to compute nullifier. + */ + public note: Note, + /** + * Address of the contract this tx is interacting with. + */ + public contractAddress: AztecAddress, + /** + * Storage slot of the contract this tx is interacting with. + */ + public storageSlot: Fr, + ) {} + + /** + * Deserializes the L1NotePayload object from a Buffer. + * @param buffer - Buffer or BufferReader object to deserialize. + * @returns An instance of L1NotePayload. + */ + static fromBuffer(buffer: Buffer | BufferReader): L1NotePayload { + const reader = BufferReader.asReader(buffer); + return new L1NotePayload(reader.readObject(Note), reader.readObject(AztecAddress), reader.readFr()); + } + + /** + * Serializes the L1NotePayload object into a Buffer. + * @returns Buffer representation of the L1NotePayload object. + */ + toBuffer() { + return serializeToBuffer([this.note, this.contractAddress, this.storageSlot]); + } + + /** + * Encrypt the L1NotePayload object using the owner's public key and the ephemeral private key. + * @param ownerPubKey - Public key of the owner of the L1NotePayload object. + * @param curve - The curve instance to use. + * @returns The encrypted L1NotePayload object. + */ + public toEncryptedBuffer(ownerPubKey: PublicKey, curve: Grumpkin): Buffer { + const ephPrivKey: GrumpkinPrivateKey = GrumpkinScalar.random(); + return encryptBuffer(this.toBuffer(), ownerPubKey, ephPrivKey, curve); + } + + /** + * Decrypts the L1NotePayload object using the owner's private key. + * @param data - Encrypted L1NotePayload object. + * @param ownerPrivKey - Private key of the owner of the L1NotePayload object. + * @param curve - The curve instance to use. + * @returns Instance of L1NotePayload if the decryption was successful, undefined otherwise. + */ + static fromEncryptedBuffer( + data: Buffer, + ownerPrivKey: GrumpkinPrivateKey, + curve: Grumpkin, + ): L1NotePayload | undefined { + const buf = decryptBuffer(data, ownerPrivKey, curve); + if (!buf) { + return; + } + return L1NotePayload.fromBuffer(buf); + } + + /** + * Create a random L1NotePayload object (useful for testing purposes). + * @returns A random L1NotePayload object. + */ + static random() { + return new L1NotePayload(Note.random(), AztecAddress.random(), Fr.random()); + } +} diff --git a/yarn-project/types/src/logs/l1_note_payload/note.test.ts b/yarn-project/types/src/logs/l1_note_payload/note.test.ts new file mode 100644 index 00000000000..fd55db8121b --- /dev/null +++ b/yarn-project/types/src/logs/l1_note_payload/note.test.ts @@ -0,0 +1,12 @@ +import { Fr } from '@aztec/foundation/fields'; + +import { Note } from './note.js'; + +describe('note', () => { + it('convert to and from buffer', () => { + const fields = Array.from({ length: 5 }).map(() => Fr.random()); + const note = new Note(fields); + const buf = note.toBuffer(); + expect(Note.fromBuffer(buf)).toEqual(note); + }); +}); diff --git a/yarn-project/types/src/logs/l1_note_payload/note.ts b/yarn-project/types/src/logs/l1_note_payload/note.ts new file mode 100644 index 00000000000..6fdda129ec8 --- /dev/null +++ b/yarn-project/types/src/logs/l1_note_payload/note.ts @@ -0,0 +1,58 @@ +import { Vector } from '@aztec/circuits.js'; +import { Fr } from '@aztec/foundation/fields'; +import { BufferReader } from '@aztec/foundation/serialize'; + +/** + * The Note class represents a Note emitted from a Noir contract as a vector of Fr (finite field) elements. + * This data also represents a preimage to a note hash. This class extends the Vector class, which allows for + * additional operations on the underlying field elements. + */ +export class Note extends Vector { + /** + * Create a Note instance from a Buffer or BufferReader. + * The input 'buffer' can be either a Buffer containing the serialized Fr elements or a BufferReader instance. + * This function reads the Fr elements in the buffer and constructs a Note with them. + * + * @param buffer - The Buffer or BufferReader containing the serialized Fr elements. + * @returns A Note instance containing the deserialized Fr elements. + */ + static fromBuffer(buffer: Buffer | BufferReader) { + const reader = BufferReader.asReader(buffer); + return new Note(reader.readVector(Fr)); + } + + /** + * Generates a random Note instance with a variable number of items. + * The number of items is determined by a random value between 1 and 10 (inclusive). + * Each item in the Note is generated using the Fr.random() method. + * + * @returns A randomly generated Note instance. + */ + static random() { + const numItems = Math.floor(Math.random() * 10) + 1; + const items = Array.from({ length: numItems }, () => Fr.random()); + return new Note(items); + } + + /** + * Returns a hex representation of the note. + * @returns A hex string with the vector length as first element. + */ + toString() { + return '0x' + this.toBuffer().toString('hex'); + } + + /** + * Creates a new Note instance from a hex string. + * @param str - Hex representation. + * @returns A Note instance. + */ + static fromString(str: string) { + const hex = str.replace(/^0x/, ''); + return Note.fromBuffer(Buffer.from(hex, 'hex')); + } + + get length() { + return this.items.length; + } +} diff --git a/yarn-project/types/src/logs/note_spending_info/index.ts b/yarn-project/types/src/logs/note_spending_info/index.ts deleted file mode 100644 index 532086ccee2..00000000000 --- a/yarn-project/types/src/logs/note_spending_info/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './encrypt_buffer.js'; -export * from './note_preimage.js'; -export * from './note_spending_info.js'; diff --git a/yarn-project/types/src/logs/note_spending_info/note_preimage.test.ts b/yarn-project/types/src/logs/note_spending_info/note_preimage.test.ts deleted file mode 100644 index dfed1ca38f8..00000000000 --- a/yarn-project/types/src/logs/note_spending_info/note_preimage.test.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Fr } from '@aztec/foundation/fields'; - -import { NotePreimage } from './note_preimage.js'; - -describe('note_preimage', () => { - it('convert to and from buffer', () => { - const fields = Array.from({ length: 5 }).map(() => Fr.random()); - const notePreimage = new NotePreimage(fields); - const buf = notePreimage.toBuffer(); - expect(NotePreimage.fromBuffer(buf)).toEqual(notePreimage); - }); -}); diff --git a/yarn-project/types/src/logs/note_spending_info/note_preimage.ts b/yarn-project/types/src/logs/note_spending_info/note_preimage.ts deleted file mode 100644 index 3d849147984..00000000000 --- a/yarn-project/types/src/logs/note_spending_info/note_preimage.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { Vector } from '@aztec/circuits.js'; -import { Fr } from '@aztec/foundation/fields'; -import { BufferReader } from '@aztec/foundation/serialize'; - -/** - * The NotePreimage class represents a vector of Fr (finite field) elements, used for constructing - * and manipulating preimages of zk-SNARK commitments in the AZTEC protocol. This class provides - * methods to create a NotePreimage instance from a buffer or generate a random one. It extends - * the Vector class, which allows for additional operations on the underlying field elements. - */ -export class NotePreimage extends Vector { - /** - * Create a NotePreimage instance from a Buffer or BufferReader. - * The input 'buffer' can be either a Buffer containing the serialized Fr elements or a BufferReader instance. - * This function reads the Fr elements in the buffer and constructs a NotePreimage with them. - * - * @param buffer - The Buffer or BufferReader containing the serialized Fr elements. - * @returns A NotePreimage instance containing the deserialized Fr elements. - */ - static fromBuffer(buffer: Buffer | BufferReader) { - const reader = BufferReader.asReader(buffer); - return new NotePreimage(reader.readVector(Fr)); - } - - /** - * Generates a random NotePreimage instance with a variable number of items. - * The number of items is determined by a random value between 1 and 10 (inclusive). - * Each item in the NotePreimage is generated using the Fr.random() method. - * - * @returns A randomly generated NotePreimage instance. - */ - static random() { - const numItems = Math.floor(Math.random() * 10) + 1; - const items = Array.from({ length: numItems }, () => Fr.random()); - return new NotePreimage(items); - } - - /** - * Returns a hex representation of this preimage. - * @returns A hex string with the vector length as first element. - */ - toString() { - return '0x' + this.toBuffer().toString('hex'); - } - - /** - * Creates a new NotePreimage instance from a hex string. - * @param str - Hex representation. - * @returns A NotePreimage instance. - */ - static fromString(str: string) { - const hex = str.replace(/^0x/, ''); - return NotePreimage.fromBuffer(Buffer.from(hex, 'hex')); - } -} diff --git a/yarn-project/types/src/logs/note_spending_info/note_spending_info.test.ts b/yarn-project/types/src/logs/note_spending_info/note_spending_info.test.ts deleted file mode 100644 index 9dac462c946..00000000000 --- a/yarn-project/types/src/logs/note_spending_info/note_spending_info.test.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { CircuitsWasm } from '@aztec/circuits.js'; -import { Grumpkin } from '@aztec/circuits.js/barretenberg'; -import { GrumpkinScalar, Point } from '@aztec/foundation/fields'; - -import { NoteSpendingInfo } from './note_spending_info.js'; - -describe('note_spending_info', () => { - let grumpkin: Grumpkin; - - beforeAll(async () => { - grumpkin = new Grumpkin(await CircuitsWasm.get()); - }); - - it('convert to and from buffer', () => { - const noteSpendingInfo = NoteSpendingInfo.random(); - const buf = noteSpendingInfo.toBuffer(); - expect(NoteSpendingInfo.fromBuffer(buf)).toEqual(noteSpendingInfo); - }); - - it('convert to and from encrypted buffer', () => { - const noteSpendingInfo = NoteSpendingInfo.random(); - const ownerPrivKey = GrumpkinScalar.random(); - const ownerPubKey = grumpkin.mul(Grumpkin.generator, ownerPrivKey); - const encrypted = noteSpendingInfo.toEncryptedBuffer(ownerPubKey, grumpkin); - const decrypted = NoteSpendingInfo.fromEncryptedBuffer(encrypted, ownerPrivKey, grumpkin); - expect(decrypted).not.toBeUndefined(); - expect(decrypted).toEqual(noteSpendingInfo); - }); - - it('return undefined if unable to decrypt the encrypted buffer', () => { - const noteSpendingInfo = NoteSpendingInfo.random(); - const ownerPubKey = Point.random(); - const encrypted = noteSpendingInfo.toEncryptedBuffer(ownerPubKey, grumpkin); - const randomPrivKey = GrumpkinScalar.random(); - const decrypted = NoteSpendingInfo.fromEncryptedBuffer(encrypted, randomPrivKey, grumpkin); - expect(decrypted).toBeUndefined(); - }); -}); diff --git a/yarn-project/types/src/logs/note_spending_info/note_spending_info.ts b/yarn-project/types/src/logs/note_spending_info/note_spending_info.ts deleted file mode 100644 index e45c25562d8..00000000000 --- a/yarn-project/types/src/logs/note_spending_info/note_spending_info.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { AztecAddress, GrumpkinPrivateKey, PublicKey } from '@aztec/circuits.js'; -import { Grumpkin } from '@aztec/circuits.js/barretenberg'; -import { serializeToBuffer } from '@aztec/circuits.js/utils'; -import { Fr, GrumpkinScalar } from '@aztec/foundation/fields'; -import { BufferReader } from '@aztec/foundation/serialize'; - -import { decryptBuffer, encryptBuffer } from './encrypt_buffer.js'; -import { NotePreimage } from './note_preimage.js'; - -/** - * A class which wraps the data required to compute a nullifier/to spend a note. Along with that this class contains - * the necessary functionality to encrypt and decrypt the data. - */ -export class NoteSpendingInfo { - constructor( - /** - * Preimage which can be used along with private key to compute nullifier. - */ - public notePreimage: NotePreimage, - /** - * Address of the contract this tx is interacting with. - */ - public contractAddress: AztecAddress, - /** - * Storage slot of the contract this tx is interacting with. - */ - public storageSlot: Fr, - ) {} - - /** - * Deserializes the NoteSpendingInfo object from a Buffer. - * @param buffer - Buffer or BufferReader object to deserialize. - * @returns An instance of NoteSpendingInfo. - */ - static fromBuffer(buffer: Buffer | BufferReader): NoteSpendingInfo { - const reader = BufferReader.asReader(buffer); - return new NoteSpendingInfo(reader.readObject(NotePreimage), reader.readObject(AztecAddress), reader.readFr()); - } - - /** - * Serializes the NoteSpendingInfo object into a Buffer. - * @returns Buffer representation of the NoteSpendingInfo object. - */ - toBuffer() { - return serializeToBuffer([this.notePreimage, this.contractAddress, this.storageSlot]); - } - - /** - * Encrypt the NoteSpendingInfo object using the owner's public key and the ephemeral private key. - * @param ownerPubKey - Public key of the owner of the NoteSpendingInfo object. - * @param curve - The curve instance to use. - * @returns The encrypted NoteSpendingInfo object. - */ - public toEncryptedBuffer(ownerPubKey: PublicKey, curve: Grumpkin): Buffer { - const ephPrivKey: GrumpkinPrivateKey = GrumpkinScalar.random(); - return encryptBuffer(this.toBuffer(), ownerPubKey, ephPrivKey, curve); - } - - /** - * Decrypts the NoteSpendingInfo object using the owner's private key. - * @param data - Encrypted NoteSpendingInfo object. - * @param ownerPrivKey - Private key of the owner of the NoteSpendingInfo object. - * @param curve - The curve instance to use. - * @returns Instance of NoteSpendingInfo if the decryption was successful, undefined otherwise. - */ - static fromEncryptedBuffer( - data: Buffer, - ownerPrivKey: GrumpkinPrivateKey, - curve: Grumpkin, - ): NoteSpendingInfo | undefined { - const buf = decryptBuffer(data, ownerPrivKey, curve); - if (!buf) { - return; - } - return NoteSpendingInfo.fromBuffer(buf); - } - - /** - * Create a random NoteSpendingInfo object (useful for testing purposes). - * @returns A random NoteSpendingInfo object. - */ - static random() { - return new NoteSpendingInfo(NotePreimage.random(), AztecAddress.random(), Fr.random()); - } -} diff --git a/yarn-project/types/src/mocks.ts b/yarn-project/types/src/mocks.ts index 3fded6d748d..459c436afff 100644 --- a/yarn-project/types/src/mocks.ts +++ b/yarn-project/types/src/mocks.ts @@ -1,6 +1,8 @@ import { + AztecAddress, CompleteAddress, EthAddress, + Fr, MAX_NEW_CONTRACTS_PER_TX, MAX_PUBLIC_CALL_STACK_LENGTH_PER_TX, Proof, @@ -12,8 +14,8 @@ import { Tuple } from '@aztec/foundation/serialize'; import times from 'lodash.times'; -import { DeployedContract, ExtendedContractData, FunctionL2Logs, TxL2Logs } from './index.js'; -import { Tx } from './tx/index.js'; +import { DeployedContract, ExtendedContractData, ExtendedNote, FunctionL2Logs, Note, TxL2Logs } from './index.js'; +import { Tx, TxHash } from './tx/index.js'; /** * Testing utility to create empty logs composed from a single empty log. @@ -23,6 +25,8 @@ export function makeEmptyLogs(): TxL2Logs { return new TxL2Logs(functionLogs); } +export const randomTxHash = (): TxHash => new TxHash(randomBytes(32)); + export const mockTx = (seed = 1) => { return new Tx( makePrivateKernelPublicInputsFinal(seed), @@ -48,3 +52,13 @@ export const randomDeployedContract = async (): Promise => ({ completeAddress: await CompleteAddress.random(), portalContract: EthAddress.random(), }); + +export const randomExtendedNote = ({ + note = Note.random(), + owner = AztecAddress.random(), + contractAddress = AztecAddress.random(), + txHash = randomTxHash(), + storageSlot = Fr.random(), +}: Partial = {}) => { + return new ExtendedNote(note, owner, contractAddress, storageSlot, txHash); +}; diff --git a/yarn-project/types/src/notes/extended_note.test.ts b/yarn-project/types/src/notes/extended_note.test.ts new file mode 100644 index 00000000000..0b5f8c89edb --- /dev/null +++ b/yarn-project/types/src/notes/extended_note.test.ts @@ -0,0 +1,10 @@ +import { randomExtendedNote } from '../mocks.js'; +import { ExtendedNote } from './extended_note.js'; + +describe('Extended Note', () => { + it('convert to and from buffer', () => { + const extendedNote = randomExtendedNote(); + const buf = extendedNote.toBuffer(); + expect(ExtendedNote.fromBuffer(buf)).toEqual(extendedNote); + }); +}); diff --git a/yarn-project/types/src/notes/extended_note.ts b/yarn-project/types/src/notes/extended_note.ts new file mode 100644 index 00000000000..1b39068802e --- /dev/null +++ b/yarn-project/types/src/notes/extended_note.ts @@ -0,0 +1,50 @@ +import { AztecAddress, Fr } from '@aztec/circuits.js'; +import { BufferReader, Note, TxHash } from '@aztec/types'; + +/** + * A note with contextual data. + */ +export class ExtendedNote { + constructor( + /** The note as emitted from the Noir contract. */ + public note: Note, + /** The owner whose public key was used to encrypt the note. */ + public owner: AztecAddress, + /** The contract address this note is created in. */ + public contractAddress: AztecAddress, + /** The specific storage location of the note on the contract. */ + public storageSlot: Fr, + /** The hash of the tx the note was created in. */ + public txHash: TxHash, + ) {} + + toBuffer(): Buffer { + return Buffer.concat([ + this.note.toBuffer(), + this.owner.toBuffer(), + this.contractAddress.toBuffer(), + this.storageSlot.toBuffer(), + this.txHash.buffer, + ]); + } + static fromBuffer(buffer: Buffer | BufferReader) { + const reader = BufferReader.asReader(buffer); + + const note = Note.fromBuffer(reader); + const owner = AztecAddress.fromBuffer(reader); + const contractAddress = AztecAddress.fromBuffer(reader); + const storageSlot = Fr.fromBuffer(reader); + const txHash = new TxHash(reader.readBytes(TxHash.SIZE)); + + return new this(note, owner, contractAddress, storageSlot, txHash); + } + + toString() { + return '0x' + this.toBuffer().toString('hex'); + } + + static fromString(str: string) { + const hex = str.replace(/^0x/, ''); + return ExtendedNote.fromBuffer(Buffer.from(hex, 'hex')); + } +} diff --git a/yarn-project/types/src/notes/index.ts b/yarn-project/types/src/notes/index.ts new file mode 100644 index 00000000000..cba82199985 --- /dev/null +++ b/yarn-project/types/src/notes/index.ts @@ -0,0 +1,2 @@ +export * from './note_filter.js'; +export * from './extended_note.js'; diff --git a/yarn-project/types/src/notes/note_filter.ts b/yarn-project/types/src/notes/note_filter.ts new file mode 100644 index 00000000000..515dabb7c35 --- /dev/null +++ b/yarn-project/types/src/notes/note_filter.ts @@ -0,0 +1,18 @@ +import { AztecAddress, Fr } from '@aztec/circuits.js'; + +import { TxHash } from '../index.js'; + +/** + * A filter used to fetch Notes. + * @remarks This filter is applied as an intersection of all it's params. + */ +export type NoteFilter = { + /** Hash of a transaction from which to fetch the notes. */ + txHash?: TxHash; + /** The contract address the note belongs to. */ + contractAddress?: AztecAddress; + /** The specific storage location of the note on the contract. */ + storageSlot?: Fr; + /** The owner of the note (whose public key was used to encrypt the note). */ + owner?: AztecAddress; +}; diff --git a/yarn-project/types/src/tx/tx_receipt.ts b/yarn-project/types/src/tx/tx_receipt.ts index 5d2a167b375..a2df85aaf7f 100644 --- a/yarn-project/types/src/tx/tx_receipt.ts +++ b/yarn-project/types/src/tx/tx_receipt.ts @@ -1,5 +1,5 @@ import { AztecAddress } from '@aztec/foundation/aztec-address'; -import { TxHash } from '@aztec/types'; +import { ExtendedNote, TxHash } from '@aztec/types'; /** * Possible status of a transaction. @@ -40,6 +40,10 @@ export class TxReceipt { * The deployed contract's address. */ public contractAddress?: AztecAddress, + /** + * Notes created in this tx. + */ + public notes?: ExtendedNote[], ) {} /**