Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Unify unencrypted log emission and decoding #7232

Merged
merged 3 commits into from
Jun 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions noir-projects/aztec-nr/aztec/src/context/public_context.nr
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,23 @@ impl PublicContext {
pub fn storage_address(self) -> AztecAddress {
storage_address()
}

pub fn fee_per_l2_gas(self) -> Field {
fee_per_l2_gas()
}

pub fn fee_per_da_gas(self) -> Field {
fee_per_da_gas()
}
/**
* Emit a log with the given event selector and message.
* @param event_selector The event selector for the log.
* @param message The message to emit in the log.
*/

pub fn emit_unencrypted_log<T, N>(&mut self, log: T) where T: Serialize<N> {
emit_unencrypted_log(Serialize::serialize(log).as_slice());
}

pub fn note_hash_exists(self, note_hash: Field, leaf_index: Field) -> bool {
note_hash_exists(note_hash, leaf_index) == 1
}

pub fn l1_to_l2_msg_exists(self, msg_hash: Field, msg_leaf_index: Field) -> bool {
l1_to_l2_msg_exists(msg_hash, msg_leaf_index) == 1
}
Expand Down
1 change: 1 addition & 0 deletions noir-projects/aztec-nr/aztec/src/lib.nr
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ mod state_vars;
mod prelude;
mod public_storage;
mod encrypted_logs;
mod unencrypted_logs;
use dep::protocol_types;
mod utils;

Expand Down
1 change: 1 addition & 0 deletions noir-projects/aztec-nr/aztec/src/unencrypted_logs.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mod unencrypted_event_emission;
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use crate::{
context::{PrivateContext, PublicContext}, event::event_interface::EventInterface,
encrypted_logs::payload::compute_encrypted_event_log, oracle::logs_traits::LensForEncryptedEvent
};
use dep::protocol_types::{address::AztecAddress, grumpkin_point::GrumpkinPoint, traits::Serialize};

fn emit<Event, NB, MB, OB, N, M>(
context: &mut PublicContext,
event: Event
) where Event: EventInterface<NB, MB>, Event: Serialize<N>, [Field; N]: LensForEventSelector<N, M> {
let selector = Event::get_event_type_id();

let serialized_event = event.serialize();
let mut emitted_log = [0; M];

// We put the selector in the "last" place, to avoid reading or assigning to an expression in an index
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems slightly odd to me, would it not be evaluated to constants instead, e.g., i + 1 when unrolled so not doing much difference in the end? 🤷 Don't matter to much to me though, still pretty neat.

for i in 0..serialized_event.len() {
emitted_log[i] = serialized_event[i];
}

emitted_log[serialized_event.len()] = selector.to_field();

context.emit_unencrypted_log(emitted_log);
}

pub fn encode_event<Event, NB, MB, OB, N, M>(context: &mut PublicContext) -> fn[(&mut PublicContext,)](Event) -> () where Event: EventInterface<NB, MB>, Event: Serialize<N>, [Field; N]: LensForEventSelector<N, M> {
| e: Event | {
emit(
context,
e,
);
}
}

trait LensForEventSelector<N, M> {
// N = event preimage input in fields
// M = event preimage input in fields + event selector as field
fn output(self: [Field; N]) -> [Field; M];
}

impl LensForEventSelector<1, 2> for [Field; 1] {
fn output(self) -> [Field; 2] {[self[0] as Field; 2]}
}
impl LensForEventSelector<2, 3> for [Field; 2] {
fn output(self) -> [Field; 3] {[self[0] as Field; 3]}
}
impl LensForEventSelector<3, 4> for [Field; 3] {
fn output(self) -> [Field; 4] {[self[0] as Field; 4]}
}
impl LensForEventSelector<4, 5> for [Field; 4] {
fn output(self) -> [Field; 5] {[self[0] as Field; 5]}
}
impl LensForEventSelector<5, 6> for [Field; 5] {
fn output(self) -> [Field; 6] {[self[0] as Field; 6]}
}
impl LensForEventSelector<6, 7> for [Field; 6] {
fn output(self) -> [Field; 7] {[self[0] as Field; 7]}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ contract TestLog {
use dep::aztec::encrypted_logs::incoming_body::EncryptedLogIncomingBody;
use dep::aztec::event::event_interface::EventInterface;
use dep::aztec::encrypted_logs::encrypted_event_emission::{encode_and_encrypt_event, encode_and_encrypt_event_with_keys};
use dep::aztec::unencrypted_logs::unencrypted_event_emission::encode_event;

#[aztec(event)]
struct ExampleEvent0 {
Expand Down Expand Up @@ -64,4 +65,15 @@ contract TestLog {
)
);
}

#[aztec(public)]
fn emit_unencrypted_events(preimages: [Field; 4]) {
let event0 = ExampleEvent0 { value0: preimages[0], value1: preimages[1] };

event0.emit(encode_event(&mut context));

let event1 = ExampleEvent1 { value2: preimages[2], value3: preimages[3] };

event1.emit(encode_event(&mut context));
}
}
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 @@ -99,6 +99,7 @@ export {
EncryptedLogHeader,
EncryptedNoteLogIncomingBody,
EncryptedLogOutgoingBody,
EventType,
ExtendedNote,
FunctionCall,
GrumpkinPrivateKey,
Expand Down
8 changes: 5 additions & 3 deletions yarn-project/aztec.js/src/wallet/base_wallet.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
type AuthWitness,
type EventMetadata,
type EventType,
type ExtendedNote,
type GetUnencryptedLogsResponse,
type IncomingNotesFilter,
Expand Down Expand Up @@ -182,11 +183,12 @@ export abstract class BaseWallet implements Wallet {
return this.pxe.getPXEInfo();
}
getEvents<T>(
type: EventType,
eventMetadata: EventMetadata<T>,
from: number,
limit: number,
eventMetadata: EventMetadata<T>,
ivpk: Point = this.getCompleteAddress().publicKeys.masterIncomingViewingPublicKey,
): Promise<T[]> {
return this.pxe.getEvents(from, limit, eventMetadata, ivpk);
) {
return this.pxe.getEvents(type, eventMetadata, from, limit, ivpk);
}
}
23 changes: 19 additions & 4 deletions yarn-project/circuit-types/src/interfaces/pxe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -381,14 +381,21 @@ export interface PXE {
isContractPubliclyDeployed(address: AztecAddress): Promise<boolean>;

/**
* Returns the events of a specified type.
* 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 from - The block number to search from.
* @param limit - The amount of blocks to search.
* @param eventMetadata - Identifier of the event. This should be the class generated from the contract. e.g. Contract.events.Event
* @param ivpk - The incoming viewing public key that corresponds to the incoming viewing secret key that can decrypt the log.
* @param ivpk - (Used for encrypted logs only) The incoming viewing public key that corresponds to the incoming viewing secret key that can decrypt the log.
* @returns - The deserialized events.
*/
getEvents<T>(from: number, limit: number, eventMetadata: EventMetadata<T>, ivpk: Point): Promise<T[]>;
getEvents<T>(
type: EventType,
eventMetadata: EventMetadata<T>,
from: number,
limit: number,
ivpk: Point,
): Promise<T[]>;
}
// docs:end:pxe-interface

Expand All @@ -401,6 +408,14 @@ export interface EventMetadata<T> {
fieldNames: string[];
}

/**
* This is used in getting events via the filter
*/
export enum EventType {
Encrypted = 'Encrypted',
Unencrypted = 'Unencrypted',
}

/**
* Provides basic information about the running PXE.
*/
Expand Down
55 changes: 51 additions & 4 deletions yarn-project/end-to-end/src/e2e_event_logs.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { type AccountWalletWithSecretKey, type AztecNode, Fr, L1EventPayload, TaggedLog } from '@aztec/aztec.js';
import {
type AccountWalletWithSecretKey,
type AztecNode,
EventType,
Fr,
L1EventPayload,
TaggedLog,
} from '@aztec/aztec.js';
import { deriveMasterIncomingViewingSecretKey } from '@aztec/circuits.js';
import { EventSelector } from '@aztec/foundation/abi';
import { makeTuple } from '@aztec/foundation/array';
Expand Down Expand Up @@ -31,7 +38,7 @@ describe('Logs', () => {
afterAll(() => teardown());

describe('functionality around emitting an encrypted log', () => {
it('emits multiple events as encrypted logs and decodes them', async () => {
it('emits multiple events as encrypted logs and decodes a single one manually', async () => {
const randomness = makeTuple(2, Fr.random);
const preimage = makeTuple(4, Fr.random);

Expand Down Expand Up @@ -103,15 +110,17 @@ describe('Logs', () => {
const lastTx = await testLogContract.methods.emit_encrypted_events(randomness[++i], preimage[i]).send().wait();

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

const collectedEvent1s = await wallets[0].getEvents(
EventType.Encrypted,
TestLogContract.events.ExampleEvent1,
firstTx.blockNumber!,
lastTx.blockNumber! - firstTx.blockNumber! + 1,
TestLogContract.events.ExampleEvent1,
// This function can also be called specifying the incoming viewing public key associated with the encrypted event.
wallets[0].getCompleteAddress().publicKeys.masterIncomingViewingPublicKey,
);
Expand All @@ -129,5 +138,43 @@ describe('Logs', () => {
preimage.map(preimage => ({ value2: preimage[2], value3: preimage[3] })).sort(exampleEvent1Sort),
);
});

it('emits multiple events as unencrypted logs and decodes them', async () => {
const preimage = makeTuple(5, makeTuple.bind(undefined, 4, Fr.random)) as Tuple<Tuple<Fr, 4>, 5>;

let i = 0;
const firstTx = await testLogContract.methods.emit_unencrypted_events(preimage[i]).send().wait();
await Promise.all(
[...new Array(3)].map(() => testLogContract.methods.emit_unencrypted_events(preimage[++i]).send().wait()),
);
const lastTx = await testLogContract.methods.emit_unencrypted_events(preimage[++i]).send().wait();

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

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

expect(collectedEvent0s.length).toBe(5);
expect(collectedEvent1s.length).toBe(5);

const exampleEvent0Sort = (a: ExampleEvent0, b: ExampleEvent0) => (a.value0 > b.value0 ? 1 : -1);
expect(collectedEvent0s.sort(exampleEvent0Sort)).toStrictEqual(
preimage.map(preimage => ({ value0: preimage[0], value1: preimage[1] })).sort(exampleEvent0Sort),
);

const exampleEvent1Sort = (a: ExampleEvent1, b: ExampleEvent1) => (a.value2 > b.value2 ? 1 : -1);
expect(collectedEvent1s.sort(exampleEvent1Sort)).toStrictEqual(
preimage.map(preimage => ({ value2: preimage[2], value3: preimage[3] })).sort(exampleEvent1Sort),
);
});
});
});
76 changes: 73 additions & 3 deletions yarn-project/pxe/src/pxe_service/pxe_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
EncryptedNoteTxL2Logs,
EncryptedTxL2Logs,
type EventMetadata,
EventType,
ExtendedNote,
type FunctionCall,
type GetUnencryptedLogsResponse,
Expand Down Expand Up @@ -35,8 +36,14 @@ import {
getContractClassFromArtifact,
} from '@aztec/circuits.js';
import { computeNoteHashNonce, siloNullifier } from '@aztec/circuits.js/hash';
import { type ContractArtifact, type DecodedReturn, FunctionSelector, encodeArguments } from '@aztec/foundation/abi';
import { type Fq, Fr, type Point } from '@aztec/foundation/fields';
import {
type ContractArtifact,
type DecodedReturn,
EventSelector,
FunctionSelector,
encodeArguments,
} from '@aztec/foundation/abi';
import { type Fq, Fr, Point } from '@aztec/foundation/fields';
import { SerialQueue } from '@aztec/foundation/fifo';
import { type DebugLogger, createDebugLogger } from '@aztec/foundation/log';
import { type KeyStore } from '@aztec/key-store';
Expand Down Expand Up @@ -834,7 +841,34 @@ export class PXEService implements PXE {
return !!(await this.node.getContract(address));
}

public async getEvents<T>(from: number, limit: number, eventMetadata: EventMetadata<T>, ivpk: Point): Promise<T[]> {
public getEvents<T>(
type: EventType.Encrypted,
eventMetadata: EventMetadata<T>,
from: number,
limit: number,
ivpk: Point,
): Promise<T[]>;
public getEvents<T>(
type: EventType.Unencrypted,
eventMetadata: EventMetadata<T>,
from: number,
limit: number,
): Promise<T[]>;
public getEvents<T>(
type: EventType,
eventMetadata: EventMetadata<T>,
from: number,
limit: number,
ivpk: Point = Point.ZERO,
): Promise<T[]> {
if (type.includes(EventType.Encrypted)) {
return this.getEncryptedEvents(from, limit, eventMetadata, ivpk);
}

return this.getUnencryptedEvents(from, limit, eventMetadata);
}

async getEncryptedEvents<T>(from: number, limit: number, eventMetadata: EventMetadata<T>, ivpk: Point): Promise<T[]> {
const blocks = await this.node.getBlocks(from, limit);

const txEffects = blocks.flatMap(block => block.body.txEffects);
Expand Down Expand Up @@ -874,4 +908,40 @@ export class PXEService implements PXE {

return decodedEvents;
}

async getUnencryptedEvents<T>(from: number, limit: number, eventMetadata: EventMetadata<T>): Promise<T[]> {
const { logs: unencryptedLogs } = await this.node.getUnencryptedLogs({
fromBlock: from,
toBlock: from + limit,
});

const decodedEvents = unencryptedLogs
.map(unencryptedLog => {
const unencryptedLogBuf = unencryptedLog.log.data;
if (
!EventSelector.fromBuffer(unencryptedLogBuf.subarray(unencryptedLogBuf.byteLength - 4)).equals(
eventMetadata.eventSelector,
)
) {
return undefined;
}

if (unencryptedLogBuf.byteLength !== eventMetadata.fieldNames.length * 32 + 32) {
throw new Error(
'Something is weird here, we have matching FunctionSelectors, but the actual payload has mismatched length',
);
}

return eventMetadata.fieldNames.reduce(
(acc, curr, i) => ({
...acc,
[curr]: new Fr(unencryptedLogBuf.subarray(i * 32, i * 32 + 32)),
}),
{} as T,
);
})
.filter(unencryptedLog => unencryptedLog !== undefined) as T[];

return decodedEvents;
}
}
Loading