Skip to content
Closed
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
12 changes: 10 additions & 2 deletions yarn-project/simulator/src/public/avm/opcodes/misc.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { jest } from '@jest/globals';
import { mock } from 'jest-mock-extended';

import type { PublicSideEffectTraceInterface } from '../../side_effect_trace_interface.js';
import { Field, Uint8, Uint32 } from '../avm_memory_types.js';
import { initContext, initExecutionEnvironment } from '../fixtures/initializers.js';
import { initContext, initExecutionEnvironment, initPersistableStateManager } from '../fixtures/initializers.js';
import { Opcode } from '../serialization/instruction_serialization.js';
import { DebugLog } from './misc.js';

Expand Down Expand Up @@ -30,7 +32,9 @@ describe('Misc Instructions', () => {

it('Should execute DebugLog in client-initiated simulation mode', async () => {
const env = initExecutionEnvironment({ clientInitiatedSimulation: true });
const context = initContext({ env });
const trace = mock<PublicSideEffectTraceInterface>();
const persistableState = initPersistableStateManager({ trace });
const context = initContext({ env, persistableState });
// Set up memory with message and fields
const messageOffset = 10;
const fieldsOffset = 100;
Expand Down Expand Up @@ -64,6 +68,10 @@ describe('Misc Instructions', () => {

// Check that logger.verbose was called with formatted message
expect(mockVerbose).toHaveBeenCalledWith(`Hello ${fieldValue.toFr()}!`);

// expect debug log to be traced
const msgId = 0; // TODO(dbanks12): implement messageId
expect(trace.traceDebugLog).toHaveBeenCalledWith(msgId, [fieldValue.toFr()]);
} finally {
// Restore the mock
mockIsVerbose.mockRestore();
Expand Down
19 changes: 12 additions & 7 deletions yarn-project/simulator/src/public/avm/opcodes/misc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,19 @@ export class DebugLog extends Instruction {
memory.checkTagsRange(TypeTag.UINT8, messageOffset, this.messageSize);
memory.checkTagsRange(TypeTag.FIELD, fieldsOffset, fieldsSize);

// Interpret str<N> = [u8; N] to string.
const messageAsStr = rawMessage.map(field => String.fromCharCode(field.toNumber())).join('');
const formattedStr = applyStringFormatting(
messageAsStr,
fields.map(field => field.toFr()),
);
// Convert fields to Fr array for the side effect trace
const fieldsAsFr = fields.map(field => field.toFr());

DebugLog.logger.verbose(formattedStr);
const messageId = 0; // TODO(dbanks12): implement messageId as operand
context.persistableState.writeDebugLog(messageId, fieldsAsFr);

if (DebugLog.logger.isLevelEnabled('verbose')) {
// Interpret str<N> = [u8; N] to string.
const messageAsStr = rawMessage.map(field => String.fromCharCode(field.toNumber())).join('');
const formattedStr = applyStringFormatting(messageAsStr, fieldsAsFr);

DebugLog.logger.verbose(formattedStr);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -513,7 +513,7 @@ export class PublicProcessor implements Traceable {
private async processTxWithPublicCalls(tx: Tx): Promise<[ProcessedTx, NestedProcessReturnValues[]]> {
const timer = new Timer();

const { avmProvingRequest, gasUsed, revertCode, revertReason, processedPhases } =
const { avmProvingRequest, gasUsed, revertCode, revertReason, processedPhases, publicDebuggedLogs } =
await this.publicTxSimulator.simulate(tx);

if (!avmProvingRequest) {
Expand Down Expand Up @@ -542,7 +542,14 @@ export class PublicProcessor implements Traceable {
const durationMs = timer.ms();
this.metrics.recordTx(phaseCount, durationMs, gasUsed.publicGas);

const processedTx = makeProcessedTxFromTxWithPublicCalls(tx, avmProvingRequest, gasUsed, revertCode, revertReason);
const processedTx = makeProcessedTxFromTxWithPublicCalls(
tx,
avmProvingRequest,
gasUsed,
revertCode,
revertReason,
publicDebuggedLogs,
);

const returnValues = processedPhases.find(({ phase }) => phase === TxExecutionPhase.APP_LOGIC)?.returnValues ?? [];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,13 @@ export class PublicTxContext {
return txFee;
}

/**
* Get the public debugged logs accumulated during this transaction.
*/
public getDebugLogs() {
return this.trace.getSideEffects().publicDebuggedLogs;
}

/**
* Generate the public inputs for the AVM circuit.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
} from '@aztec/stdlib/avm';
import { SimulationError } from '@aztec/stdlib/errors';
import type { Gas, GasUsed } from '@aztec/stdlib/gas';
import type { PublicDebuggedLog } from '@aztec/stdlib/logs';
import { ProvingRequestType } from '@aztec/stdlib/proofs';
import type { MerkleTreeWriteOperations } from '@aztec/stdlib/trees';
import {
Expand Down Expand Up @@ -47,6 +48,8 @@ export type PublicTxResult = {
/** Revert reason, if any */
revertReason?: SimulationError;
processedPhases: ProcessedPhase[];
/** Debug logs emitted during public execution */
publicDebuggedLogs: PublicDebuggedLog[];
};

export class PublicTxSimulator {
Expand Down Expand Up @@ -176,6 +179,7 @@ export class PublicTxSimulator {
revertCode,
revertReason: context.revertReason,
processedPhases: processedPhases,
publicDebuggedLogs: context.getDebugLogs(),
};
} finally {
// Make sure there are no new contracts in the tx-level cache.
Expand Down
12 changes: 11 additions & 1 deletion yarn-project/simulator/src/public/side_effect_trace.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { PublicDataUpdateRequest } from '@aztec/stdlib/avm';
import { AztecAddress } from '@aztec/stdlib/aztec-address';
import { computePublicDataTreeLeafSlot } from '@aztec/stdlib/hash';
import { NoteHash, Nullifier } from '@aztec/stdlib/kernel';
import { PublicLog } from '@aztec/stdlib/logs';
import { PublicDebuggedLog, PublicLog } from '@aztec/stdlib/logs';
import { L2ToL1Message } from '@aztec/stdlib/messaging';
import { makeContractClassPublic } from '@aztec/stdlib/testing';

Expand All @@ -31,6 +31,8 @@ describe('Public Side Effect Trace', () => {
const recipient = Fr.random();
const content = Fr.random();
const log = [Fr.random(), Fr.random(), Fr.random()];
const debugLogMessageId = 42;
const debugLog = [new Fr(1), new Fr(2)];

let startCounter: number;
let startCounterPlus1: number;
Expand Down Expand Up @@ -88,6 +90,11 @@ describe('Public Side Effect Trace', () => {
expect(trace.getSideEffects().publicLogs).toEqual([expectedLog]);
});

it('Should trace debug logs', () => {
trace.traceDebugLog(debugLogMessageId, debugLog);
expect(trace.getSideEffects().publicDebuggedLogs).toEqual([new PublicDebuggedLog(debugLogMessageId, debugLog)]);
});

describe('Maximum accesses', () => {
it('Should enforce maximum number of user public storage writes', async () => {
for (let i = 0; i < MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX; i++) {
Expand Down Expand Up @@ -228,6 +235,7 @@ describe('Public Side Effect Trace', () => {
testCounter++;
nestedTrace.tracePublicLog(address, log);
testCounter++;
nestedTrace.traceDebugLog(debugLogMessageId, debugLog); // no counter incr

trace.merge(nestedTrace, reverted);

Expand All @@ -244,6 +252,8 @@ describe('Public Side Effect Trace', () => {
expect(parentSideEffects.nullifiers).toEqual([]);
expect(parentSideEffects.l2ToL1Msgs).toEqual([]);
expect(parentSideEffects.publicLogs).toEqual([]);
// debug logs don't get dropped on revert
expect(parentSideEffects.publicDebuggedLogs).toEqual([new PublicDebuggedLog(debugLogMessageId, debugLog)]);
// parent trace does not adopt nested call's writtenPublicDataSlots
expect(trace.isStorageCold(address, slot)).toBe(true);
} else {
Expand Down
14 changes: 13 additions & 1 deletion yarn-project/simulator/src/public/side_effect_trace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { PublicDataUpdateRequest } from '@aztec/stdlib/avm';
import type { AztecAddress } from '@aztec/stdlib/aztec-address';
import { computePublicDataTreeLeafSlot } from '@aztec/stdlib/hash';
import { NoteHash, Nullifier } from '@aztec/stdlib/kernel';
import { PublicLog } from '@aztec/stdlib/logs';
import { PublicDebuggedLog, PublicLog } from '@aztec/stdlib/logs';
import { L2ToL1Message, ScopedL2ToL1Message } from '@aztec/stdlib/messaging';

import { strict as assert } from 'assert';
Expand All @@ -36,6 +36,7 @@ export type SideEffects = {
nullifiers: Nullifier[];
l2ToL1Msgs: ScopedL2ToL1Message[];
publicLogs: PublicLog[];
publicDebuggedLogs: PublicDebuggedLog[];
};

export class SideEffectArrayLengths {
Expand Down Expand Up @@ -69,6 +70,7 @@ export class SideEffectTrace implements PublicSideEffectTraceInterface {
private nullifiers: Nullifier[] = [];
private l2ToL1Messages: ScopedL2ToL1Message[] = [];
private publicLogs: PublicLog[] = [];
private publicDebuggedLogs: PublicDebuggedLog[] = [];

/** Make sure a forked trace is never merged twice. */
private alreadyMergedIntoParent = false;
Expand Down Expand Up @@ -114,6 +116,9 @@ export class SideEffectTrace implements PublicSideEffectTraceInterface {
this.sideEffectCounter = forkedTrace.sideEffectCounter;
this.uniqueClassIds.acceptAndMerge(forkedTrace.uniqueClassIds);

// Debug logs are always merged, even on revert
this.publicDebuggedLogs.push(...forkedTrace.publicDebuggedLogs);

if (!reverted) {
this.publicDataWrites.push(...forkedTrace.publicDataWrites);
this.noteHashes.push(...forkedTrace.noteHashes);
Expand Down Expand Up @@ -237,6 +242,12 @@ export class SideEffectTrace implements PublicSideEffectTraceInterface {
this.incrementSideEffectCounter();
}

public traceDebugLog(messageId: number, fields: Fr[]) {
// Debug logs don't have a limit like other side effects
const debugLog = new PublicDebuggedLog(messageId, fields);
this.publicDebuggedLogs.push(debugLog);
}

public traceGetContractClass(contractClassId: Fr, exists: boolean) {
// We limit the number of unique contract class IDs due to hashing and the trace length limit.
if (exists && !this.uniqueClassIds.has(contractClassId.toString())) {
Expand All @@ -260,6 +271,7 @@ export class SideEffectTrace implements PublicSideEffectTraceInterface {
nullifiers: this.nullifiers,
l2ToL1Msgs: this.l2ToL1Messages,
publicLogs: this.publicLogs,
publicDebuggedLogs: this.publicDebuggedLogs,
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@ export interface PublicSideEffectTraceInterface {
traceNewNullifier(siloedNullifier: Fr): void;
traceNewL2ToL1Message(contractAddress: AztecAddress, recipient: Fr, content: Fr): void;
tracePublicLog(contractAddress: AztecAddress, log: Fr[]): void;
traceDebugLog(messageId: number, fields: Fr[]): void;
traceGetContractClass(contractClassId: Fr, exists: boolean): void;
}
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,15 @@ export class PublicPersistableStateManager {
this.trace.tracePublicLog(contractAddress, log);
}

/**
* Write a debug log
* @param messageId - message ID (currently always 0)
* @param fields - debug log fields
*/
public writeDebugLog(messageId: number, fields: Fr[]) {
this.trace.traceDebugLog(messageId, fields);
}

/**
* Get a contract instance.
* @param contractAddress - address of the contract instance to retrieve.
Expand Down
1 change: 1 addition & 0 deletions yarn-project/stdlib/src/logs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export * from './log_with_tx_data.js';
export * from './indexed_tagging_secret.js';
export * from './contract_class_log.js';
export * from './public_log.js';
export * from './public_debugged_log.js';
export * from './private_log.js';
export * from './pending_tagged_log.js';
export * from './log_id.js';
Expand Down
55 changes: 55 additions & 0 deletions yarn-project/stdlib/src/logs/public_debugged_log.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import type { FieldsOf } from '@aztec/foundation/array';
import { Fr } from '@aztec/foundation/fields';
import { BufferReader, FieldReader, serializeToBuffer, serializeToFields } from '@aztec/foundation/serialize';

/**
* Represents a debug log emitted during public execution.
*/
export class PublicDebuggedLog {
constructor(
/** Message ID (currently always 0, reserved for future use) */
public messageId: number,
/** The fields array from the DebugLog opcode */
public fields: Fr[],
) {}

static from(fields: FieldsOf<PublicDebuggedLog>) {
return new PublicDebuggedLog(fields.messageId, fields.fields);
}

toBuffer(): Buffer {
return serializeToBuffer([this.messageId, this.fields.length, this.fields]);
}

static fromBuffer(buffer: Buffer | BufferReader): PublicDebuggedLog {
const reader = BufferReader.asReader(buffer);
const messageId = reader.readNumber();
const fieldsLength = reader.readNumber();
const fields = reader.readArray(fieldsLength, Fr);
return new PublicDebuggedLog(messageId, fields);
}

toFields(): Fr[] {
return serializeToFields([this.messageId, this.fields.length, this.fields]);
}

static fromFields(fields: Fr[] | FieldReader): PublicDebuggedLog {
const reader = FieldReader.asReader(fields);
const messageId = reader.readField().toNumber();
const fieldsLength = reader.readField().toNumber();
const fieldsArray = reader.readFieldArray(fieldsLength);
return new PublicDebuggedLog(messageId, fieldsArray);
}

toString(): string {
return `PublicDebuggedLog { messageId: ${this.messageId}, fields: [${this.fields.map(f => f.toString()).join(', ')}] }`;
}

equals(other: PublicDebuggedLog): boolean {
return (
this.messageId === other.messageId &&
this.fields.length === other.fields.length &&
this.fields.every((field, index) => field.equals(other.fields[index]))
);
}
}
3 changes: 2 additions & 1 deletion yarn-project/stdlib/src/tests/factories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1732,7 +1732,8 @@ export async function makeBloatedProcessedTx({
},
gasUsed,
RevertCode.OK,
undefined /* revertReason */,
/*revertReason=*/ undefined,
/*publicDebuggedLogs=*/ [],
);
}
}
Expand Down
8 changes: 8 additions & 0 deletions yarn-project/stdlib/src/tx/processed_tx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Gas } from '../gas/gas.js';
import type { GasUsed } from '../gas/gas_used.js';
import { computeL2ToL1MessageHash } from '../hash/hash.js';
import type { PrivateKernelTailCircuitPublicInputs } from '../kernel/private_kernel_tail_circuit_public_inputs.js';
import type { PublicDebuggedLog } from '../logs/public_debugged_log.js';
import type { ClientIvcProof } from '../proofs/client_ivc_proof.js';
import type { GlobalVariables } from './global_variables.js';
import type { Tx } from './tx.js';
Expand Down Expand Up @@ -61,6 +62,10 @@ export type ProcessedTx = {
* Reason the tx was reverted.
*/
revertReason: SimulationError | undefined;
/**
* Debug logs emitted during public execution.
*/
publicDebuggedLogs: PublicDebuggedLog[];
};

/**
Expand Down Expand Up @@ -125,6 +130,7 @@ export function makeProcessedTxFromPrivateOnlyTx(
gasUsed,
revertCode: RevertCode.OK,
revertReason: undefined,
publicDebuggedLogs: [],
};
}

Expand All @@ -140,6 +146,7 @@ export function makeProcessedTxFromTxWithPublicCalls(
gasUsed: GasUsed,
revertCode: RevertCode,
revertReason: SimulationError | undefined,
publicDebuggedLogs: PublicDebuggedLog[] = [],
): ProcessedTx {
const avmPublicInputs = avmProvingRequest.inputs.publicInputs;

Expand Down Expand Up @@ -189,5 +196,6 @@ export function makeProcessedTxFromTxWithPublicCalls(
gasUsed,
revertCode,
revertReason,
publicDebuggedLogs,
};
}
Loading