diff --git a/yarn-project/aztec.js/src/index.ts b/yarn-project/aztec.js/src/index.ts index 626f858ba38..cf7198dd956 100644 --- a/yarn-project/aztec.js/src/index.ts +++ b/yarn-project/aztec.js/src/index.ts @@ -102,6 +102,7 @@ export { EncryptedLogOutgoingBody, EventType, ExtendedNote, + UniqueNote, FunctionCall, L1Actor, L1ToL2Message, diff --git a/yarn-project/aztec.js/src/rpc_clients/pxe_client.ts b/yarn-project/aztec.js/src/rpc_clients/pxe_client.ts index 54b2aa81a11..4e8424bf5ea 100644 --- a/yarn-project/aztec.js/src/rpc_clients/pxe_client.ts +++ b/yarn-project/aztec.js/src/rpc_clients/pxe_client.ts @@ -15,6 +15,7 @@ import { TxHash, TxReceipt, UnencryptedL2BlockL2Logs, + UniqueNote, } from '@aztec/circuit-types'; import { AztecAddress, @@ -45,6 +46,7 @@ export const createPXEClient = (url: string, fetch = makeFetch([1, 2, 3], false) FunctionSelector, EthAddress, ExtendedNote, + UniqueNote, ExtendedUnencryptedL2Log, Fr, GrumpkinScalar, diff --git a/yarn-project/aztec.js/src/wallet/base_wallet.ts b/yarn-project/aztec.js/src/wallet/base_wallet.ts index 7a6d4ec81de..b4c1ebec830 100644 --- a/yarn-project/aztec.js/src/wallet/base_wallet.ts +++ b/yarn-project/aztec.js/src/wallet/base_wallet.ts @@ -17,6 +17,7 @@ import { type TxExecutionRequest, type TxHash, type TxReceipt, + type UniqueNote, } from '@aztec/circuit-types'; import { type NoteProcessorStats } from '@aztec/circuit-types/stats'; import { @@ -120,16 +121,12 @@ export abstract class BaseWallet implements Wallet { getTxReceipt(txHash: TxHash): Promise { return this.pxe.getTxReceipt(txHash); } - getIncomingNotes(filter: IncomingNotesFilter): Promise { + getIncomingNotes(filter: IncomingNotesFilter): Promise { return this.pxe.getIncomingNotes(filter); } - getOutgoingNotes(filter: OutgoingNotesFilter): Promise { + getOutgoingNotes(filter: OutgoingNotesFilter): Promise { return this.pxe.getOutgoingNotes(filter); } - // TODO(#4956): Un-expose this - getNoteNonces(note: ExtendedNote): Promise { - return this.pxe.getNoteNonces(note); - } getPublicStorageAt(contract: AztecAddress, storageSlot: Fr): Promise { return this.pxe.getPublicStorageAt(contract, storageSlot); } diff --git a/yarn-project/circuit-types/src/interfaces/pxe.ts b/yarn-project/circuit-types/src/interfaces/pxe.ts index efa100eac0d..3fb1dad58e8 100644 --- a/yarn-project/circuit-types/src/interfaces/pxe.ts +++ b/yarn-project/circuit-types/src/interfaces/pxe.ts @@ -18,7 +18,7 @@ import { type AuthWitness } from '../auth_witness.js'; import { type L2Block } from '../l2_block.js'; import { type GetUnencryptedLogsResponse, type L1EventPayload, type LogFilter } from '../logs/index.js'; import { type IncomingNotesFilter } from '../notes/incoming_notes_filter.js'; -import { type ExtendedNote, type OutgoingNotesFilter } from '../notes/index.js'; +import { type ExtendedNote, type OutgoingNotesFilter, type UniqueNote } from '../notes/index.js'; import { type NoteProcessorStats } from '../stats/stats.js'; import { type SimulatedTx, type Tx, type TxHash, type TxReceipt } from '../tx/index.js'; import { type TxEffect } from '../tx_effect.js'; @@ -235,23 +235,14 @@ export interface PXE { * @param filter - The filter to apply to the notes. * @returns The requested notes. */ - getIncomingNotes(filter: IncomingNotesFilter): Promise; + getIncomingNotes(filter: IncomingNotesFilter): Promise; /** * Gets outgoing notes of accounts registered in this PXE based on the provided filter. * @param filter - The filter to apply to the notes. * @returns The requested notes. */ - getOutgoingNotes(filter: OutgoingNotesFilter): Promise; - - /** - * 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 a single nonce may be returned since there might be more than one nonce for a given note. - * TODO(#4956): Un-expose this - */ - getNoteNonces(note: ExtendedNote): Promise; + getOutgoingNotes(filter: OutgoingNotesFilter): Promise; /** * Adds a note to the database. diff --git a/yarn-project/circuit-types/src/mocks.ts b/yarn-project/circuit-types/src/mocks.ts index 62bae58036f..c8734f769ef 100644 --- a/yarn-project/circuit-types/src/mocks.ts +++ b/yarn-project/circuit-types/src/mocks.ts @@ -31,7 +31,7 @@ import { Fr } from '@aztec/foundation/fields'; import { type ContractInstanceWithAddress, SerializableContractInstance } from '@aztec/types/contracts'; import { EncryptedNoteTxL2Logs, EncryptedTxL2Logs, Note, UnencryptedTxL2Logs } from './logs/index.js'; -import { ExtendedNote } from './notes/index.js'; +import { ExtendedNote, UniqueNote } from './notes/index.js'; import { PublicExecutionRequest } from './public_execution_request.js'; import { NestedProcessReturnValues, PublicSimulationOutput, SimulatedTx, Tx, TxHash } from './tx/index.js'; @@ -255,3 +255,15 @@ export const randomExtendedNote = ({ }: Partial = {}) => { return new ExtendedNote(note, owner, contractAddress, storageSlot, noteTypeId, txHash); }; + +export const randomUniqueNote = ({ + note = Note.random(), + owner = AztecAddress.random(), + contractAddress = AztecAddress.random(), + txHash = randomTxHash(), + storageSlot = Fr.random(), + noteTypeId = NoteSelector.random(), + nonce = Fr.random(), +}: Partial = {}) => { + return new UniqueNote(note, owner, contractAddress, storageSlot, noteTypeId, txHash, nonce); +}; diff --git a/yarn-project/circuit-types/src/notes/extended_note.test.ts b/yarn-project/circuit-types/src/notes/extended_note.test.ts index 0b5f8c89edb..25a2280b527 100644 --- a/yarn-project/circuit-types/src/notes/extended_note.test.ts +++ b/yarn-project/circuit-types/src/notes/extended_note.test.ts @@ -1,5 +1,5 @@ -import { randomExtendedNote } from '../mocks.js'; -import { ExtendedNote } from './extended_note.js'; +import { randomExtendedNote, randomUniqueNote } from '../mocks.js'; +import { ExtendedNote, UniqueNote } from './extended_note.js'; describe('Extended Note', () => { it('convert to and from buffer', () => { @@ -8,3 +8,11 @@ describe('Extended Note', () => { expect(ExtendedNote.fromBuffer(buf)).toEqual(extendedNote); }); }); + +describe('Unique Note', () => { + it('convert to and from buffer', () => { + const uniqueNote = randomUniqueNote(); + const buf = uniqueNote.toBuffer(); + expect(UniqueNote.fromBuffer(buf)).toEqual(uniqueNote); + }); +}); diff --git a/yarn-project/circuit-types/src/notes/extended_note.ts b/yarn-project/circuit-types/src/notes/extended_note.ts index bf91e2dc49d..4b3184563d0 100644 --- a/yarn-project/circuit-types/src/notes/extended_note.ts +++ b/yarn-project/circuit-types/src/notes/extended_note.ts @@ -57,3 +57,55 @@ export class ExtendedNote { return ExtendedNote.fromBuffer(Buffer.from(hex, 'hex')); } } + +export class UniqueNote extends ExtendedNote { + constructor( + /** The note as emitted from the Noir contract. */ + note: Note, + /** The owner whose public key was used to encrypt the note. */ + owner: AztecAddress, + /** The contract address this note is created in. */ + contractAddress: AztecAddress, + /** The specific storage location of the note on the contract. */ + storageSlot: Fr, + /** The type identifier of the note on the contract. */ + noteTypeId: NoteSelector, + /** The hash of the tx the note was created in. */ + txHash: TxHash, + /** The nonce of the note. */ + public nonce: Fr, + ) { + super(note, owner, contractAddress, storageSlot, noteTypeId, txHash); + } + + override toBuffer(): Buffer { + return Buffer.concat([ + this.note.toBuffer(), + this.owner.toBuffer(), + this.contractAddress.toBuffer(), + this.storageSlot.toBuffer(), + this.noteTypeId.toBuffer(), + this.txHash.buffer, + this.nonce.toBuffer(), + ]); + } + + static override 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 noteTypeId = reader.readObject(NoteSelector); + const txHash = new TxHash(reader.readBytes(TxHash.SIZE)); + const nonce = Fr.fromBuffer(reader); + + return new this(note, owner, contractAddress, storageSlot, noteTypeId, txHash, nonce); + } + + static override fromString(str: string) { + const hex = str.replace(/^0x/, ''); + return UniqueNote.fromBuffer(Buffer.from(hex, 'hex')); + } +} diff --git a/yarn-project/circuit-types/src/tx/tx_receipt.ts b/yarn-project/circuit-types/src/tx/tx_receipt.ts index 1543ef9fd46..e784e938c9c 100644 --- a/yarn-project/circuit-types/src/tx/tx_receipt.ts +++ b/yarn-project/circuit-types/src/tx/tx_receipt.ts @@ -1,7 +1,7 @@ import { RevertCode } from '@aztec/circuits.js'; import { type Fr } from '@aztec/foundation/fields'; -import { type ExtendedNote } from '../notes/extended_note.js'; +import { type UniqueNote } from '../notes/extended_note.js'; import { type PublicDataWrite } from '../public_data_write.js'; import { TxHash } from './tx_hash.js'; @@ -126,11 +126,11 @@ interface DebugInfo { * in the PXE which was used to submit the tx. You will not get notes of accounts which are not registered in * the PXE here even though they were created in this tx. */ - visibleIncomingNotes: ExtendedNote[]; + visibleIncomingNotes: UniqueNote[]; /** * Notes created in this tx which were successfully decoded with the outgoing keys of accounts which are registered * in the PXE which was used to submit the tx. You will not get notes of accounts which are not registered in * the PXE here even though they were created in this tx. */ - visibleOutgoingNotes: ExtendedNote[]; + visibleOutgoingNotes: UniqueNote[]; } diff --git a/yarn-project/end-to-end/src/e2e_crowdfunding_and_claim.test.ts b/yarn-project/end-to-end/src/e2e_crowdfunding_and_claim.test.ts index f88ba231142..d143c627bb5 100644 --- a/yarn-project/end-to-end/src/e2e_crowdfunding_and_claim.test.ts +++ b/yarn-project/end-to-end/src/e2e_crowdfunding_and_claim.test.ts @@ -12,6 +12,7 @@ import { PackedValues, TxExecutionRequest, type TxHash, + type UniqueNote, computeSecretHash, deriveKeys, } from '@aztec/aztec.js'; @@ -180,27 +181,22 @@ describe('e2e_crowdfunding_and_claim', () => { ]); }; - // Processes extended note such that it can be passed to a claim function of Claim contract - const processExtendedNote = async (extendedNote: ExtendedNote) => { - // TODO(#4956): Make fetching the nonce manually unnecessary - // To be able to perform the inclusion proof we need to fetch the nonce of the value note - const noteNonces = await pxe.getNoteNonces(extendedNote); - expect(noteNonces?.length).toEqual(1); - + // Processes unique note such that it can be passed to a claim function of Claim contract + const processUniqueNote = (uniqueNote: UniqueNote) => { return { header: { // eslint-disable-next-line camelcase - contract_address: extendedNote.contractAddress, + contract_address: uniqueNote.contractAddress, // eslint-disable-next-line camelcase - storage_slot: extendedNote.storageSlot, + storage_slot: uniqueNote.storageSlot, // eslint-disable-next-line camelcase note_hash_counter: 0, // set as 0 as note is not transient - nonce: noteNonces[0], + nonce: uniqueNote.nonce, }, - value: extendedNote.note.items[0], + value: uniqueNote.note.items[0], // eslint-disable-next-line camelcase - npk_m_hash: extendedNote.note.items[1], - randomness: extendedNote.note.items[2], + npk_m_hash: uniqueNote.note.items[1], + randomness: uniqueNote.note.items[2], }; }; @@ -233,7 +229,7 @@ describe('e2e_crowdfunding_and_claim', () => { expect(notes!.length).toEqual(1); // Set the value note in a format which can be passed to claim function - valueNote = await processExtendedNote(notes![0]); + valueNote = processUniqueNote(notes![0]); } // 3) We claim the reward token via the Claim contract @@ -304,7 +300,7 @@ describe('e2e_crowdfunding_and_claim', () => { expect(notes!.length).toEqual(1); // Set the value note in a format which can be passed to claim function - const anotherDonationNote = await processExtendedNote(notes![0]); + const anotherDonationNote = processUniqueNote(notes![0]); // We create an unrelated pxe and wallet without access to the nsk_app that correlates to the npk_m specified in the proof note. let unrelatedWallet: AccountWallet; @@ -356,7 +352,7 @@ describe('e2e_crowdfunding_and_claim', () => { const receipt = await inclusionsProofsContract.methods.create_note(owner, 5n).send().wait({ debug: true }); const { visibleIncomingNotes } = receipt.debugInfo!; expect(visibleIncomingNotes.length).toEqual(1); - note = await processExtendedNote(visibleIncomingNotes![0]); + note = processUniqueNote(visibleIncomingNotes![0]); } // 3) Test the note was included 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 c4ab90e3a9d..3a99d2b572a 100644 --- a/yarn-project/pxe/src/pxe_http/pxe_http_server.ts +++ b/yarn-project/pxe/src/pxe_http/pxe_http_server.ts @@ -16,6 +16,7 @@ import { TxHash, TxReceipt, UnencryptedL2BlockL2Logs, + UniqueNote, } from '@aztec/circuit-types'; import { FunctionSelector } from '@aztec/circuits.js'; import { NoteSelector } from '@aztec/foundation/abi'; @@ -48,6 +49,7 @@ export function createPXERpcServer(pxeService: PXE): JsonRpcServer { GrumpkinScalar, Note, ExtendedNote, + UniqueNote, AuthWitness, L2Block, TxEffect, diff --git a/yarn-project/pxe/src/pxe_service/pxe_service.ts b/yarn-project/pxe/src/pxe_service/pxe_service.ts index 7b6ba12d62c..cdb96184d8d 100644 --- a/yarn-project/pxe/src/pxe_service/pxe_service.ts +++ b/yarn-project/pxe/src/pxe_service/pxe_service.ts @@ -5,7 +5,7 @@ import { EncryptedTxL2Logs, type EventMetadata, EventType, - ExtendedNote, + type ExtendedNote, type FunctionCall, type GetUnencryptedLogsResponse, type IncomingNotesFilter, @@ -26,6 +26,7 @@ import { type TxHash, type TxReceipt, UnencryptedTxL2Logs, + UniqueNote, isNoirCallStackUnresolved, } from '@aztec/circuit-types'; import { @@ -296,7 +297,7 @@ export class PXEService implements PXE { return await this.node.getPublicStorageAt(contract, slot, 'latest'); } - public async getIncomingNotes(filter: IncomingNotesFilter): Promise { + public async getIncomingNotes(filter: IncomingNotesFilter): Promise { const noteDaos = await this.db.getIncomingNotes(filter); // TODO(#6531): Refactor --> This type conversion is ugly but I decided to keep it this way for now because @@ -312,12 +313,20 @@ export class PXEService implements PXE { } owner = completeAddresses.address; } - return new ExtendedNote(dao.note, owner, dao.contractAddress, dao.storageSlot, dao.noteTypeId, dao.txHash); + return new UniqueNote( + dao.note, + owner, + dao.contractAddress, + dao.storageSlot, + dao.noteTypeId, + dao.txHash, + dao.nonce, + ); }); return Promise.all(extendedNotes); } - public async getOutgoingNotes(filter: OutgoingNotesFilter): Promise { + public async getOutgoingNotes(filter: OutgoingNotesFilter): Promise { const noteDaos = await this.db.getOutgoingNotes(filter); // TODO(#6532): Refactor --> This type conversion is ugly but I decided to keep it this way for now because @@ -333,7 +342,15 @@ export class PXEService implements PXE { } owner = completeAddresses.address; } - return new ExtendedNote(dao.note, owner, dao.contractAddress, dao.storageSlot, dao.noteTypeId, dao.txHash); + return new UniqueNote( + dao.note, + owner, + dao.contractAddress, + dao.storageSlot, + dao.noteTypeId, + dao.txHash, + dao.nonce, + ); }); return Promise.all(extendedNotes); } @@ -344,7 +361,7 @@ export class PXEService implements PXE { throw new Error(`Unknown account: ${note.owner.toString()}`); } - const nonces = await this.getNoteNonces(note); + const nonces = await this.#getNoteNonces(note); if (nonces.length === 0) { throw new Error(`Cannot find the note in tx: ${note.txHash}.`); } @@ -394,7 +411,7 @@ export class PXEService implements PXE { throw new Error(`Unknown account: ${note.owner.toString()}`); } - const nonces = await this.getNoteNonces(note); + const nonces = await this.#getNoteNonces(note); if (nonces.length === 0) { throw new Error(`Cannot find the note in tx: ${note.txHash}.`); } @@ -440,9 +457,8 @@ export class PXEService implements PXE { * @param note - The note to find the nonces for. * @returns The nonces of the note. * @remarks More than a single nonce may be returned since there might be more than one nonce for a given note. - * TODO(#4956): Un-expose this */ - public async getNoteNonces(note: ExtendedNote): Promise { + async #getNoteNonces(note: ExtendedNote): Promise { const tx = await this.node.getTxEffect(note.txHash); if (!tx) { throw new Error(`Unknown tx: ${note.txHash}`);