Skip to content

Commit

Permalink
feat(perf): cleanup and minor speedups in AVM Ephemeral trees
Browse files Browse the repository at this point in the history
  • Loading branch information
dbanks12 committed Feb 9, 2025
1 parent 18151df commit d44707f
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 45 deletions.
2 changes: 1 addition & 1 deletion yarn-project/simulator/src/avm/avm_simulator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ export class AvmSimulator {

const endTotalTime = performance.now();
const totalTime = endTotalTime - startTotalTime;
this.log.debug(`Total execution time: ${totalTime}ms`);
this.log.debug(`Core AVM simulation took ${totalTime}ms`);

// Return results for processing by calling context
return results;
Expand Down
28 changes: 22 additions & 6 deletions yarn-project/simulator/src/avm/avm_tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,21 @@ import { type IndexedTreeLeafPreimage, type TreeLeafPreimage } from '@aztec/foun
import { strict as assert } from 'assert';
import cloneDeep from 'lodash.clonedeep';

const MAX_TREE_DEPTH = 128;

/**
* Helper function to precompute zero hashes
*/
async function preComputeZeroHashes(): Promise<Fr[]> {
let currentHash = Fr.zero();
const zeroHashes: Fr[] = [];
for (let i = 0; i < MAX_TREE_DEPTH; i++) {
zeroHashes.push(currentHash);
currentHash = await poseidon2Hash([currentHash, currentHash]);
}
return zeroHashes;
}

/****************************************************/
/****** Structs Used by the AvmEphemeralForest ******/
/****************************************************/
Expand Down Expand Up @@ -554,6 +569,8 @@ export class EphemeralAvmTree {
private tree: Tree;
public frontier: Fr[];

private static precomputedZeroHashes: Fr[] | undefined;

private constructor(private root: Leaf, public leafCount: bigint, public depth: number, private zeroHashes: Fr[]) {
this.tree = root;
this.frontier = [];
Expand All @@ -565,13 +582,12 @@ export class EphemeralAvmTree {
treeDb: MerkleTreeReadOperations,
merkleId: MerkleTreeId,
): Promise<EphemeralAvmTree> {
let zeroHash = Fr.zero();
// Can probably cache this elsewhere
const zeroHashes = [];
for (let i = 0; i < depth; i++) {
zeroHashes.push(zeroHash);
zeroHash = await poseidon2Hash([zeroHash, zeroHash]);
let zeroHashes = EphemeralAvmTree.precomputedZeroHashes;
if (zeroHashes === undefined) {
zeroHashes = await preComputeZeroHashes();
EphemeralAvmTree.precomputedZeroHashes = zeroHashes;
}
const zeroHash = zeroHashes[depth - 1];
const tree = new EphemeralAvmTree(Leaf(zeroHash), forkedLeafCount, depth, zeroHashes);
await tree.initializeFrontier(treeDb, merkleId);
return tree;
Expand Down
46 changes: 23 additions & 23 deletions yarn-project/simulator/src/avm/journal/journal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,16 +144,16 @@ export class AvmPersistableStateManager {
* @param value - the value being written to the slot
*/
public async writeStorage(contractAddress: AztecAddress, slot: Fr, value: Fr, protocolWrite = false): Promise<void> {
this.log.debug(`Storage write (address=${contractAddress}, slot=${slot}): value=${value}`);
this.log.trace(`Storage write (address=${contractAddress}, slot=${slot}): value=${value}`);
const leafSlot = await computePublicDataTreeLeafSlot(contractAddress, slot);
this.log.debug(`leafSlot=${leafSlot}`);
this.log.trace(`leafSlot=${leafSlot}`);
// Cache storage writes for later reference/reads
this.publicStorage.write(contractAddress, slot, value);

if (this.doMerkleOperations) {
const result = await this.merkleTrees.writePublicStorage(leafSlot, value);
assert(result !== undefined, 'Public data tree insertion error. You might want to disable doMerkleOperations.');
this.log.debug(`Inserted public data tree leaf at leafSlot ${leafSlot}, value: ${value}`);
this.log.trace(`Inserted public data tree leaf at leafSlot ${leafSlot}, value: ${value}`);

const lowLeafInfo = result.lowWitness;
const lowLeafPreimage = result.lowWitness.preimage as PublicDataTreeLeafPreimage;
Expand Down Expand Up @@ -195,9 +195,9 @@ export class AvmPersistableStateManager {
*/
public async readStorage(contractAddress: AztecAddress, slot: Fr): Promise<Fr> {
const { value, cached } = await this.publicStorage.read(contractAddress, slot);
this.log.debug(`Storage read (address=${contractAddress}, slot=${slot}): value=${value}, cached=${cached}`);
this.log.trace(`Storage read (address=${contractAddress}, slot=${slot}): value=${value}, cached=${cached}`);
const leafSlot = await computePublicDataTreeLeafSlot(contractAddress, slot);
this.log.debug(`leafSlot=${leafSlot}`);
this.log.trace(`leafSlot=${leafSlot}`);

if (this.doMerkleOperations) {
// Get leaf if present, low leaf if absent
Expand All @@ -212,8 +212,8 @@ export class AvmPersistableStateManager {
const leafPath = await this.merkleTrees.getSiblingPath(MerkleTreeId.PUBLIC_DATA_TREE, leafIndex);
const leafPreimage = preimage as PublicDataTreeLeafPreimage;

this.log.debug(`leafPreimage.slot: ${leafPreimage.slot}, leafPreimage.value: ${leafPreimage.value}`);
this.log.debug(
this.log.trace(`leafPreimage.slot: ${leafPreimage.slot}, leafPreimage.value: ${leafPreimage.value}`);
this.log.trace(
`leafPreimage.nextSlot: ${leafPreimage.nextSlot}, leafPreimage.nextIndex: ${Number(leafPreimage.nextIndex)}`,
);

Expand All @@ -223,7 +223,7 @@ export class AvmPersistableStateManager {
`Value mismatch when performing public data read (got value: ${value}, value in ephemeral tree: ${leafPreimage.value})`,
);
} else {
this.log.debug(`Slot has never been written before!`);
this.log.trace(`Slot has never been written before!`);
// Sanity check that the leaf slot is skipped by low leaf when it doesn't exist
assert(
leafPreimage.slot.toBigInt() < leafSlot.toBigInt() &&
Expand All @@ -250,7 +250,7 @@ export class AvmPersistableStateManager {
*/
public async peekStorage(contractAddress: AztecAddress, slot: Fr): Promise<Fr> {
const { value, cached } = await this.publicStorage.read(contractAddress, slot);
this.log.debug(`Storage peek (address=${contractAddress}, slot=${slot}): value=${value}, cached=${cached}`);
this.log.trace(`Storage peek (address=${contractAddress}, slot=${slot}): value=${value}, cached=${cached}`);
return Promise.resolve(value);
}

Expand All @@ -266,7 +266,7 @@ export class AvmPersistableStateManager {
public async checkNoteHashExists(contractAddress: AztecAddress, noteHash: Fr, leafIndex: Fr): Promise<boolean> {
const gotLeafValue = (await this.worldStateDB.getCommitmentValue(leafIndex.toBigInt())) ?? Fr.ZERO;
const exists = gotLeafValue.equals(noteHash);
this.log.debug(
this.log.trace(
`noteHashes(${contractAddress})@${noteHash} ?? leafIndex: ${leafIndex} | gotLeafValue: ${gotLeafValue}, exists: ${exists}.`,
);
if (this.doMerkleOperations) {
Expand Down Expand Up @@ -306,7 +306,7 @@ export class AvmPersistableStateManager {
* @param noteHash - the siloed unique hash to write
*/
public async writeUniqueNoteHash(noteHash: Fr): Promise<void> {
this.log.debug(`noteHashes += @${noteHash}.`);
this.log.trace(`noteHashes += @${noteHash}.`);

if (this.doMerkleOperations) {
// Should write a helper for this
Expand All @@ -325,7 +325,7 @@ export class AvmPersistableStateManager {
* @returns exists - whether the nullifier exists in the nullifier set
*/
public async checkNullifierExists(contractAddress: AztecAddress, nullifier: Fr): Promise<boolean> {
this.log.debug(`Checking existence of nullifier (address=${contractAddress}, nullifier=${nullifier})`);
this.log.trace(`Checking existence of nullifier (address=${contractAddress}, nullifier=${nullifier})`);
const siloedNullifier = await siloNullifier(contractAddress, nullifier);
const [exists, leafOrLowLeafPreimage, leafOrLowLeafIndex, leafOrLowLeafPath] = await this.getNullifierMembership(
siloedNullifier,
Expand Down Expand Up @@ -367,7 +367,7 @@ export class AvmPersistableStateManager {
]
> {
const [exists, isPending, _] = await this.nullifiers.checkExists(siloedNullifier);
this.log.debug(`Checked siloed nullifier ${siloedNullifier} (exists=${exists}), pending=${isPending}`);
this.log.trace(`Checked siloed nullifier ${siloedNullifier} (exists=${exists}), pending=${isPending}`);

if (this.doMerkleOperations) {
// Get leaf if present, low leaf if absent
Expand All @@ -386,7 +386,7 @@ export class AvmPersistableStateManager {
);

if (exists) {
this.log.debug(`Siloed nullifier ${siloedNullifier} exists at leafIndex=${leafIndex}`);
this.log.trace(`Siloed nullifier ${siloedNullifier} exists at leafIndex=${leafIndex}`);
} else {
// Sanity check that the leaf value is skipped by low leaf when it doesn't exist
assert(
Expand All @@ -407,7 +407,7 @@ export class AvmPersistableStateManager {
* @param nullifier - the unsiloed nullifier to write
*/
public async writeNullifier(contractAddress: AztecAddress, nullifier: Fr) {
this.log.debug(`Inserting new nullifier (address=${nullifier}, nullifier=${contractAddress})`);
this.log.trace(`Inserting new nullifier (address=${nullifier}, nullifier=${contractAddress})`);
const siloedNullifier = await siloNullifier(contractAddress, nullifier);
await this.writeSiloedNullifier(siloedNullifier);
}
Expand All @@ -417,7 +417,7 @@ export class AvmPersistableStateManager {
* @param siloedNullifier - the siloed nullifier to write
*/
public async writeSiloedNullifier(siloedNullifier: Fr) {
this.log.debug(`Inserting siloed nullifier=${siloedNullifier}`);
this.log.trace(`Inserting siloed nullifier=${siloedNullifier}`);

if (this.doMerkleOperations) {
// Maybe overkill, but we should check if the nullifier is already present in the tree before attempting to insert
Expand Down Expand Up @@ -447,13 +447,13 @@ export class AvmPersistableStateManager {
// Cache pending nullifiers for later access
await this.nullifiers.append(siloedNullifier);
// We append the new nullifier
this.log.debug(
this.log.trace(
`Nullifier tree root before insertion ${await this.merkleTrees.treeMap
.get(MerkleTreeId.NULLIFIER_TREE)!
.getRoot()}`,
);
const appendResult = await this.merkleTrees.appendNullifier(siloedNullifier);
this.log.debug(
this.log.trace(
`Nullifier tree root after insertion ${await this.merkleTrees.treeMap
.get(MerkleTreeId.NULLIFIER_TREE)!
.getRoot()}`,
Expand Down Expand Up @@ -496,7 +496,7 @@ export class AvmPersistableStateManager {
): Promise<boolean> {
const valueAtIndex = (await this.worldStateDB.getL1ToL2LeafValue(msgLeafIndex.toBigInt())) ?? Fr.ZERO;
const exists = valueAtIndex.equals(msgHash);
this.log.debug(
this.log.trace(
`l1ToL2Messages(@${msgLeafIndex}) ?? exists: ${exists}, expected: ${msgHash}, found: ${valueAtIndex}.`,
);

Expand All @@ -522,7 +522,7 @@ export class AvmPersistableStateManager {
* @param content - Message content.
*/
public writeL2ToL1Message(contractAddress: AztecAddress, recipient: Fr, content: Fr) {
this.log.debug(`L2ToL1Messages(${contractAddress}) += (recipient: ${recipient}, content: ${content}).`);
this.log.trace(`L2ToL1Messages(${contractAddress}) += (recipient: ${recipient}, content: ${content}).`);
this.trace.traceNewL2ToL1Message(contractAddress, recipient, content);
}

Expand All @@ -532,7 +532,7 @@ export class AvmPersistableStateManager {
* @param log - log contents
*/
public writePublicLog(contractAddress: AztecAddress, log: Fr[]) {
this.log.debug(`PublicLog(${contractAddress}) += event with ${log.length} fields.`);
this.log.trace(`PublicLog(${contractAddress}) += event with ${log.length} fields.`);
this.trace.tracePublicLog(contractAddress, log);
}

Expand All @@ -542,7 +542,7 @@ export class AvmPersistableStateManager {
* @returns the contract instance or undefined if it does not exist.
*/
public async getContractInstance(contractAddress: AztecAddress): Promise<SerializableContractInstance | undefined> {
this.log.debug(`Getting contract instance for address ${contractAddress}`);
this.log.trace(`Getting contract instance for address ${contractAddress}`);
const instanceWithAddress = await this.worldStateDB.getContractInstance(contractAddress);
const exists = instanceWithAddress !== undefined;

Expand All @@ -568,7 +568,7 @@ export class AvmPersistableStateManager {

if (exists) {
const instance = new SerializableContractInstance(instanceWithAddress);
this.log.debug(
this.log.trace(
`Got contract instance (address=${contractAddress}): exists=${exists}, instance=${jsonStringify(instance)}`,
);
if (this.doMerkleOperations) {
Expand Down
4 changes: 2 additions & 2 deletions yarn-project/simulator/src/public/public_tx_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,15 +162,15 @@ export class PublicTxContext {
* NOTE: this does not "halt" the entire transaction execution.
*/
revert(phase: TxExecutionPhase, revertReason: SimulationError | undefined = undefined, culprit = '') {
this.log.debug(`${TxExecutionPhase[phase]} phase reverted! ${culprit} failed with reason: ${revertReason}`);
this.log.warn(`${TxExecutionPhase[phase]} phase reverted! ${culprit} failed with reason: ${revertReason}`);

if (revertReason && !this.revertReason) {
// don't override revertReason
// (if app logic and teardown both revert, we want app logic's reason)
this.revertReason = revertReason;
}
if (phase === TxExecutionPhase.SETUP) {
this.log.debug(`Setup phase reverted! The transaction will be thrown out.`);
this.log.warn(`Setup phase reverted! The transaction will be thrown out.`);
if (revertReason) {
throw revertReason;
} else {
Expand Down
Loading

0 comments on commit d44707f

Please sign in to comment.