Skip to content

Commit

Permalink
feat: Capture broadcasted unconstrained functions in node
Browse files Browse the repository at this point in the history
  • Loading branch information
spalladino committed Mar 20, 2024
1 parent 516576f commit a6738ed
Show file tree
Hide file tree
Showing 32 changed files with 587 additions and 116 deletions.
8 changes: 4 additions & 4 deletions l1-contracts/slither_output.md
Original file line number Diff line number Diff line change
Expand Up @@ -258,15 +258,15 @@ solc-0.8.23 is not recommended for deployment
Impact: Informational
Confidence: Medium
- [ ] ID-27
Variable [Constants.LOGS_HASHES_NUM_BYTES_PER_BASE_ROLLUP](src/core/libraries/ConstantsGen.sol#L131) is too similar to [Constants.NOTE_HASHES_NUM_BYTES_PER_BASE_ROLLUP](src/core/libraries/ConstantsGen.sol#L124)
Variable [Constants.LOGS_HASHES_NUM_BYTES_PER_BASE_ROLLUP](src/core/libraries/ConstantsGen.sol#L132) is too similar to [Constants.NOTE_HASHES_NUM_BYTES_PER_BASE_ROLLUP](src/core/libraries/ConstantsGen.sol#L125)

src/core/libraries/ConstantsGen.sol#L131
src/core/libraries/ConstantsGen.sol#L132


- [ ] ID-28
Variable [Constants.L1_TO_L2_MESSAGE_LENGTH](src/core/libraries/ConstantsGen.sol#L111) is too similar to [Constants.L2_TO_L1_MESSAGE_LENGTH](src/core/libraries/ConstantsGen.sol#L112)
Variable [Constants.L1_TO_L2_MESSAGE_LENGTH](src/core/libraries/ConstantsGen.sol#L112) is too similar to [Constants.L2_TO_L1_MESSAGE_LENGTH](src/core/libraries/ConstantsGen.sol#L113)

src/core/libraries/ConstantsGen.sol#L111
src/core/libraries/ConstantsGen.sol#L112


- [ ] ID-29
Expand Down
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
45 changes: 34 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 @@ -307,24 +311,43 @@ export class Archiver implements ArchiveSource {
}

private async storeBroadcastedIndividualFunctions(allLogs: UnencryptedL2Log[], _blockNum: number) {
const events = PrivateFunctionBroadcastedEvent.fromLogs(allLogs, ClassRegistererAddress);
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, ClassRegistererAddress);
const unconstrainedFnEvents = UnconstrainedFunctionBroadcastedEvent.fromLogs(allLogs, ClassRegistererAddress);

// 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<void> {
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 @@ -61,6 +72,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 @@ -85,6 +98,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 @@ -98,6 +124,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 @@ -119,3 +146,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);
}

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

Expand Down
Loading

0 comments on commit a6738ed

Please sign in to comment.