Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Contract class registerer contract #4403

Merged
merged 9 commits into from
Feb 5, 2024
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
5 changes: 4 additions & 1 deletion l1-contracts/src/core/libraries/ConstantsGen.sol
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,10 @@ library Constants {
uint256 internal constant MAPPING_SLOT_PEDERSEN_SEPARATOR = 4;
uint256 internal constant NUM_FIELDS_PER_SHA256 = 2;
uint256 internal constant ARGS_HASH_CHUNK_LENGTH = 32;
uint256 internal constant ARGS_HASH_CHUNK_COUNT = 16;
uint256 internal constant ARGS_HASH_CHUNK_COUNT = 32;
uint256 internal constant MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS = 1000;
uint256 internal constant CONTRACT_CLASS_REGISTERED_MAGIC_VALUE =
0x6999d1e02b08a447a463563453cb36919c9dd7150336fc7c4d2b52f8;
uint256 internal constant L1_TO_L2_MESSAGE_LENGTH = 8;
uint256 internal constant L1_TO_L2_MESSAGE_ORACLE_CALL_LENGTH = 25;
uint256 internal constant MAX_NOTE_FIELDS_LENGTH = 20;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,8 @@ export class ClientExecutionContext extends ViewDataOracle {
*/
public emitUnencryptedLog(log: UnencryptedL2Log) {
this.unencryptedLogs.push(log);
this.log(`Emitted unencrypted log: "${log.toHumanReadable()}"`);
const text = log.toHumanReadable();
this.log(`Emitted unencrypted log: "${text.length > 100 ? text.slice(0, 100) + '...' : text}"`);
}

/**
Expand Down
62 changes: 58 additions & 4 deletions yarn-project/archiver/src/archiver/archiver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,25 @@ import {
LogFilter,
LogType,
TxHash,
UnencryptedL2Log,
} from '@aztec/circuit-types';
import { FunctionSelector, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP } from '@aztec/circuits.js';
import {
CONTRACT_CLASS_REGISTERED_MAGIC_VALUE,
ContractClassRegisteredEvent,
FunctionSelector,
NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP,
} from '@aztec/circuits.js';
import { createEthereumChain } from '@aztec/ethereum';
import { AztecAddress } from '@aztec/foundation/aztec-address';
import { toBigIntBE } from '@aztec/foundation/bigint-buffer';
import { padArrayEnd } from '@aztec/foundation/collection';
import { EthAddress } from '@aztec/foundation/eth-address';
import { Fr } from '@aztec/foundation/fields';
import { DebugLogger, createDebugLogger } from '@aztec/foundation/log';
import { RunningPromise } from '@aztec/foundation/running-promise';
import {
ContractClass,
ContractClassWithId,
ContractClassPublic,
ContractInstance,
ContractInstanceWithAddress,
} from '@aztec/types/contracts';
Expand Down Expand Up @@ -63,6 +70,12 @@ export class Archiver implements ArchiveSource {
*/
private lastLoggedL1BlockNumber = 0n;

// TODO(@spalladino): Calculate this on the fly somewhere else!
/** Address of the ClassRegisterer contract with a salt=1 */
private classRegistererAddress = AztecAddress.fromString(
'0x1c9f737a5ab5a7bb5ea970ba40737d44dc22fbcbe19fd8171429f2c2c433afb5',
);

/**
* Creates a new instance of the Archiver.
* @param publicClient - A client for interacting with the Ethereum node.
Expand Down Expand Up @@ -272,6 +285,16 @@ export class Archiver implements ArchiveSource {
),
);

// Unroll all logs emitted during the retrieved blocks and extract any contract classes from them
await Promise.all(
retrievedBlocks.retrievedData.map(async block => {
const blockLogs = (block.newUnencryptedLogs?.txLogs ?? [])
.flatMap(txLog => txLog.unrollLogs())
.map(log => UnencryptedL2Log.fromBuffer(log));
await this.storeRegisteredContractClasses(blockLogs, block.number);
}),
);

// store contracts for which we have retrieved L2 blocks
const lastKnownL2BlockNum = retrievedBlocks.retrievedData[retrievedBlocks.retrievedData.length - 1].number;
await Promise.all(
Expand Down Expand Up @@ -302,6 +325,33 @@ export class Archiver implements ArchiveSource {
);
}

/**
* Extracts and stores contract classes out of ContractClassRegistered events emitted by the class registerer contract.
* @param allLogs - All logs emitted in a bunch of blocks.
*/
private async storeRegisteredContractClasses(allLogs: UnencryptedL2Log[], blockNum: number) {
const contractClasses: ContractClassPublic[] = [];
for (const log of allLogs) {
try {
if (
!log.contractAddress.equals(this.classRegistererAddress) ||
toBigIntBE(log.data.subarray(0, 32)) !== CONTRACT_CLASS_REGISTERED_MAGIC_VALUE
) {
continue;
}
const event = ContractClassRegisteredEvent.fromLogData(log.data);
contractClasses.push(event.toContractClassPublic());
} catch (err) {
this.log.warn(`Error processing log ${log.toHumanReadable()}: ${err}`);
}
}

if (contractClasses.length > 0) {
contractClasses.forEach(c => this.log(`Registering contract class ${c.id.toString()}`));
await this.store.addContractClasses(contractClasses, blockNum);
}
}

/**
* Stores extended contract data as classes and instances.
* Temporary solution until we source this data from the contract class registerer and instance deployer.
Expand Down Expand Up @@ -448,6 +498,10 @@ export class Archiver implements ArchiveSource {
return this.store.getBlockNumber();
}

public getContractClass(id: Fr): Promise<ContractClassPublic | undefined> {
return this.store.getContractClass(id);
}

/**
* Gets up to `limit` amount of pending L1 to L2 messages.
* @param limit - The number of messages to return.
Expand Down Expand Up @@ -475,7 +529,7 @@ export class Archiver implements ArchiveSource {
*/
function extendedContractDataToContractClassAndInstance(
data: ExtendedContractData,
): [ContractClassWithId, ContractInstanceWithAddress] {
): [ContractClassPublic, ContractInstanceWithAddress] {
const contractClass: ContractClass = {
version: 1,
artifactHash: Fr.ZERO,
Expand All @@ -498,7 +552,7 @@ function extendedContractDataToContractClassAndInstance(
};
const address = data.contractData.contractAddress;
return [
{ ...contractClass, id: contractClassId },
{ ...contractClass, id: contractClassId, privateFunctionsRoot: Fr.ZERO },
{ ...contractInstance, address },
];
}
6 changes: 3 additions & 3 deletions yarn-project/archiver/src/archiver/archiver_store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
} from '@aztec/circuit-types';
import { Fr } from '@aztec/circuits.js';
import { AztecAddress } from '@aztec/foundation/aztec-address';
import { ContractClassWithId, ContractInstanceWithAddress } from '@aztec/types/contracts';
import { ContractClassPublic, ContractInstanceWithAddress } from '@aztec/types/contracts';

/**
* Represents the latest L1 block processed by the archiver for various objects in L2.
Expand Down Expand Up @@ -175,13 +175,13 @@ export interface ArchiverDataStore {
* @param blockNumber - Number of the L2 block the contracts were registered in.
* @returns True if the operation is successful.
*/
addContractClasses(data: ContractClassWithId[], blockNumber: number): Promise<boolean>;
addContractClasses(data: ContractClassPublic[], blockNumber: number): Promise<boolean>;

/**
* Returns a contract class given its id, or undefined if not exists.
* @param id - Id of the contract class.
*/
getContractClass(id: Fr): Promise<ContractClassWithId | undefined>;
getContractClass(id: Fr): Promise<ContractClassPublic | undefined>;

/**
* Add new contract instances from an L2 block to the store's list.
Expand Down
12 changes: 4 additions & 8 deletions yarn-project/archiver/src/archiver/archiver_store_test_suite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,9 @@ import {
} from '@aztec/circuit-types';
import '@aztec/circuit-types/jest';
import { AztecAddress, Fr } from '@aztec/circuits.js';
import { makeContractClassPublic } from '@aztec/circuits.js/factories';
import { randomBytes } from '@aztec/foundation/crypto';
import {
ContractClassWithId,
ContractInstanceWithAddress,
SerializableContractClass,
SerializableContractInstance,
} from '@aztec/types/contracts';
import { ContractClassPublic, ContractInstanceWithAddress, SerializableContractInstance } from '@aztec/types/contracts';

import { ArchiverDataStore } from './archiver_store.js';

Expand Down Expand Up @@ -345,11 +341,11 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
});

describe('contractClasses', () => {
let contractClass: ContractClassWithId;
let contractClass: ContractClassPublic;
const blockNum = 10;

beforeEach(async () => {
contractClass = { ...SerializableContractClass.random(), id: Fr.random() };
contractClass = makeContractClassPublic();
await store.addContractClasses([contractClass], blockNum);
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Fr } from '@aztec/foundation/fields';
import { Fr, FunctionSelector } from '@aztec/circuits.js';
import { BufferReader, numToUInt8, serializeToBuffer } from '@aztec/foundation/serialize';
import { AztecKVStore, AztecMap } from '@aztec/kv-store';
import { ContractClassWithId, SerializableContractClass } from '@aztec/types/contracts';
import { ContractClassPublic } from '@aztec/types/contracts';

/**
* LMDB implementation of the ArchiverDataStore interface.
Expand All @@ -12,15 +13,52 @@ export class ContractClassStore {
this.#contractClasses = db.openMap('archiver_contract_classes');
}

addContractClass(contractClass: ContractClassWithId): Promise<boolean> {
return this.#contractClasses.set(
contractClass.id.toString(),
new SerializableContractClass(contractClass).toBuffer(),
);
addContractClass(contractClass: ContractClassPublic): Promise<boolean> {
return this.#contractClasses.set(contractClass.id.toString(), serializeContractClassPublic(contractClass));
}

getContractClass(id: Fr): ContractClassWithId | undefined {
getContractClass(id: Fr): ContractClassPublic | undefined {
const contractClass = this.#contractClasses.get(id.toString());
return contractClass && SerializableContractClass.fromBuffer(contractClass).withId(id);
return contractClass && { ...deserializeContractClassPublic(contractClass), id };
}
}

export function serializeContractClassPublic(contractClass: ContractClassPublic): Buffer {
return serializeToBuffer(
numToUInt8(contractClass.version),
contractClass.artifactHash,
contractClass.privateFunctions?.length ?? 0,
contractClass.privateFunctions?.map(f => serializeToBuffer(f.selector, f.vkHash, f.isInternal)) ?? [],
contractClass.publicFunctions.length,
contractClass.publicFunctions?.map(f =>
serializeToBuffer(f.selector, f.bytecode.length, f.bytecode, f.isInternal),
) ?? [],
contractClass.packedBytecode.length,
contractClass.packedBytecode,
contractClass.privateFunctionsRoot,
);
}

export function deserializeContractClassPublic(buffer: Buffer): Omit<ContractClassPublic, 'id'> {
const reader = BufferReader.asReader(buffer);
return {
version: reader.readUInt8() as 1,
artifactHash: reader.readObject(Fr),
privateFunctions: reader.readVector({
fromBuffer: reader => ({
selector: reader.readObject(FunctionSelector),
vkHash: reader.readObject(Fr),
isInternal: reader.readBoolean(),
}),
}),
publicFunctions: reader.readVector({
fromBuffer: reader => ({
selector: reader.readObject(FunctionSelector),
bytecode: reader.readBuffer(),
isInternal: reader.readBoolean(),
}),
}),
packedBytecode: reader.readBuffer(),
privateFunctionsRoot: reader.readObject(Fr),
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { Fr } from '@aztec/circuits.js';
import { AztecAddress } from '@aztec/foundation/aztec-address';
import { createDebugLogger } from '@aztec/foundation/log';
import { AztecKVStore } from '@aztec/kv-store';
import { ContractClassWithId, ContractInstanceWithAddress } from '@aztec/types/contracts';
import { ContractClassPublic, ContractInstanceWithAddress } from '@aztec/types/contracts';

import { ArchiverDataStore, ArchiverL1SynchPoint } from '../archiver_store.js';
import { BlockStore } from './block_store.js';
Expand Down Expand Up @@ -46,15 +46,15 @@ export class KVArchiverDataStore implements ArchiverDataStore {
this.#contractInstanceStore = new ContractInstanceStore(db);
}

getContractClass(id: Fr): Promise<ContractClassWithId | undefined> {
getContractClass(id: Fr): Promise<ContractClassPublic | undefined> {
return Promise.resolve(this.#contractClassStore.getContractClass(id));
}

getContractInstance(address: AztecAddress): Promise<ContractInstanceWithAddress | undefined> {
return Promise.resolve(this.#contractInstanceStore.getContractInstance(address));
}

async addContractClasses(data: ContractClassWithId[], _blockNumber: number): Promise<boolean> {
async addContractClasses(data: ContractClassPublic[], _blockNumber: number): Promise<boolean> {
return (await Promise.all(data.map(c => this.#contractClassStore.addContractClass(c)))).every(Boolean);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
} from '@aztec/circuit-types';
import { Fr, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP } from '@aztec/circuits.js';
import { AztecAddress } from '@aztec/foundation/aztec-address';
import { ContractClassWithId, ContractInstanceWithAddress } from '@aztec/types/contracts';
import { ContractClassPublic, ContractInstanceWithAddress } from '@aztec/types/contracts';

import { ArchiverDataStore } from '../archiver_store.js';
import { L1ToL2MessageStore, PendingL1ToL2MessageStore } from './l1_to_l2_message_store.js';
Expand Down Expand Up @@ -69,7 +69,7 @@ export class MemoryArchiverStore implements ArchiverDataStore {
*/
private pendingL1ToL2Messages: PendingL1ToL2MessageStore = new PendingL1ToL2MessageStore();

private contractClasses: Map<string, ContractClassWithId> = new Map();
private contractClasses: Map<string, ContractClassPublic> = new Map();

private contractInstances: Map<string, ContractInstanceWithAddress> = new Map();

Expand All @@ -81,15 +81,15 @@ export class MemoryArchiverStore implements ArchiverDataStore {
public readonly maxLogs: number,
) {}

public getContractClass(id: Fr): Promise<ContractClassWithId | undefined> {
public getContractClass(id: Fr): Promise<ContractClassPublic | undefined> {
return Promise.resolve(this.contractClasses.get(id.toString()));
}

public getContractInstance(address: AztecAddress): Promise<ContractInstanceWithAddress | undefined> {
return Promise.resolve(this.contractInstances.get(address.toString()));
}

public addContractClasses(data: ContractClassWithId[], _blockNumber: number): Promise<boolean> {
public addContractClasses(data: ContractClassPublic[], _blockNumber: number): Promise<boolean> {
for (const contractClass of data) {
this.contractClasses.set(contractClass.id.toString(), contractClass);
}
Expand Down
5 changes: 5 additions & 0 deletions yarn-project/aztec-node/src/aztec-node/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import {
SequencerClient,
getGlobalVariableBuilder,
} from '@aztec/sequencer-client';
import { ContractClassPublic } from '@aztec/types/contracts';
import {
MerkleTrees,
ServerWorldStateSynchronizer,
Expand Down Expand Up @@ -237,6 +238,10 @@ export class AztecNodeService implements AztecNode {
return await this.contractDataSource.getContractData(contractAddress);
}

public getContractClass(id: Fr): Promise<ContractClassPublic | undefined> {
return this.contractDataSource.getContractClass(id);
}

/**
* Gets up to `limit` amount of logs starting from `from`.
* @param from - Number of the L2 block to which corresponds the first logs to be returned.
Expand Down
7 changes: 7 additions & 0 deletions yarn-project/circuit-types/src/contract_data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
serializeBufferArrayToVector,
serializeToBuffer,
} from '@aztec/foundation/serialize';
import { ContractClassPublic } from '@aztec/types/contracts';

/**
* Used for retrieval of contract data (A3 address, portal contract address, bytecode).
Expand Down Expand Up @@ -55,6 +56,12 @@ export interface ContractDataSource {
* @returns The number of the latest L2 block processed by the implementation.
*/
getBlockNumber(): Promise<number>;

/**
* Returns the contract class for a given contract class id, or undefined if not found.
* @param id - Contract class id.
*/
getContractClass(id: Fr): Promise<ContractClassPublic | undefined>;
}

/**
Expand Down
7 changes: 7 additions & 0 deletions yarn-project/circuit-types/src/interfaces/aztec-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Header } from '@aztec/circuits.js';
import { L1ContractAddresses } from '@aztec/ethereum';
import { AztecAddress } from '@aztec/foundation/aztec-address';
import { Fr } from '@aztec/foundation/fields';
import { ContractClassPublic } from '@aztec/types/contracts';

import { ContractData, ExtendedContractData } from '../contract_data.js';
import { L2Block } from '../l2_block.js';
Expand Down Expand Up @@ -136,4 +137,10 @@ export interface AztecNode extends StateInfoProvider {
* @param config - Updated configuration to be merged with the current one.
*/
setConfig(config: Partial<SequencerConfig>): Promise<void>;

/**
* Returns a registered contract class given its id.
* @param id - Id of the contract class.
*/
getContractClass(id: Fr): Promise<ContractClassPublic | undefined>;
}
Loading
Loading