Skip to content

Commit

Permalink
feat: Speed up transaction execution (#10172)
Browse files Browse the repository at this point in the history
This PR make a number of optimisation related to the speed up of
transaction execution. Namely:

1. We don't re-initialise the instruction set mapping with each
instruction decode.
2. We now compute public bytecode commitments at the point of receiving
a contract and persist them, meaning they don't need to be computed at
the point of execution.
3. We only store and iterate opcode and program counter tally
information when in debug.
4. Function names are also cached at the point contract artifacts are
shared with the node.
5. World state status summary and previous block archive roots are
cached to reduce the impact on the world state DB whilst execution is
taking place.
  • Loading branch information
PhilWindle authored Nov 26, 2024
1 parent 12744d6 commit da265b6
Show file tree
Hide file tree
Showing 32 changed files with 506 additions and 94 deletions.
14 changes: 14 additions & 0 deletions barretenberg/cpp/src/barretenberg/crypto/poseidon2/c_bind.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,17 @@ WASM_EXPORT void poseidon2_permutation(fr::vec_in_buf inputs_buffer, fr::vec_out
const std::vector<fr> results(results_array.begin(), results_array.end());
*output = to_heap_buffer(results);
}

WASM_EXPORT void poseidon2_hash_accumulate(fr::vec_in_buf inputs_buffer, fr::out_buf output)
{
std::vector<fr> to_hash;
read(inputs_buffer, to_hash);
const size_t numHashes = to_hash.size();
fr result = 0;
size_t count = 0;
while (count < numHashes) {
result = crypto::Poseidon2<crypto::Poseidon2Bn254ScalarFieldParams>::hash({ to_hash[count], result });
++count;
}
write(output, result);
}
12 changes: 12 additions & 0 deletions barretenberg/ts/src/barretenberg_api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -704,6 +704,18 @@ export class BarretenbergApiSync {
return out[0];
}

poseidon2HashAccumulate(inputsBuffer: Fr[]): Fr {
const inArgs = [inputsBuffer].map(serializeBufferable);
const outTypes: OutputType[] = [Fr];
const result = this.wasm.callWasmExport(
'poseidon2_hash_accumulate',
inArgs,
outTypes.map(t => t.SIZE_IN_BYTES),
);
const out = result.map((r, i) => outTypes[i].fromBuffer(r));
return out[0];
}

poseidon2Hashes(inputsBuffer: Fr[]): Fr {
const inArgs = [inputsBuffer].map(serializeBufferable);
const outTypes: OutputType[] = [Fr];
Expand Down
1 change: 1 addition & 0 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@
"rollups",
"rushstack",
"sanitise",
"sanitised",
"schnorr",
"secp",
"SEMRESATTRS",
Expand Down
36 changes: 32 additions & 4 deletions yarn-project/archiver/src/archiver/archiver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
type PublicFunction,
UnconstrainedFunctionBroadcastedEvent,
type UnconstrainedFunctionWithMembershipProof,
computePublicBytecodeCommitment,
isValidPrivateFunctionMembershipProof,
isValidUnconstrainedFunctionMembershipProof,
} from '@aztec/circuits.js';
Expand Down Expand Up @@ -706,6 +707,10 @@ export class Archiver implements ArchiveSource {
return this.store.getContractClass(id);
}

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

public getContract(address: AztecAddress): Promise<ContractInstanceWithAddress | undefined> {
return this.store.getContractInstance(address);
}
Expand Down Expand Up @@ -734,7 +739,11 @@ export class Archiver implements ArchiveSource {

// TODO(#10007): Remove this method
async addContractClass(contractClass: ContractClassPublic): Promise<void> {
await this.store.addContractClasses([contractClass], 0);
await this.store.addContractClasses(
[contractClass],
[computePublicBytecodeCommitment(contractClass.packedBytecode)],
0,
);
return;
}

Expand All @@ -746,6 +755,10 @@ export class Archiver implements ArchiveSource {
return this.store.getContractArtifact(address);
}

getContractFunctionName(address: AztecAddress, selector: FunctionSelector): Promise<string | undefined> {
return this.store.getContractFunctionName(address, selector);
}

async getL2Tips(): Promise<L2Tips> {
const [latestBlockNumber, provenBlockNumber] = await Promise.all([
this.getBlockNumber(),
Expand Down Expand Up @@ -804,8 +817,12 @@ class ArchiverStoreHelper
constructor(private readonly store: ArchiverDataStore) {}

// TODO(#10007): Remove this method
addContractClasses(contractClasses: ContractClassPublic[], blockNum: number): Promise<boolean> {
return this.store.addContractClasses(contractClasses, blockNum);
addContractClasses(
contractClasses: ContractClassPublic[],
bytecodeCommitments: Fr[],
blockNum: number,
): Promise<boolean> {
return this.store.addContractClasses(contractClasses, bytecodeCommitments, blockNum);
}

/**
Expand All @@ -820,7 +837,12 @@ class ArchiverStoreHelper
if (contractClasses.length > 0) {
contractClasses.forEach(c => this.#log.verbose(`Registering contract class ${c.id.toString()}`));
if (operation == Operation.Store) {
return await this.store.addContractClasses(contractClasses, blockNum);
// TODO: Will probably want to create some worker threads to compute these bytecode commitments as they are expensive
return await this.store.addContractClasses(
contractClasses,
contractClasses.map(x => computePublicBytecodeCommitment(x.packedBytecode)),
blockNum,
);
} else if (operation == Operation.Delete) {
return await this.store.deleteContractClasses(contractClasses, blockNum);
}
Expand Down Expand Up @@ -1028,6 +1050,9 @@ class ArchiverStoreHelper
getContractClass(id: Fr): Promise<ContractClassPublic | undefined> {
return this.store.getContractClass(id);
}
getBytecodeCommitment(contractClassId: Fr): Promise<Fr | undefined> {
return this.store.getBytecodeCommitment(contractClassId);
}
getContractInstance(address: AztecAddress): Promise<ContractInstanceWithAddress | undefined> {
return this.store.getContractInstance(address);
}
Expand All @@ -1040,6 +1065,9 @@ class ArchiverStoreHelper
getContractArtifact(address: AztecAddress): Promise<ContractArtifact | undefined> {
return this.store.getContractArtifact(address);
}
getContractFunctionName(address: AztecAddress, selector: FunctionSelector): Promise<string | undefined> {
return this.store.getContractFunctionName(address, selector);
}
getTotalL1ToL2MessageCount(): Promise<bigint> {
return this.store.getTotalL1ToL2MessageCount();
}
Expand Down
11 changes: 9 additions & 2 deletions yarn-project/archiver/src/archiver/archiver_store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
type Header,
type UnconstrainedFunctionWithMembershipProof,
} from '@aztec/circuits.js';
import { type ContractArtifact } from '@aztec/foundation/abi';
import { type ContractArtifact, type FunctionSelector } from '@aztec/foundation/abi';
import { type AztecAddress } from '@aztec/foundation/aztec-address';

import { type DataRetrieval } from './structs/data_retrieval.js';
Expand Down Expand Up @@ -229,10 +229,12 @@ export interface ArchiverDataStore {
* @param blockNumber - Number of the L2 block the contracts were registered in.
* @returns True if the operation is successful.
*/
addContractClasses(data: ContractClassPublic[], blockNumber: number): Promise<boolean>;
addContractClasses(data: ContractClassPublic[], bytecodeCommitments: Fr[], blockNumber: number): Promise<boolean>;

deleteContractClasses(data: ContractClassPublic[], blockNumber: number): Promise<boolean>;

getBytecodeCommitment(contractClassId: Fr): Promise<Fr | undefined>;

/**
* Returns a contract class given its id, or undefined if not exists.
* @param id - Id of the contract class.
Expand Down Expand Up @@ -269,6 +271,11 @@ export interface ArchiverDataStore {
addContractArtifact(address: AztecAddress, contract: ContractArtifact): Promise<void>;
getContractArtifact(address: AztecAddress): Promise<ContractArtifact | undefined>;

// TODO: These function names are in memory only as they are for development/debugging. They require the full contract
// artifact supplied to the node out of band. This should be reviewed and potentially removed as part of
// the node api cleanup process.
getContractFunctionName(address: AztecAddress, selector: FunctionSelector): Promise<string | undefined>;

/**
* Estimates the size of the store in bytes.
*/
Expand Down
13 changes: 11 additions & 2 deletions yarn-project/archiver/src/archiver/archiver_store_test_suite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
L1_TO_L2_MSG_SUBTREE_HEIGHT,
MAX_NULLIFIERS_PER_TX,
SerializableContractInstance,
computePublicBytecodeCommitment,
} from '@aztec/circuits.js';
import {
makeContractClassPublic,
Expand Down Expand Up @@ -310,7 +311,11 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch

beforeEach(async () => {
contractClass = makeContractClassPublic();
await store.addContractClasses([contractClass], blockNum);
await store.addContractClasses(
[contractClass],
[computePublicBytecodeCommitment(contractClass.packedBytecode)],
blockNum,
);
});

it('returns previously stored contract class', async () => {
Expand All @@ -323,7 +328,11 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
});

it('returns contract class if later "deployment" class was deleted', async () => {
await store.addContractClasses([contractClass], blockNum + 1);
await store.addContractClasses(
[contractClass],
[computePublicBytecodeCommitment(contractClass.packedBytecode)],
blockNum + 1,
);
await store.deleteContractClasses([contractClass], blockNum + 1);
await expect(store.getContractClass(contractClass.id)).resolves.toMatchObject(contractClass);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,30 @@ import { type AztecKVStore, type AztecMap } from '@aztec/kv-store';
*/
export class ContractClassStore {
#contractClasses: AztecMap<string, Buffer>;
#bytecodeCommitments: AztecMap<string, Buffer>;

constructor(private db: AztecKVStore) {
this.#contractClasses = db.openMap('archiver_contract_classes');
this.#bytecodeCommitments = db.openMap('archiver_bytecode_commitments');
}

async addContractClass(contractClass: ContractClassPublic, blockNumber: number): Promise<void> {
async addContractClass(
contractClass: ContractClassPublic,
bytecodeCommitment: Fr,
blockNumber: number,
): Promise<void> {
await this.#contractClasses.setIfNotExists(
contractClass.id.toString(),
serializeContractClassPublic({ ...contractClass, l2BlockNumber: blockNumber }),
);
await this.#bytecodeCommitments.setIfNotExists(contractClass.id.toString(), bytecodeCommitment.toBuffer());
}

async deleteContractClasses(contractClass: ContractClassPublic, blockNumber: number): Promise<void> {
const restoredContractClass = this.#contractClasses.get(contractClass.id.toString());
if (restoredContractClass && deserializeContractClassPublic(restoredContractClass).l2BlockNumber >= blockNumber) {
await this.#contractClasses.delete(contractClass.id.toString());
await this.#bytecodeCommitments.delete(contractClass.id.toString());
}
}

Expand All @@ -39,6 +47,11 @@ export class ContractClassStore {
return contractClass && { ...deserializeContractClassPublic(contractClass), id };
}

getBytecodeCommitment(id: Fr): Fr | undefined {
const value = this.#bytecodeCommitments.get(id.toString());
return value === undefined ? undefined : Fr.fromBuffer(value);
}

getContractClassIds(): Fr[] {
return Array.from(this.#contractClasses.keys()).map(key => Fr.fromString(key));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
type Header,
type UnconstrainedFunctionWithMembershipProof,
} from '@aztec/circuits.js';
import { type ContractArtifact } from '@aztec/foundation/abi';
import { type ContractArtifact, FunctionSelector } from '@aztec/foundation/abi';
import { type AztecAddress } from '@aztec/foundation/aztec-address';
import { createDebugLogger } from '@aztec/foundation/log';
import { type AztecKVStore } from '@aztec/kv-store';
Expand All @@ -46,6 +46,7 @@ export class KVArchiverDataStore implements ArchiverDataStore {
#contractClassStore: ContractClassStore;
#contractInstanceStore: ContractInstanceStore;
#contractArtifactStore: ContractArtifactsStore;
private functionNames = new Map<string, string>();

#log = createDebugLogger('aztec:archiver:data-store');

Expand All @@ -63,8 +64,19 @@ export class KVArchiverDataStore implements ArchiverDataStore {
return Promise.resolve(this.#contractArtifactStore.getContractArtifact(address));
}

addContractArtifact(address: AztecAddress, contract: ContractArtifact): Promise<void> {
return this.#contractArtifactStore.addContractArtifact(address, contract);
// TODO: These function names are in memory only as they are for development/debugging. They require the full contract
// artifact supplied to the node out of band. This should be reviewed and potentially removed as part of
// the node api cleanup process.
getContractFunctionName(address: AztecAddress, selector: FunctionSelector): Promise<string | undefined> {
return Promise.resolve(this.functionNames.get(selector.toString()));
}

async addContractArtifact(address: AztecAddress, contract: ContractArtifact): Promise<void> {
await this.#contractArtifactStore.addContractArtifact(address, contract);
// Building tup this map of selectors to function names save expensive re-hydration of contract artifacts later
contract.functions.forEach(f => {
this.functionNames.set(FunctionSelector.fromNameAndParameters(f.name, f.parameters).toString(), f.name);
});
}

getContractClass(id: Fr): Promise<ContractClassPublic | undefined> {
Expand All @@ -76,11 +88,20 @@ export class KVArchiverDataStore implements ArchiverDataStore {
}

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

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

async deleteContractClasses(data: ContractClassPublic[], blockNumber: number): Promise<boolean> {
Expand All @@ -89,6 +110,10 @@ export class KVArchiverDataStore implements ArchiverDataStore {
);
}

getBytecodeCommitment(contractClassId: Fr): Promise<Fr | undefined> {
return Promise.resolve(this.#contractClassStore.getBytecodeCommitment(contractClassId));
}

addFunctions(
contractClassId: Fr,
privateFunctions: ExecutablePrivateFunctionWithMembershipProof[],
Expand Down
Loading

0 comments on commit da265b6

Please sign in to comment.