Skip to content

Commit

Permalink
feat: Contract classes and instances (AztecProtocol#4192)
Browse files Browse the repository at this point in the history
Adds `ContractInstance` and `ContractClass` interfaces as specified in
the yellow paper, with the addition of `PublicFunctions` in
`ContractClasses` until we remove them as first class citizens from the
protocol. Also includes hashes for contract classes and artifacts.
Stores contract classes and instances in the node in new tables, as well
as artifacts and instances in the pxe.

Fixes AztecProtocol#4053
  • Loading branch information
spalladino authored Jan 25, 2024
1 parent 2c6ce71 commit b2fb0ec
Show file tree
Hide file tree
Showing 44 changed files with 2,729 additions and 13 deletions.
3 changes: 2 additions & 1 deletion yarn-project/archiver/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
"@aztec/foundation": "workspace:^",
"@aztec/kv-store": "workspace:^",
"@aztec/l1-artifacts": "workspace:^",
"@types/lodash.omit": "^4.5.7",
"@aztec/types": "workspace:^",
"debug": "^4.3.4",
"lmdb": "^2.9.1",
"lodash.omit": "^4.5.0",
Expand All @@ -54,6 +54,7 @@
"@jest/globals": "^29.5.0",
"@types/debug": "^4.1.7",
"@types/jest": "^29.5.0",
"@types/lodash.omit": "^4.5.7",
"@types/node": "^18.15.11",
"@types/ws": "^8.5.4",
"concurrently": "^8.0.1",
Expand Down
58 changes: 57 additions & 1 deletion yarn-project/archiver/src/archiver/archiver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,20 @@ import {
LogType,
TxHash,
} from '@aztec/circuit-types';
import { FunctionSelector, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP } from '@aztec/circuits.js';
import { FunctionSelector, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP, getContractClassId } from '@aztec/circuits.js';
import { createEthereumChain } from '@aztec/ethereum';
import { AztecAddress } from '@aztec/foundation/aztec-address';
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,
ContractInstance,
ContractInstanceWithAddress,
} from '@aztec/types/contracts';

import omit from 'lodash.omit';
import { Chain, HttpTransport, PublicClient, createPublicClient, http } from 'viem';
Expand Down Expand Up @@ -268,6 +274,7 @@ export class Archiver implements L2BlockSource, L2LogsSource, ContractDataSource
this.log(`Retrieved extended contract data for l2 block number: ${l2BlockNum}`);
if (l2BlockNum <= lastKnownL2BlockNum) {
await this.store.addExtendedContractData(contracts, l2BlockNum);
await this.storeContractDataAsClassesAndInstances(contracts, l2BlockNum);
}
}),
);
Expand All @@ -294,6 +301,24 @@ export class Archiver implements L2BlockSource, L2LogsSource, ContractDataSource
);
}

/**
* Stores extended contract data as classes and instances.
* Temporary solution until we source this data from the contract class registerer and instance deployer.
* @param contracts - The extended contract data to be stored.
* @param l2BlockNum - The L2 block number to which the contract data corresponds.
*/
async storeContractDataAsClassesAndInstances(contracts: ExtendedContractData[], l2BlockNum: number) {
const classesAndInstances = contracts.map(extendedContractDataToContractClassAndInstance);
await this.store.addContractClasses(
classesAndInstances.map(([c, _]) => c),
l2BlockNum,
);
await this.store.addContractInstances(
classesAndInstances.map(([_, i]) => i),
l2BlockNum,
);
}

/**
* Stops the archiver.
* @returns A promise signalling completion of the stop process.
Expand Down Expand Up @@ -440,3 +465,34 @@ export class Archiver implements L2BlockSource, L2LogsSource, ContractDataSource
return this.store.getConfirmedL1ToL2Message(messageKey);
}
}

/** Converts ExtendedContractData into contract classes and instances. */
function extendedContractDataToContractClassAndInstance(
data: ExtendedContractData,
): [ContractClassWithId, ContractInstanceWithAddress] {
const contractClass: ContractClass = {
version: 1,
artifactHash: Fr.ZERO,
publicFunctions: data.publicFunctions.map(f => ({
selector: f.selector,
bytecode: f.bytecode,
isInternal: f.isInternal,
})),
privateFunctions: [],
packedBytecode: data.bytecode,
};
const contractClassId = getContractClassId(contractClass);
const contractInstance: ContractInstance = {
version: 1,
salt: Fr.ZERO,
contractClassId,
initializationHash: Fr.ZERO,
portalContractAddress: data.contractData.portalContractAddress,
publicKeysHash: data.partialAddress,
};
const address = data.contractData.contractAddress;
return [
{ ...contractClass, id: contractClassId },
{ ...contractInstance, address },
];
}
29 changes: 29 additions & 0 deletions yarn-project/archiver/src/archiver/archiver_store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +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';

/**
* Represents the latest L1 block processed by the archiver for various objects in L2.
Expand Down Expand Up @@ -167,4 +168,32 @@ export interface ArchiverDataStore {
* Gets the last L1 block number processed by the archiver
*/
getL1BlockNumber(): Promise<ArchiverL1SynchPoint>;

/**
* Add new contract classes from an L2 block to the store's list.
* @param data - List of contract classes to be added.
* @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>;

/**
* 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>;

/**
* Add new contract instances from an L2 block to the store's list.
* @param data - List of contract instances to be added.
* @param blockNumber - Number of the L2 block the instances were deployed in.
* @returns True if the operation is successful.
*/
addContractInstances(data: ContractInstanceWithAddress[], blockNumber: number): Promise<boolean>;

/**
* Returns a contract instance given its address, or undefined if not exists.
* @param address - Address of the contract.
*/
getContractInstance(address: AztecAddress): Promise<ContractInstanceWithAddress | undefined>;
}
42 changes: 42 additions & 0 deletions yarn-project/archiver/src/archiver/archiver_store_test_suite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ import {
import '@aztec/circuit-types/jest';
import { AztecAddress, Fr } from '@aztec/circuits.js';
import { randomBytes } from '@aztec/foundation/crypto';
import {
ContractClassWithId,
ContractInstanceWithAddress,
SerializableContractClass,
SerializableContractInstance,
} from '@aztec/types/contracts';

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

Expand Down Expand Up @@ -320,6 +326,42 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
});
});

describe('contractInstances', () => {
let contractInstance: ContractInstanceWithAddress;
const blockNum = 10;

beforeEach(async () => {
contractInstance = { ...SerializableContractInstance.random(), address: AztecAddress.random() };
await store.addContractInstances([contractInstance], blockNum);
});

it('returns previously stored contract instances', async () => {
await expect(store.getContractInstance(contractInstance.address)).resolves.toMatchObject(contractInstance);
});

it('returns undefined if contract instance is not found', async () => {
await expect(store.getContractInstance(AztecAddress.random())).resolves.toBeUndefined();
});
});

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

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

it('returns previously stored contract class', async () => {
await expect(store.getContractClass(contractClass.id)).resolves.toMatchObject(contractClass);
});

it('returns undefined if contract class is not found', async () => {
await expect(store.getContractClass(Fr.random())).resolves.toBeUndefined();
});
});

describe('getContractData', () => {
let block: L2Block;
beforeEach(async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { AztecAddress } from '@aztec/circuits.js';
import { createDebugLogger } from '@aztec/foundation/log';
import { AztecKVStore, AztecMap, Range } from '@aztec/kv-store';

/* eslint-disable */
type BlockIndexValue = [blockNumber: number, index: number];

type BlockContext = {
Expand All @@ -12,7 +11,6 @@ type BlockContext = {
block: Buffer;
blockHash: Buffer;
};
/* eslint-enable */

/**
* LMDB implementation of the ArchiverDataStore interface.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Fr } from '@aztec/foundation/fields';
import { AztecKVStore, AztecMap } from '@aztec/kv-store';
import { ContractClassWithId, SerializableContractClass } from '@aztec/types/contracts';

/**
* LMDB implementation of the ArchiverDataStore interface.
*/
export class ContractClassStore {
#contractClasses: AztecMap<string, Buffer>;

constructor(db: AztecKVStore) {
this.#contractClasses = db.createMap('archiver_contract_classes');
}

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

getContractClass(id: Fr): ContractClassWithId | undefined {
const contractClass = this.#contractClasses.get(id.toString());
return contractClass && SerializableContractClass.fromBuffer(contractClass).withId(id);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { AztecAddress } from '@aztec/circuits.js';
import { AztecKVStore, AztecMap } from '@aztec/kv-store';
import { ContractInstanceWithAddress, SerializableContractInstance } from '@aztec/types/contracts';

/**
* LMDB implementation of the ArchiverDataStore interface.
*/
export class ContractInstanceStore {
#contractInstances: AztecMap<string, Buffer>;

constructor(db: AztecKVStore) {
this.#contractInstances = db.createMap('archiver_contract_instances');
}

addContractInstance(contractInstance: ContractInstanceWithAddress): Promise<boolean> {
return this.#contractInstances.set(
contractInstance.address.toString(),
new SerializableContractInstance(contractInstance).toBuffer(),
);
}

getContractInstance(address: AztecAddress): ContractInstanceWithAddress | undefined {
const contractInstance = this.#contractInstances.get(address.toString());
return contractInstance && SerializableContractInstance.fromBuffer(contractInstance).withAddress(address);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@ 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 { ArchiverDataStore, ArchiverL1SynchPoint } from '../archiver_store.js';
import { BlockStore } from './block_store.js';
import { ContractClassStore } from './contract_class_store.js';
import { ContractInstanceStore } from './contract_instance_store.js';
import { ContractStore } from './contract_store.js';
import { LogStore } from './log_store.js';
import { MessageStore } from './message_store.js';
Expand All @@ -29,6 +32,8 @@ export class KVArchiverDataStore implements ArchiverDataStore {
#logStore: LogStore;
#contractStore: ContractStore;
#messageStore: MessageStore;
#contractClassStore: ContractClassStore;
#contractInstanceStore: ContractInstanceStore;

#log = createDebugLogger('aztec:archiver:lmdb');

Expand All @@ -37,6 +42,24 @@ export class KVArchiverDataStore implements ArchiverDataStore {
this.#logStore = new LogStore(db, this.#blockStore, logsMaxPageSize);
this.#contractStore = new ContractStore(db, this.#blockStore);
this.#messageStore = new MessageStore(db);
this.#contractClassStore = new ContractClassStore(db);
this.#contractInstanceStore = new ContractInstanceStore(db);
}

getContractClass(id: Fr): Promise<ContractClassWithId | 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> {
return (await Promise.all(data.map(c => this.#contractClassStore.addContractClass(c)))).every(Boolean);
}

async addContractInstances(data: ContractInstanceWithAddress[], _blockNumber: number): Promise<boolean> {
return (await Promise.all(data.map(c => this.#contractInstanceStore.addContractInstance(c)))).every(Boolean);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +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 { ArchiverDataStore } from '../archiver_store.js';
import { L1ToL2MessageStore, PendingL1ToL2MessageStore } from './l1_to_l2_message_store.js';
Expand Down Expand Up @@ -68,6 +69,10 @@ export class MemoryArchiverStore implements ArchiverDataStore {
*/
private pendingL1ToL2Messages: PendingL1ToL2MessageStore = new PendingL1ToL2MessageStore();

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

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

private lastL1BlockAddedMessages: bigint = 0n;
private lastL1BlockCancelledMessages: bigint = 0n;

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

public getContractClass(id: Fr): Promise<ContractClassWithId | 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> {
for (const contractClass of data) {
this.contractClasses.set(contractClass.id.toString(), contractClass);
}
return Promise.resolve(true);
}

public addContractInstances(data: ContractInstanceWithAddress[], _blockNumber: number): Promise<boolean> {
for (const contractInstance of data) {
this.contractInstances.set(contractInstance.address.toString(), contractInstance);
}
return Promise.resolve(true);
}

/**
* Append new blocks to the store's list.
* @param blocks - The L2 blocks to be added to the store.
Expand Down
3 changes: 3 additions & 0 deletions yarn-project/archiver/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
},
{
"path": "../l1-artifacts"
},
{
"path": "../types"
}
],
"include": ["src"]
Expand Down
2 changes: 1 addition & 1 deletion yarn-project/circuit-types/src/contract_data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ export class ExtendedContractData {
/** The base contract data: aztec & portal addresses. */
public contractData: ContractData,
/** Artifacts of public functions. */
private publicFunctions: EncodedContractFunction[],
public readonly publicFunctions: EncodedContractFunction[],
/** Partial addresses of the contract. */
public readonly partialAddress: PartialAddress,
/** Public key of the contract. */
Expand Down
Loading

0 comments on commit b2fb0ec

Please sign in to comment.