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
1 change: 1 addition & 0 deletions l1-contracts/src/core/libraries/ConstantsGen.sol
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ library Constants {
uint256 internal constant MAX_PACKED_BYTECODE_SIZE_PER_PRIVATE_FUNCTION_IN_FIELDS = 3000;
uint256 internal constant MAX_PACKED_BYTECODE_SIZE_PER_UNCONSTRAINED_FUNCTION_IN_FIELDS = 3000;
uint256 internal constant REGISTERER_PRIVATE_FUNCTION_BROADCASTED_ADDITIONAL_FIELDS = 19;
uint256 internal constant REGISTERER_UNCONSTRAINED_FUNCTION_BROADCASTED_ADDITIONAL_FIELDS = 12;
uint256 internal constant REGISTERER_CONTRACT_CLASS_REGISTERED_MAGIC_VALUE =
0x6999d1e02b08a447a463563453cb36919c9dd7150336fc7c4d2b52f8;
uint256 internal constant REGISTERER_PRIVATE_FUNCTION_BROADCASTED_MAGIC_VALUE =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ use dep::aztec::protocol_types::{
contract_class_id::ContractClassId,
constants::{
ARTIFACT_FUNCTION_TREE_MAX_HEIGHT, MAX_PACKED_BYTECODE_SIZE_PER_UNCONSTRAINED_FUNCTION_IN_FIELDS,
REGISTERER_UNCONSTRAINED_FUNCTION_BROADCASTED_MAGIC_VALUE
REGISTERER_UNCONSTRAINED_FUNCTION_BROADCASTED_MAGIC_VALUE,
REGISTERER_UNCONSTRAINED_FUNCTION_BROADCASTED_ADDITIONAL_FIELDS
},
traits::{Serialize}
traits::Serialize
};

struct UnconstrainedFunction {
Expand All @@ -33,22 +34,24 @@ struct ClassUnconstrainedFunctionBroadcasted {
artifact_metadata_hash: Field,
private_functions_artifact_tree_root: Field,
artifact_function_tree_sibling_path: [Field; ARTIFACT_FUNCTION_TREE_MAX_HEIGHT],
artifact_function_tree_leaf_index: Field,
function: UnconstrainedFunction
}

impl Serialize<MAX_PACKED_BYTECODE_SIZE_PER_UNCONSTRAINED_FUNCTION_IN_FIELDS + 11> for ClassUnconstrainedFunctionBroadcasted {
fn serialize(self: Self) -> [Field; MAX_PACKED_BYTECODE_SIZE_PER_UNCONSTRAINED_FUNCTION_IN_FIELDS + 11] {
let mut packed = [0; MAX_PACKED_BYTECODE_SIZE_PER_UNCONSTRAINED_FUNCTION_IN_FIELDS + 11];
impl Serialize<MAX_PACKED_BYTECODE_SIZE_PER_UNCONSTRAINED_FUNCTION_IN_FIELDS + REGISTERER_UNCONSTRAINED_FUNCTION_BROADCASTED_ADDITIONAL_FIELDS> for ClassUnconstrainedFunctionBroadcasted {
fn serialize(self: Self) -> [Field; MAX_PACKED_BYTECODE_SIZE_PER_UNCONSTRAINED_FUNCTION_IN_FIELDS + REGISTERER_UNCONSTRAINED_FUNCTION_BROADCASTED_ADDITIONAL_FIELDS] {
let mut packed = [0; MAX_PACKED_BYTECODE_SIZE_PER_UNCONSTRAINED_FUNCTION_IN_FIELDS + REGISTERER_UNCONSTRAINED_FUNCTION_BROADCASTED_ADDITIONAL_FIELDS];
packed[0] = REGISTERER_UNCONSTRAINED_FUNCTION_BROADCASTED_MAGIC_VALUE;
packed[1] = self.contract_class_id.to_field();
packed[2] = self.artifact_metadata_hash;
packed[3] = self.private_functions_artifact_tree_root;
for i in 0..ARTIFACT_FUNCTION_TREE_MAX_HEIGHT {
packed[i + 4] = self.artifact_function_tree_sibling_path[i];
}
packed[4 + ARTIFACT_FUNCTION_TREE_MAX_HEIGHT] = self.artifact_function_tree_leaf_index;
let packed_function = self.function.serialize();
for i in 0..MAX_PACKED_BYTECODE_SIZE_PER_UNCONSTRAINED_FUNCTION_IN_FIELDS + 2 {
packed[i + 4 + ARTIFACT_FUNCTION_TREE_MAX_HEIGHT] = packed_function[i];
packed[i + 5 + ARTIFACT_FUNCTION_TREE_MAX_HEIGHT] = packed_function[i];
}
packed
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,15 @@ contract ContractClassRegisterer {
artifact_metadata_hash: Field,
private_functions_artifact_tree_root: Field,
artifact_function_tree_sibling_path: [Field; ARTIFACT_FUNCTION_TREE_MAX_HEIGHT],
artifact_function_tree_leaf_index: Field,
function_data: UnconstrainedFunction
) {
let event = ClassUnconstrainedFunctionBroadcasted {
contract_class_id,
artifact_metadata_hash,
private_functions_artifact_tree_root,
artifact_function_tree_sibling_path,
artifact_function_tree_leaf_index,
function: function_data
};
dep::aztec::oracle::debug_log::debug_log_array_with_prefix(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ global MAX_PACKED_BYTECODE_SIZE_PER_PRIVATE_FUNCTION_IN_FIELDS: u64 = 3000;
global MAX_PACKED_BYTECODE_SIZE_PER_UNCONSTRAINED_FUNCTION_IN_FIELDS: u64 = 3000;
// How many fields are on the serialized ClassPrivateFunctionBroadcasted event in addition to MAX_PACKED_BYTECODE_SIZE_PER_PRIVATE_FUNCTION_IN_FIELDS.
global REGISTERER_PRIVATE_FUNCTION_BROADCASTED_ADDITIONAL_FIELDS: u64 = 19;
// How many fields are on the serialized ClassUnconstrainedFunctionBroadcasted event in addition to MAX_PACKED_BYTECODE_SIZE_PER_UNCONSTRAINED_FUNCTION_IN_FIELDS.
global REGISTERER_UNCONSTRAINED_FUNCTION_BROADCASTED_ADDITIONAL_FIELDS: u64 = 12;
// Since we are not yet emitting selectors we'll use this magic value to identify events emitted by the ClassRegisterer.
// This is just a stopgap until we implement proper selectors.
// sha224sum 'struct ContractClassRegistered {contract_class_id: ContractClassId, version: Field, artifact_hash: Field, private_functions_root: Field, packed_public_bytecode: [Field; MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS] }'
Expand Down
48 changes: 37 additions & 11 deletions yarn-project/archiver/src/archiver/archiver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ import { ContractClassRegisteredEvent, FunctionSelector } from '@aztec/circuits.
import {
ContractInstanceDeployedEvent,
PrivateFunctionBroadcastedEvent,
UnconstrainedFunctionBroadcastedEvent,
isValidPrivateFunctionMembershipProof,
isValidUnconstrainedFunctionMembershipProof,
} from '@aztec/circuits.js/contract';
import { createEthereumChain } from '@aztec/ethereum';
import { AztecAddress } from '@aztec/foundation/aztec-address';
Expand All @@ -29,7 +31,9 @@ import {
ContractClassPublic,
ContractDataSource,
ContractInstanceWithAddress,
ExecutablePrivateFunctionWithMembershipProof,
PublicFunction,
UnconstrainedFunctionWithMembershipProof,
} from '@aztec/types/contracts';

import groupBy from 'lodash.groupby';
Expand Down Expand Up @@ -310,24 +314,46 @@ export class Archiver implements ArchiveSource {
}

private async storeBroadcastedIndividualFunctions(allLogs: UnencryptedL2Log[], _blockNum: number) {
const events = PrivateFunctionBroadcastedEvent.fromLogs(allLogs, getCanonicalClassRegistererAddress());
for (const [classIdString, classEvents] of Object.entries(groupBy(events, e => e.contractClassId.toString()))) {
// Filter out private and unconstrained function broadcast events
const privateFnEvents = PrivateFunctionBroadcastedEvent.fromLogs(allLogs, getCanonicalClassRegistererAddress());
const unconstrainedFnEvents = UnconstrainedFunctionBroadcastedEvent.fromLogs(
allLogs,
getCanonicalClassRegistererAddress(),
);

// Group all events by contract class id
for (const [classIdString, classEvents] of Object.entries(
groupBy([...privateFnEvents, ...unconstrainedFnEvents], e => e.contractClassId.toString()),
)) {
const contractClassId = Fr.fromString(classIdString);
const contractClass = await this.store.getContractClass(contractClassId);
if (!contractClass) {
this.log.warn(`Skipping private functions as contract class ${contractClassId.toString()} was not found`);
this.log.warn(`Skipping broadcasted functions as contract class ${contractClassId.toString()} was not found`);
continue;
}
const validFns = classEvents
.map(e => e.toExecutableFunctionWithMembershipProof())
.filter(fn => isValidPrivateFunctionMembershipProof(fn, contractClass));
if (validFns.length !== classEvents.length) {
this.log.warn(`Skipping ${classEvents.length - validFns.length} invalid private functions`);

// Split private and unconstrained functions, and filter out invalid ones
const allFns = classEvents.map(e => e.toFunctionWithMembershipProof());
const privateFns = allFns.filter(
(fn): fn is ExecutablePrivateFunctionWithMembershipProof => 'unconstrainedFunctionsArtifactTreeRoot' in fn,
);
const unconstrainedFns = allFns.filter(
(fn): fn is UnconstrainedFunctionWithMembershipProof => 'privateFunctionsArtifactTreeRoot' in fn,
);
const validPrivateFns = privateFns.filter(fn => isValidPrivateFunctionMembershipProof(fn, contractClass));
const validUnconstrainedFns = unconstrainedFns.filter(fn =>
isValidUnconstrainedFunctionMembershipProof(fn, contractClass),
);
const validFnCount = validPrivateFns.length + validUnconstrainedFns.length;
if (validFnCount !== allFns.length) {
this.log.warn(`Skipping ${allFns.length - validFnCount} invalid functions`);
}
if (validFns.length > 0) {
this.log(`Storing ${validFns.length} private functions for contract class ${contractClassId.toString()}`);

// Store the functions in the contract class in a single operation
if (validFnCount > 0) {
this.log(`Storing ${validFnCount} functions for contract class ${contractClassId.toString()}`);
}
await this.store.addPrivateFunctions(contractClassId, validFns);
await this.store.addFunctions(contractClassId, validPrivateFns, validUnconstrainedFns);
}
}

Expand Down
4 changes: 3 additions & 1 deletion yarn-project/archiver/src/archiver/archiver_store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
ContractClassPublic,
ContractInstanceWithAddress,
ExecutablePrivateFunctionWithMembershipProof,
UnconstrainedFunctionWithMembershipProof,
} from '@aztec/types/contracts';

import { DataRetrieval } from './data_retrieval.js';
Expand Down Expand Up @@ -165,9 +166,10 @@ export interface ArchiverDataStore {
/**
* Adds private functions to a contract class.
*/
addPrivateFunctions(
addFunctions(
contractClassId: Fr,
privateFunctions: ExecutablePrivateFunctionWithMembershipProof[],
unconstrainedFunctions: UnconstrainedFunctionWithMembershipProof[],
): Promise<boolean>;

/**
Expand Down
27 changes: 23 additions & 4 deletions yarn-project/archiver/src/archiver/archiver_store_test_suite.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { InboxLeaf, L2Block, L2BlockContext, LogId, LogType, TxHash, UnencryptedL2Log } from '@aztec/circuit-types';
import '@aztec/circuit-types/jest';
import { AztecAddress, Fr, INITIAL_L2_BLOCK_NUM, L1_TO_L2_MSG_SUBTREE_HEIGHT } from '@aztec/circuits.js';
import { makeContractClassPublic, makeExecutablePrivateFunctionWithMembershipProof } from '@aztec/circuits.js/testing';
import {
makeContractClassPublic,
makeExecutablePrivateFunctionWithMembershipProof,
makeUnconstrainedFunctionWithMembershipProof,
} from '@aztec/circuits.js/testing';
import { times } from '@aztec/foundation/collection';
import { randomBytes, randomInt } from '@aztec/foundation/crypto';
import { ContractClassPublic, ContractInstanceWithAddress, SerializableContractInstance } from '@aztec/types/contracts';
Expand Down Expand Up @@ -246,18 +250,33 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch

it('adds new private functions', async () => {
const fns = times(3, makeExecutablePrivateFunctionWithMembershipProof);
await store.addPrivateFunctions(contractClass.id, fns);
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.addPrivateFunctions(contractClass.id, fns.slice(0, 1));
await store.addPrivateFunctions(contractClass.id, fns);
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 unconstrained functions', async () => {
const fns = times(3, makeUnconstrainedFunctionWithMembershipProof);
await store.addFunctions(contractClass.id, [], fns);
const stored = await store.getContractClass(contractClass.id);
expect(stored?.unconstrainedFunctions).toEqual(fns);
});

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

describe('getUnencryptedLogs', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { Fr, FunctionSelector, Vector } from '@aztec/circuits.js';
import { BufferReader, numToUInt8, serializeToBuffer } from '@aztec/foundation/serialize';
import { AztecKVStore, AztecMap } from '@aztec/kv-store';
import { ContractClassPublic, ExecutablePrivateFunctionWithMembershipProof } from '@aztec/types/contracts';
import {
ContractClassPublic,
ExecutablePrivateFunctionWithMembershipProof,
UnconstrainedFunctionWithMembershipProof,
} from '@aztec/types/contracts';

/**
* LMDB implementation of the ArchiverDataStore interface.
Expand All @@ -26,9 +30,10 @@ export class ContractClassStore {
return Array.from(this.#contractClasses.keys()).map(key => Fr.fromString(key));
}

async addPrivateFunctions(
async addFunctions(
contractClassId: Fr,
newPrivateFunctions: ExecutablePrivateFunctionWithMembershipProof[],
newUnconstrainedFunctions: UnconstrainedFunctionWithMembershipProof[],
): Promise<boolean> {
await this.db.transaction(() => {
const existingClassBuffer = this.#contractClasses.get(contractClassId.toString());
Expand All @@ -37,13 +42,19 @@ export class ContractClassStore {
}

const existingClass = deserializeContractClassPublic(existingClassBuffer);
const existingFns = existingClass.privateFunctions;
const { privateFunctions: existingPrivateFns, unconstrainedFunctions: existingUnconstrainedFns } = existingClass;

const updatedClass = {
const updatedClass: Omit<ContractClassPublic, 'id'> = {
...existingClass,
privateFunctions: [
...existingFns,
...newPrivateFunctions.filter(newFn => !existingFns.some(f => f.selector.equals(newFn.selector))),
...existingPrivateFns,
...newPrivateFunctions.filter(newFn => !existingPrivateFns.some(f => f.selector.equals(newFn.selector))),
],
unconstrainedFunctions: [
...existingUnconstrainedFns,
...newUnconstrainedFunctions.filter(
newFn => !existingUnconstrainedFns.some(f => f.selector.equals(newFn.selector)),
),
],
};
void this.#contractClasses.set(contractClassId.toString(), serializeContractClassPublic(updatedClass));
Expand All @@ -62,6 +73,8 @@ function serializeContractClassPublic(contractClass: Omit<ContractClassPublic, '
) ?? [],
contractClass.privateFunctions.length,
contractClass.privateFunctions.map(serializePrivateFunction),
contractClass.unconstrainedFunctions.length,
contractClass.unconstrainedFunctions.map(serializeUnconstrainedFunction),
contractClass.packedBytecode.length,
contractClass.packedBytecode,
contractClass.privateFunctionsRoot,
Expand All @@ -86,6 +99,19 @@ function serializePrivateFunction(fn: ExecutablePrivateFunctionWithMembershipPro
);
}

function serializeUnconstrainedFunction(fn: UnconstrainedFunctionWithMembershipProof): 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<ContractClassPublic, 'id'> {
const reader = BufferReader.asReader(buffer);
return {
Expand All @@ -99,6 +125,7 @@ function deserializeContractClassPublic(buffer: Buffer): Omit<ContractClassPubli
}),
}),
privateFunctions: reader.readVector({ fromBuffer: deserializePrivateFunction }),
unconstrainedFunctions: reader.readVector({ fromBuffer: deserializeUnconstrainedFunction }),
packedBytecode: reader.readBuffer(),
privateFunctionsRoot: reader.readObject(Fr),
};
Expand All @@ -120,3 +147,16 @@ function deserializePrivateFunction(buffer: Buffer | BufferReader): ExecutablePr
artifactTreeLeafIndex: reader.readNumber(),
};
}

function deserializeUnconstrainedFunction(buffer: Buffer | BufferReader): UnconstrainedFunctionWithMembershipProof {
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(),
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
ContractClassPublic,
ContractInstanceWithAddress,
ExecutablePrivateFunctionWithMembershipProof,
UnconstrainedFunctionWithMembershipProof,
} from '@aztec/types/contracts';

import { ArchiverDataStore, ArchiverL1SynchPoint } from '../archiver_store.js';
Expand Down Expand Up @@ -67,11 +68,12 @@ export class KVArchiverDataStore implements ArchiverDataStore {
return (await Promise.all(data.map(c => this.#contractClassStore.addContractClass(c)))).every(Boolean);
}

addPrivateFunctions(
addFunctions(
contractClassId: Fr,
privateFunctions: ExecutablePrivateFunctionWithMembershipProof[],
unconstrainedFunctions: UnconstrainedFunctionWithMembershipProof[],
): Promise<boolean> {
return this.#contractClassStore.addPrivateFunctions(contractClassId, privateFunctions);
return this.#contractClassStore.addFunctions(contractClassId, privateFunctions, unconstrainedFunctions);
}

async addContractInstances(data: ContractInstanceWithAddress[], _blockNumber: number): Promise<boolean> {
Expand Down
Loading