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 classes and instances #4192

Merged
merged 6 commits into from
Jan 25, 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
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
Loading