Skip to content

Commit

Permalink
fix: EventMetadata class implementation for serialisation (#9574)
Browse files Browse the repository at this point in the history
This is to fix `getEvents` PXE method over JSON RPC
- Create `EventMetadata` class 
- Remove `decodeEvent` from noir typescript contract generation
  • Loading branch information
spypsy authored Oct 30, 2024
1 parent 5e52900 commit bdff73a
Show file tree
Hide file tree
Showing 13 changed files with 165 additions and 90 deletions.
1 change: 1 addition & 0 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@
"Validium",
"vals",
"viem",
"vpks",
"Vyper",
"wasms",
"webassembly",
Expand Down
1 change: 1 addition & 0 deletions yarn-project/aztec.js/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ export {
EncryptedNoteL2BlockL2Logs,
EpochProofQuote,
EpochProofQuotePayload,
EventMetadata,
EventType,
ExtendedNote,
FunctionCall,
Expand Down
5 changes: 4 additions & 1 deletion yarn-project/aztec.js/src/rpc_clients/pxe_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
EncryptedL2Log,
EncryptedL2NoteLog,
EncryptedNoteL2BlockL2Logs,
EventMetadata,
ExtendedNote,
ExtendedUnencryptedL2Log,
L2Block,
Expand Down Expand Up @@ -36,7 +37,7 @@ import {
PrivateCircuitPublicInputs,
PublicKeys,
} from '@aztec/circuits.js';
import { NoteSelector } from '@aztec/foundation/abi';
import { EventSelector, NoteSelector } from '@aztec/foundation/abi';
import { Buffer32 } from '@aztec/foundation/buffer';
import { createJsonRpcClient, makeFetch } from '@aztec/foundation/json-rpc/client';

Expand All @@ -55,6 +56,7 @@ export const createPXEClient = (url: string, fetch = makeFetch([1, 2, 3], false)
CompleteAddress,
FunctionSelector,
EthAddress,
EventSelector,
ExtendedNote,
UniqueNote,
ExtendedUnencryptedL2Log,
Expand All @@ -75,6 +77,7 @@ export const createPXEClient = (url: string, fetch = makeFetch([1, 2, 3], false)
EncryptedNoteL2BlockL2Logs,
EncryptedL2NoteLog,
EncryptedL2Log,
EventMetadata,
UnencryptedL2Log,
NoteSelector,
NullifierMembershipWitness,
Expand Down
16 changes: 11 additions & 5 deletions yarn-project/aztec.js/src/wallet/base_wallet.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {
type AuthWitness,
type EventMetadata,
type EventType,
type ExtendedNote,
type GetUnencryptedLogsResponse,
Expand Down Expand Up @@ -34,7 +33,7 @@ import {
type PartialAddress,
type Point,
} from '@aztec/circuits.js';
import { type ContractArtifact } from '@aztec/foundation/abi';
import type { AbiType, ContractArtifact, EventSelector } from '@aztec/foundation/abi';

import { type Wallet } from '../account/wallet.js';
import { type ExecutionRequestInit } from '../entrypoint/entrypoint.js';
Expand Down Expand Up @@ -199,15 +198,22 @@ export abstract class BaseWallet implements Wallet {
}
getEvents<T>(
type: EventType,
eventMetadata: EventMetadata<T>,
event: {
/** The event selector */
eventSelector: EventSelector;
/** The event's abi type */
abiType: AbiType;
/** The field names */
fieldNames: string[];
},
from: number,
limit: number,
vpks: Point[] = [
this.getCompleteAddress().publicKeys.masterIncomingViewingPublicKey,
this.getCompleteAddress().publicKeys.masterOutgoingViewingPublicKey,
],
) {
return this.pxe.getEvents(type, eventMetadata, from, limit, vpks);
): Promise<T[]> {
return this.pxe.getEvents(type, event, from, limit, vpks);
}
public getL1ToL2MembershipWitness(
contractAddress: AztecAddress,
Expand Down
30 changes: 2 additions & 28 deletions yarn-project/builder/src/contract-interface-gen/typescript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,12 +257,12 @@ function generateEvents(events: any[] | undefined) {
`;

const fieldNames = event.fields.map((field: any) => `"${field.name}"`);
const eventType = `${eventName}: {decode: (payload: L1EventPayload | UnencryptedL2Log | undefined) => ${eventName} | undefined, eventSelector: EventSelector, fieldNames: string[] }`;
const eventType = `${eventName}: {abiType: AbiType, eventSelector: EventSelector, fieldNames: string[] }`;
// Reusing the decodeFunctionSignature
const eventSignature = decodeFunctionSignature(eventName, event.fields);
const eventSelector = `EventSelector.fromSignature('${eventSignature}')`;
const eventImpl = `${eventName}: {
decode: this.decodeEvent(${eventSelector}, ${JSON.stringify(event, null, 4)}),
abiType: ${JSON.stringify(event, null, 4)},
eventSelector: ${eventSelector},
fieldNames: [${fieldNames}],
}`;
Expand All @@ -277,32 +277,6 @@ function generateEvents(events: any[] | undefined) {
return {
eventDefs: eventsMetadata.map(({ eventDef }) => eventDef).join('\n'),
events: `
// Partial application is chosen is to avoid the duplication of so much codegen.
private static decodeEvent<T>(
eventSelector: EventSelector,
eventType: AbiType,
): (payload: L1EventPayload | UnencryptedL2Log | undefined) => T | undefined {
return (payload: L1EventPayload | UnencryptedL2Log | undefined): T | undefined => {
if (payload === undefined) {
return undefined;
}
if (payload instanceof L1EventPayload) {
if (!eventSelector.equals(payload.eventTypeId)) {
return undefined;
}
return decodeFromAbi([eventType], payload.event.items) as T;
} else {
let items = [];
for (let i = 0; i < payload.data.length; i += 32) {
items.push(new Fr(payload.data.subarray(i, i + 32)));
}
return decodeFromAbi([eventType], items) as T;
}
};
}
public static get events(): { ${eventsMetadata.map(({ eventType }) => eventType).join(', ')} } {
return {
${eventsMetadata.map(({ eventImpl }) => eventImpl).join(',\n')}
Expand Down
22 changes: 4 additions & 18 deletions yarn-project/circuit-types/src/interfaces/pxe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,11 @@ import {
type Point,
type ProtocolContractAddresses,
} from '@aztec/circuits.js';
import { type ContractArtifact, type EventSelector } from '@aztec/foundation/abi';
import type { AbiType, ContractArtifact, EventSelector } from '@aztec/foundation/abi';

import { type AuthWitness } from '../auth_witness.js';
import { type L2Block } from '../l2_block.js';
import {
type GetUnencryptedLogsResponse,
type L1EventPayload,
type LogFilter,
type UnencryptedL2Log,
} from '../logs/index.js';
import { type GetUnencryptedLogsResponse, type LogFilter } from '../logs/index.js';
import { type IncomingNotesFilter } from '../notes/incoming_notes_filter.js';
import { type ExtendedNote, type OutgoingNotesFilter, type UniqueNote } from '../notes/index.js';
import { type PrivateExecutionResult } from '../private_execution_result.js';
Expand Down Expand Up @@ -409,31 +404,22 @@ export interface PXE {
/**
* Returns the events of a specified type given search parameters.
* @param type - The type of the event to search for—Encrypted, or Unencrypted.
* @param eventMetadata - Identifier of the event. This should be the class generated from the contract. e.g. Contract.events.Event
* @param eventMetadata - Metadata of the event. This should be the class generated from the contract. e.g. Contract.events.Event
* @param from - The block number to search from.
* @param limit - The amount of blocks to search.
* @param vpks - (Used for encrypted logs only) The viewing (incoming and outgoing) public keys that correspond to the viewing secret keys that can decrypt the log.
* @returns - The deserialized events.
*/
getEvents<T>(
type: EventType,
eventMetadata: EventMetadata<T>,
eventMetadata: { eventSelector: EventSelector; abiType: AbiType; fieldNames: string[] },
from: number,
limit: number,
vpks: Point[],
): Promise<T[]>;
}
// docs:end:pxe-interface

/**
* The shape of the event generated on the Contract.
*/
export interface EventMetadata<T> {
decode(payload: L1EventPayload | UnencryptedL2Log): T | undefined;
eventSelector: EventSelector;
fieldNames: string[];
}

/**
* This is used in getting events via the filter
*/
Expand Down
77 changes: 77 additions & 0 deletions yarn-project/circuit-types/src/logs/event_metadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { EventType, L1EventPayload, type UnencryptedL2Log } from '@aztec/circuit-types';
import { type AbiType } from '@aztec/foundation/abi';
import { EventSelector, decodeFromAbi } from '@aztec/foundation/abi';
import { Fr } from '@aztec/foundation/fields';

/**
* Represents metadata for an event decoder, including all information needed to reconstruct it.
*/
export class EventMetadata<T> {
public readonly decode: (payload: L1EventPayload | UnencryptedL2Log) => T | undefined;

public readonly eventSelector: EventSelector;
public readonly abiType: AbiType;
public readonly fieldNames: string[];

constructor(
public readonly eventType: EventType,
event: { eventSelector: EventSelector; abiType: AbiType; fieldNames: string[] },
) {
this.eventSelector = event.eventSelector;
this.abiType = event.abiType;
this.fieldNames = event.fieldNames;
this.decode = EventMetadata.decodeEvent<T>(event.eventSelector, event.abiType);
}

public static decodeEvent<T>(
eventSelector: EventSelector,
abiType: AbiType,
): (payload: L1EventPayload | UnencryptedL2Log | undefined) => T | undefined {
return (payload: L1EventPayload | UnencryptedL2Log | undefined): T | undefined => {
if (payload === undefined) {
return undefined;
}

if (payload instanceof L1EventPayload) {
if (!eventSelector.equals(payload.eventTypeId)) {
return undefined;
}
return decodeFromAbi([abiType], payload.event.items) as T;
} else {
const items = [];
for (let i = 0; i < payload.data.length; i += 32) {
items.push(new Fr(payload.data.subarray(i, i + 32)));
}

return decodeFromAbi([abiType], items) as T;
}
};
}

/**
* Serializes the metadata to a JSON-friendly format
*/
public toJSON() {
return {
type: 'event_metadata',
eventSelector: this.eventSelector.toString(),
eventType: this.eventType,
fieldNames: this.fieldNames,
};
}

/**
* Creates an EventMetadata instance from a JSON representation
*/
public static fromJSON(json: any): EventMetadata<any> {
if (json?.type !== 'event_metadata') {
throw new Error('Invalid event metadata format');
}

return new EventMetadata(EventType.Encrypted, {
eventSelector: EventSelector.fromString(json.eventSelector),
abiType: json.abiType,
fieldNames: json.fieldNames,
});
}
}
1 change: 1 addition & 0 deletions yarn-project/circuit-types/src/logs/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './encrypted_l2_note_log.js';
export * from './encrypted_l2_log.js';
export * from './event_metadata.js';
export * from './get_unencrypted_logs_response.js';
export * from './function_l2_logs.js';
export * from './l2_block_l2_logs.js';
Expand Down
33 changes: 22 additions & 11 deletions yarn-project/end-to-end/src/e2e_event_logs.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
type AccountWalletWithSecretKey,
type AztecNode,
EventMetadata,
EventType,
Fr,
L1EventPayload,
Expand Down Expand Up @@ -61,14 +62,18 @@ describe('Logs', () => {
expect(decryptedEvent0.eventTypeId).toStrictEqual(EventSelector.fromSignature('ExampleEvent0(Field,Field)'));

// We decode our event into the event type
const event0 = TestLogContract.events.ExampleEvent0.decode(decryptedEvent0);
const event0Metadata = new EventMetadata<ExampleEvent0>(
EventType.Encrypted,
TestLogContract.events.ExampleEvent0,
);
const event0 = event0Metadata.decode(decryptedEvent0);

// We check that the event was decoded correctly
expect(event0?.value0).toStrictEqual(preimage[0].toBigInt());
expect(event0?.value1).toStrictEqual(preimage[1].toBigInt());

// We check that an event that does not match, is not decoded correctly due to an event type id mismatch
const badEvent0 = TestLogContract.events.ExampleEvent1.decode(decryptedEvent0);
const badEvent0 = event0Metadata.decode(decryptedEvent0);
expect(badEvent0).toBe(undefined);

const decryptedEvent1 = L1EventPayload.decryptAsIncoming(encryptedLogs[2], wallets[0].getEncryptionSecret())!;
Expand All @@ -78,15 +83,20 @@ describe('Logs', () => {
expect(decryptedEvent1.eventTypeId).toStrictEqual(EventSelector.fromSignature('ExampleEvent1((Field),u8)'));

// We check our second event, which is a different type
const event1 = TestLogContract.events.ExampleEvent1.decode(decryptedEvent1);
const event1Metadata = new EventMetadata<ExampleEvent1>(
EventType.Encrypted,
TestLogContract.events.ExampleEvent1,
);

const event1 = event1Metadata.decode(decryptedEvent1);

// We expect the fields to have been populated correctly
expect(event1?.value2).toStrictEqual(preimage[2]);
// We get the last byte here because value3 is of type u8
expect(event1?.value3).toStrictEqual(BigInt(preimage[3].toBuffer().subarray(31).readUint8()));

// Again, trying to decode another event with mismatching data does not yield anything
const badEvent1 = TestLogContract.events.ExampleEvent0.decode(decryptedEvent1);
const badEvent1 = event1Metadata.decode(decryptedEvent1);
expect(badEvent1).toBe(undefined);
});

Expand All @@ -113,14 +123,15 @@ describe('Logs', () => {
.wait();

// We get all the events we can decrypt with either our incoming or outgoing viewing keys
const collectedEvent0s = await wallets[0].getEvents(

const collectedEvent0s = await wallets[0].getEvents<ExampleEvent0>(
EventType.Encrypted,
TestLogContract.events.ExampleEvent0,
firstTx.blockNumber!,
lastTx.blockNumber! - firstTx.blockNumber! + 1,
);

const collectedEvent0sWithIncoming = await wallets[0].getEvents(
const collectedEvent0sWithIncoming = await wallets[0].getEvents<ExampleEvent0>(
EventType.Encrypted,
TestLogContract.events.ExampleEvent0,
firstTx.blockNumber!,
Expand All @@ -129,15 +140,15 @@ describe('Logs', () => {
[wallets[0].getCompleteAddress().publicKeys.masterIncomingViewingPublicKey],
);

const collectedEvent0sWithOutgoing = await wallets[0].getEvents(
const collectedEvent0sWithOutgoing = await wallets[0].getEvents<ExampleEvent0>(
EventType.Encrypted,
TestLogContract.events.ExampleEvent0,
firstTx.blockNumber!,
lastTx.blockNumber! - firstTx.blockNumber! + 1,
[wallets[0].getCompleteAddress().publicKeys.masterOutgoingViewingPublicKey],
);

const collectedEvent1s = await wallets[0].getEvents(
const collectedEvent1s = await wallets[0].getEvents<ExampleEvent1>(
EventType.Encrypted,
TestLogContract.events.ExampleEvent1,
firstTx.blockNumber!,
Expand All @@ -150,7 +161,7 @@ describe('Logs', () => {
expect(collectedEvent0s.length).toBe(10);
expect(collectedEvent1s.length).toBe(5);

const emptyEvent1s = await wallets[0].getEvents(
const emptyEvent1s = await wallets[0].getEvents<ExampleEvent1>(
EventType.Encrypted,
TestLogContract.events.ExampleEvent1,
firstTx.blockNumber!,
Expand Down Expand Up @@ -199,14 +210,14 @@ describe('Logs', () => {
);
const lastTx = await testLogContract.methods.emit_unencrypted_events(preimage[++i]).send().wait();

const collectedEvent0s = await wallets[0].getEvents(
const collectedEvent0s = await wallets[0].getEvents<ExampleEvent0>(
EventType.Unencrypted,
TestLogContract.events.ExampleEvent0,
firstTx.blockNumber!,
lastTx.blockNumber! - firstTx.blockNumber! + 1,
);

const collectedEvent1s = await wallets[0].getEvents(
const collectedEvent1s = await wallets[0].getEvents<ExampleEvent1>(
EventType.Unencrypted,
TestLogContract.events.ExampleEvent1,
firstTx.blockNumber!,
Expand Down
Loading

0 comments on commit bdff73a

Please sign in to comment.