Skip to content
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
2 changes: 0 additions & 2 deletions yarn-project/archiver/src/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,6 @@ export async function registerProtocolContracts(store: KVArchiverDataStore) {
const contract = await provider.getProtocolContractArtifact(name);
const contractClassPublic: ContractClassPublic = {
...contract.contractClass,
privateFunctions: [],
utilityFunctions: [],
};

const publicFunctionSignatures = contract.artifact.functions
Expand Down
90 changes: 4 additions & 86 deletions yarn-project/archiver/src/modules/data_store_updater.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,17 @@
import { BlockNumber, CheckpointNumber } from '@aztec/foundation/branded-types';
import { filterAsync } from '@aztec/foundation/collection';
import { Fr } from '@aztec/foundation/curves/bn254';
import { createLogger } from '@aztec/foundation/log';
import {
ContractClassPublishedEvent,
PrivateFunctionBroadcastedEvent,
UtilityFunctionBroadcastedEvent,
} from '@aztec/protocol-contracts/class-registry';
import { ContractClassPublishedEvent } from '@aztec/protocol-contracts/class-registry';
import {
ContractInstancePublishedEvent,
ContractInstanceUpdatedEvent,
} from '@aztec/protocol-contracts/instance-registry';
import type { L2Block, ValidateCheckpointResult } from '@aztec/stdlib/block';
import { type PublishedCheckpoint, validateCheckpoint } from '@aztec/stdlib/checkpoint';
import {
type ExecutablePrivateFunctionWithMembershipProof,
type UtilityFunctionWithMembershipProof,
computeContractAddressFromInstance,
computePublicBytecodeCommitment,
isValidPrivateFunctionMembershipProof,
isValidUtilityFunctionMembershipProof,
} from '@aztec/stdlib/contract';
import { computeContractAddressFromInstance, computePublicBytecodeCommitment } from '@aztec/stdlib/contract';
import type { ContractClassLog, PrivateLog, PublicLog } from '@aztec/stdlib/logs';
import type { UInt64 } from '@aztec/stdlib/types';

import groupBy from 'lodash.groupby';

import type { KVArchiverDataStore } from '../store/kv_archiver_store.js';
import type { L2TipsCache } from '../store/l2_tips_cache.js';

Expand Down Expand Up @@ -56,8 +42,7 @@ export class ArchiverDataStoreUpdater {
/**
* Adds a proposed block to the store with contract class/instance extraction from logs.
* This is an uncheckpointed block that has been proposed by the sequencer but not yet included in a checkpoint on L1.
* Extracts ContractClassPublished, ContractInstancePublished, ContractInstanceUpdated events,
* and individually broadcasted functions from the block logs.
* Extracts ContractClassPublished, ContractInstancePublished, ContractInstanceUpdated events from the block logs.
*
* @param block - The proposed L2 block to add.
* @param pendingChainValidationStatus - Optional validation status to set.
Expand Down Expand Up @@ -89,8 +74,7 @@ export class ArchiverDataStoreUpdater {
* Reconciles local blocks with incoming checkpoints from L1.
* Adds new checkpoints to the store with contract class/instance extraction from logs.
* Prunes any local blocks that conflict with checkpoint data (by comparing archive roots).
* Extracts ContractClassPublished, ContractInstancePublished, ContractInstanceUpdated events,
* and individually broadcasted functions from the checkpoint block logs.
* Extracts ContractClassPublished, ContractInstancePublished, ContractInstanceUpdated events from the checkpoint block logs.
*
* @param checkpoints - The published checkpoints to add.
* @param pendingChainValidationStatus - Optional validation status to set.
Expand Down Expand Up @@ -315,9 +299,6 @@ export class ArchiverDataStoreUpdater {
this.updatePublishedContractClasses(contractClassLogs, block.number, operation),
this.updateDeployedContractInstances(privateLogs, block.number, operation),
this.updateUpdatedContractInstances(publicLogs, block.header.globalVariables.timestamp, operation),
operation === Operation.Store
? this.storeBroadcastedIndividualFunctions(contractClassLogs, block.number)
: Promise.resolve(true),
])
).every(Boolean);
}
Expand Down Expand Up @@ -417,67 +398,4 @@ export class ArchiverDataStoreUpdater {
}
return true;
}

/**
* Stores the functions that were broadcasted individually.
*
* @dev Beware that there is not a delete variant of this, since they are added to contract classes
* and will be deleted as part of the class if needed.
*/
private async storeBroadcastedIndividualFunctions(
allLogs: ContractClassLog[],
_blockNum: BlockNumber,
): Promise<boolean> {
// Filter out private and utility function broadcast events
const privateFnEvents = allLogs
.filter(log => PrivateFunctionBroadcastedEvent.isPrivateFunctionBroadcastedEvent(log))
.map(log => PrivateFunctionBroadcastedEvent.fromLog(log));
const utilityFnEvents = allLogs
.filter(log => UtilityFunctionBroadcastedEvent.isUtilityFunctionBroadcastedEvent(log))
.map(log => UtilityFunctionBroadcastedEvent.fromLog(log));

// Group all events by contract class id
for (const [classIdString, classEvents] of Object.entries(
groupBy([...privateFnEvents, ...utilityFnEvents], e => e.contractClassId.toString()),
)) {
const contractClassId = Fr.fromHexString(classIdString);
const contractClass = await this.store.getContractClass(contractClassId);
if (!contractClass) {
this.log.warn(`Skipping broadcasted functions as contract class ${contractClassId.toString()} was not found`);
continue;
}

// Split private and utility functions, and filter out invalid ones
const allFns = classEvents.map(e => e.toFunctionWithMembershipProof());
const privateFns = allFns.filter(
(fn): fn is ExecutablePrivateFunctionWithMembershipProof => 'utilityFunctionsTreeRoot' in fn,
);
const utilityFns = allFns.filter(
(fn): fn is UtilityFunctionWithMembershipProof => 'privateFunctionsArtifactTreeRoot' in fn,
);

const privateFunctionsWithValidity = await Promise.all(
privateFns.map(async fn => ({ fn, valid: await isValidPrivateFunctionMembershipProof(fn, contractClass) })),
);
const validPrivateFns = privateFunctionsWithValidity.filter(({ valid }) => valid).map(({ fn }) => fn);
const utilityFunctionsWithValidity = await Promise.all(
utilityFns.map(async fn => ({
fn,
valid: await isValidUtilityFunctionMembershipProof(fn, contractClass),
})),
);
const validUtilityFns = utilityFunctionsWithValidity.filter(({ valid }) => valid).map(({ fn }) => fn);
const validFnCount = validPrivateFns.length + validUtilityFns.length;
if (validFnCount !== allFns.length) {
this.log.warn(`Skipping ${allFns.length - validFnCount} invalid functions`);
}

// Store the functions in the contract class in a single operation
if (validFnCount > 0) {
this.log.verbose(`Storing ${validFnCount} functions for contract class ${contractClassId.toString()}`);
}
await this.store.addFunctions(contractClassId, validPrivateFns, validUtilityFns);
}
return true;
}
}
104 changes: 1 addition & 103 deletions yarn-project/archiver/src/store/contract_class_store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,7 @@ import { Fr } from '@aztec/foundation/curves/bn254';
import { toArray } from '@aztec/foundation/iterable';
import { BufferReader, numToUInt8, serializeToBuffer } from '@aztec/foundation/serialize';
import type { AztecAsyncKVStore, AztecAsyncMap } from '@aztec/kv-store';
import { FunctionSelector } from '@aztec/stdlib/abi';
import type {
ContractClassPublic,
ContractClassPublicWithBlockNumber,
ExecutablePrivateFunctionWithMembershipProof,
UtilityFunctionWithMembershipProof,
} from '@aztec/stdlib/contract';
import { Vector } from '@aztec/stdlib/types';
import type { ContractClassPublic, ContractClassPublicWithBlockNumber } from '@aztec/stdlib/contract';

/**
* LMDB-based contract class storage for the archiver.
Expand Down Expand Up @@ -60,121 +53,26 @@ export class ContractClassStore {
async getContractClassIds(): Promise<Fr[]> {
return (await toArray(this.#contractClasses.keysAsync())).map(key => Fr.fromHexString(key));
}

async addFunctions(
contractClassId: Fr,
newPrivateFunctions: ExecutablePrivateFunctionWithMembershipProof[],
newUtilityFunctions: UtilityFunctionWithMembershipProof[],
): Promise<boolean> {
await this.db.transactionAsync(async () => {
const existingClassBuffer = await this.#contractClasses.getAsync(contractClassId.toString());
if (!existingClassBuffer) {
throw new Error(`Unknown contract class ${contractClassId} when adding private functions to store`);
}

const existingClass = deserializeContractClassPublic(existingClassBuffer);
const { privateFunctions: existingPrivateFns, utilityFunctions: existingUtilityFns } = existingClass;

const updatedClass: Omit<ContractClassPublicWithBlockNumber, 'id'> = {
...existingClass,
privateFunctions: [
...existingPrivateFns,
...newPrivateFunctions.filter(newFn => !existingPrivateFns.some(f => f.selector.equals(newFn.selector))),
],
utilityFunctions: [
...existingUtilityFns,
...newUtilityFunctions.filter(newFn => !existingUtilityFns.some(f => f.selector.equals(newFn.selector))),
],
};
await this.#contractClasses.set(contractClassId.toString(), serializeContractClassPublic(updatedClass));
});

return true;
}
}

function serializeContractClassPublic(contractClass: Omit<ContractClassPublicWithBlockNumber, 'id'>): Buffer {
return serializeToBuffer(
contractClass.l2BlockNumber,
numToUInt8(contractClass.version),
contractClass.artifactHash,
contractClass.privateFunctions.length,
contractClass.privateFunctions.map(serializePrivateFunction),
contractClass.utilityFunctions.length,
contractClass.utilityFunctions.map(serializeUtilityFunction),
contractClass.packedBytecode.length,
contractClass.packedBytecode,
contractClass.privateFunctionsRoot,
);
}

function serializePrivateFunction(fn: ExecutablePrivateFunctionWithMembershipProof): Buffer {
return serializeToBuffer(
fn.selector,
fn.vkHash,
fn.bytecode.length,
fn.bytecode,
fn.functionMetadataHash,
fn.artifactMetadataHash,
fn.utilityFunctionsTreeRoot,
new Vector(fn.privateFunctionTreeSiblingPath),
fn.privateFunctionTreeLeafIndex,
new Vector(fn.artifactTreeSiblingPath),
fn.artifactTreeLeafIndex,
);
}

function serializeUtilityFunction(fn: UtilityFunctionWithMembershipProof): Buffer {
return serializeToBuffer(
fn.selector,
fn.bytecode.length,
fn.bytecode,
fn.functionMetadataHash,
fn.artifactMetadataHash,
fn.privateFunctionsArtifactTreeRoot,
new Vector(fn.artifactTreeSiblingPath),
fn.artifactTreeLeafIndex,
);
}

function deserializeContractClassPublic(buffer: Buffer): Omit<ContractClassPublicWithBlockNumber, 'id'> {
const reader = BufferReader.asReader(buffer);
return {
l2BlockNumber: reader.readNumber(),
version: reader.readUInt8() as 1,
artifactHash: reader.readObject(Fr),
privateFunctions: reader.readVector({ fromBuffer: deserializePrivateFunction }),
utilityFunctions: reader.readVector({ fromBuffer: deserializeUtilityFunction }),
packedBytecode: reader.readBuffer(),
privateFunctionsRoot: reader.readObject(Fr),
};
}

function deserializePrivateFunction(buffer: Buffer | BufferReader): ExecutablePrivateFunctionWithMembershipProof {
const reader = BufferReader.asReader(buffer);
return {
selector: reader.readObject(FunctionSelector),
vkHash: reader.readObject(Fr),
bytecode: reader.readBuffer(),
functionMetadataHash: reader.readObject(Fr),
artifactMetadataHash: reader.readObject(Fr),
utilityFunctionsTreeRoot: reader.readObject(Fr),
privateFunctionTreeSiblingPath: reader.readVector(Fr),
privateFunctionTreeLeafIndex: reader.readNumber(),
artifactTreeSiblingPath: reader.readVector(Fr),
artifactTreeLeafIndex: reader.readNumber(),
};
}

function deserializeUtilityFunction(buffer: Buffer | BufferReader): UtilityFunctionWithMembershipProof {
const reader = BufferReader.asReader(buffer);
return {
selector: reader.readObject(FunctionSelector),
bytecode: reader.readBuffer(),
functionMetadataHash: reader.readObject(Fr),
artifactMetadataHash: reader.readObject(Fr),
privateFunctionsArtifactTreeRoot: reader.readObject(Fr),
artifactTreeSiblingPath: reader.readVector(Fr),
artifactTreeLeafIndex: reader.readNumber(),
};
}
37 changes: 1 addition & 36 deletions yarn-project/archiver/src/store/kv_archiver_store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
SlotNumber,
} from '@aztec/foundation/branded-types';
import { Buffer16, Buffer32 } from '@aztec/foundation/buffer';
import { times } from '@aztec/foundation/collection';
import { randomInt } from '@aztec/foundation/crypto/random';
import { Fr } from '@aztec/foundation/curves/bn254';
import { toArray } from '@aztec/foundation/iterable';
Expand All @@ -32,11 +31,7 @@ import {
import { MAX_LOGS_PER_TAG } from '@aztec/stdlib/interfaces/api-limit';
import { ContractClassLog, LogId } from '@aztec/stdlib/logs';
import { CheckpointHeader } from '@aztec/stdlib/rollup';
import {
makeContractClassPublic,
makeExecutablePrivateFunctionWithMembershipProof,
makeUtilityFunctionWithMembershipProof,
} from '@aztec/stdlib/testing';
import { makeContractClassPublic } from '@aztec/stdlib/testing';
import '@aztec/stdlib/testing/jest';
import { AppendOnlyTreeSnapshot } from '@aztec/stdlib/trees';
import { type IndexedTxEffect, TxHash } from '@aztec/stdlib/tx';
Expand Down Expand Up @@ -2380,36 +2375,6 @@ describe('KVArchiverDataStore', () => {
it('returns undefined if contract class is not found', async () => {
await expect(store.getContractClass(Fr.random())).resolves.toBeUndefined();
});

it('adds new private functions', async () => {
const fns = times(3, makeExecutablePrivateFunctionWithMembershipProof);
await store.addFunctions(contractClass.id, fns, []);
const stored = await store.getContractClass(contractClass.id);
expect(stored?.privateFunctions).toEqual(fns);
});

it('does not duplicate private functions', async () => {
const fns = times(3, makeExecutablePrivateFunctionWithMembershipProof);
await store.addFunctions(contractClass.id, fns.slice(0, 1), []);
await store.addFunctions(contractClass.id, fns, []);
const stored = await store.getContractClass(contractClass.id);
expect(stored?.privateFunctions).toEqual(fns);
});

it('adds new utility functions', async () => {
const fns = times(3, makeUtilityFunctionWithMembershipProof);
await store.addFunctions(contractClass.id, [], fns);
const stored = await store.getContractClass(contractClass.id);
expect(stored?.utilityFunctions).toEqual(fns);
});

it('does not duplicate utility functions', async () => {
const fns = times(3, makeUtilityFunctionWithMembershipProof);
await store.addFunctions(contractClass.id, [], fns.slice(0, 1));
await store.addFunctions(contractClass.id, [], fns);
const stored = await store.getContractClass(contractClass.id);
expect(stored?.utilityFunctions).toEqual(fns);
});
});

// Note that a lot of tests here are basically duplicates of the ones in getPublicLogsByTagsFromContract but
Expand Down
13 changes: 1 addition & 12 deletions yarn-project/archiver/src/store/kv_archiver_store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ import type {
ContractDataSource,
ContractInstanceUpdateWithAddress,
ContractInstanceWithAddress,
ExecutablePrivateFunctionWithMembershipProof,
UtilityFunctionWithMembershipProof,
} from '@aztec/stdlib/contract';
import type { L1RollupConstants } from '@aztec/stdlib/epoch-helpers';
import type { GetContractClassLogsResponse, GetPublicLogsResponse } from '@aztec/stdlib/interfaces/client';
Expand All @@ -37,7 +35,7 @@ import { ContractInstanceStore } from './contract_instance_store.js';
import { LogStore } from './log_store.js';
import { MessageStore } from './message_store.js';

export const ARCHIVER_DB_VERSION = 5;
export const ARCHIVER_DB_VERSION = 6;
export const MAX_FUNCTION_SIGNATURES = 1000;
export const MAX_FUNCTION_NAME_LEN = 256;

Expand Down Expand Up @@ -194,15 +192,6 @@ export class KVArchiverDataStore implements ContractDataSource {
return this.#contractClassStore.getBytecodeCommitment(contractClassId);
}

/** Adds private functions to a contract class. */
addFunctions(
contractClassId: Fr,
privateFunctions: ExecutablePrivateFunctionWithMembershipProof[],
utilityFunctions: UtilityFunctionWithMembershipProof[],
): Promise<boolean> {
return this.#contractClassStore.addFunctions(contractClassId, privateFunctions, utilityFunctions);
}

/**
* Add new contract instances from an L2 block to the store's list.
* @param data - List of contract instances to be added.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ describe('e2e_deploy_contract contract class registration', () => {
expect(registeredClass!.artifactHash.toString()).toEqual(contractClass.artifactHash.toString());
expect(registeredClass!.privateFunctionsRoot.toString()).toEqual(contractClass.privateFunctionsRoot.toString());
expect(registeredClass!.packedBytecode.toString('hex')).toEqual(contractClass.packedBytecode.toString('hex'));
expect(registeredClass!.privateFunctions).toEqual([]);
});
});

Expand Down

This file was deleted.

This file was deleted.

This file was deleted.

Loading
Loading