Skip to content

Commit da265b6

Browse files
authored
feat: Speed up transaction execution (#10172)
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.
1 parent 12744d6 commit da265b6

32 files changed

+506
-94
lines changed

barretenberg/cpp/src/barretenberg/crypto/poseidon2/c_bind.cpp

+14
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,17 @@ WASM_EXPORT void poseidon2_permutation(fr::vec_in_buf inputs_buffer, fr::vec_out
4747
const std::vector<fr> results(results_array.begin(), results_array.end());
4848
*output = to_heap_buffer(results);
4949
}
50+
51+
WASM_EXPORT void poseidon2_hash_accumulate(fr::vec_in_buf inputs_buffer, fr::out_buf output)
52+
{
53+
std::vector<fr> to_hash;
54+
read(inputs_buffer, to_hash);
55+
const size_t numHashes = to_hash.size();
56+
fr result = 0;
57+
size_t count = 0;
58+
while (count < numHashes) {
59+
result = crypto::Poseidon2<crypto::Poseidon2Bn254ScalarFieldParams>::hash({ to_hash[count], result });
60+
++count;
61+
}
62+
write(output, result);
63+
}

barretenberg/ts/src/barretenberg_api/index.ts

+12
Original file line numberDiff line numberDiff line change
@@ -704,6 +704,18 @@ export class BarretenbergApiSync {
704704
return out[0];
705705
}
706706

707+
poseidon2HashAccumulate(inputsBuffer: Fr[]): Fr {
708+
const inArgs = [inputsBuffer].map(serializeBufferable);
709+
const outTypes: OutputType[] = [Fr];
710+
const result = this.wasm.callWasmExport(
711+
'poseidon2_hash_accumulate',
712+
inArgs,
713+
outTypes.map(t => t.SIZE_IN_BYTES),
714+
);
715+
const out = result.map((r, i) => outTypes[i].fromBuffer(r));
716+
return out[0];
717+
}
718+
707719
poseidon2Hashes(inputsBuffer: Fr[]): Fr {
708720
const inArgs = [inputsBuffer].map(serializeBufferable);
709721
const outTypes: OutputType[] = [Fr];

cspell.json

+1
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,7 @@
228228
"rollups",
229229
"rushstack",
230230
"sanitise",
231+
"sanitised",
231232
"schnorr",
232233
"secp",
233234
"SEMRESATTRS",

yarn-project/archiver/src/archiver/archiver.ts

+32-4
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
type PublicFunction,
3434
UnconstrainedFunctionBroadcastedEvent,
3535
type UnconstrainedFunctionWithMembershipProof,
36+
computePublicBytecodeCommitment,
3637
isValidPrivateFunctionMembershipProof,
3738
isValidUnconstrainedFunctionMembershipProof,
3839
} from '@aztec/circuits.js';
@@ -706,6 +707,10 @@ export class Archiver implements ArchiveSource {
706707
return this.store.getContractClass(id);
707708
}
708709

710+
public getBytecodeCommitment(id: Fr): Promise<Fr | undefined> {
711+
return this.store.getBytecodeCommitment(id);
712+
}
713+
709714
public getContract(address: AztecAddress): Promise<ContractInstanceWithAddress | undefined> {
710715
return this.store.getContractInstance(address);
711716
}
@@ -734,7 +739,11 @@ export class Archiver implements ArchiveSource {
734739

735740
// TODO(#10007): Remove this method
736741
async addContractClass(contractClass: ContractClassPublic): Promise<void> {
737-
await this.store.addContractClasses([contractClass], 0);
742+
await this.store.addContractClasses(
743+
[contractClass],
744+
[computePublicBytecodeCommitment(contractClass.packedBytecode)],
745+
0,
746+
);
738747
return;
739748
}
740749

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

758+
getContractFunctionName(address: AztecAddress, selector: FunctionSelector): Promise<string | undefined> {
759+
return this.store.getContractFunctionName(address, selector);
760+
}
761+
749762
async getL2Tips(): Promise<L2Tips> {
750763
const [latestBlockNumber, provenBlockNumber] = await Promise.all([
751764
this.getBlockNumber(),
@@ -804,8 +817,12 @@ class ArchiverStoreHelper
804817
constructor(private readonly store: ArchiverDataStore) {}
805818

806819
// TODO(#10007): Remove this method
807-
addContractClasses(contractClasses: ContractClassPublic[], blockNum: number): Promise<boolean> {
808-
return this.store.addContractClasses(contractClasses, blockNum);
820+
addContractClasses(
821+
contractClasses: ContractClassPublic[],
822+
bytecodeCommitments: Fr[],
823+
blockNum: number,
824+
): Promise<boolean> {
825+
return this.store.addContractClasses(contractClasses, bytecodeCommitments, blockNum);
809826
}
810827

811828
/**
@@ -820,7 +837,12 @@ class ArchiverStoreHelper
820837
if (contractClasses.length > 0) {
821838
contractClasses.forEach(c => this.#log.verbose(`Registering contract class ${c.id.toString()}`));
822839
if (operation == Operation.Store) {
823-
return await this.store.addContractClasses(contractClasses, blockNum);
840+
// TODO: Will probably want to create some worker threads to compute these bytecode commitments as they are expensive
841+
return await this.store.addContractClasses(
842+
contractClasses,
843+
contractClasses.map(x => computePublicBytecodeCommitment(x.packedBytecode)),
844+
blockNum,
845+
);
824846
} else if (operation == Operation.Delete) {
825847
return await this.store.deleteContractClasses(contractClasses, blockNum);
826848
}
@@ -1028,6 +1050,9 @@ class ArchiverStoreHelper
10281050
getContractClass(id: Fr): Promise<ContractClassPublic | undefined> {
10291051
return this.store.getContractClass(id);
10301052
}
1053+
getBytecodeCommitment(contractClassId: Fr): Promise<Fr | undefined> {
1054+
return this.store.getBytecodeCommitment(contractClassId);
1055+
}
10311056
getContractInstance(address: AztecAddress): Promise<ContractInstanceWithAddress | undefined> {
10321057
return this.store.getContractInstance(address);
10331058
}
@@ -1040,6 +1065,9 @@ class ArchiverStoreHelper
10401065
getContractArtifact(address: AztecAddress): Promise<ContractArtifact | undefined> {
10411066
return this.store.getContractArtifact(address);
10421067
}
1068+
getContractFunctionName(address: AztecAddress, selector: FunctionSelector): Promise<string | undefined> {
1069+
return this.store.getContractFunctionName(address, selector);
1070+
}
10431071
getTotalL1ToL2MessageCount(): Promise<bigint> {
10441072
return this.store.getTotalL1ToL2MessageCount();
10451073
}

yarn-project/archiver/src/archiver/archiver_store.ts

+9-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import {
2020
type Header,
2121
type UnconstrainedFunctionWithMembershipProof,
2222
} from '@aztec/circuits.js';
23-
import { type ContractArtifact } from '@aztec/foundation/abi';
23+
import { type ContractArtifact, type FunctionSelector } from '@aztec/foundation/abi';
2424
import { type AztecAddress } from '@aztec/foundation/aztec-address';
2525

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

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

236+
getBytecodeCommitment(contractClassId: Fr): Promise<Fr | undefined>;
237+
236238
/**
237239
* Returns a contract class given its id, or undefined if not exists.
238240
* @param id - Id of the contract class.
@@ -269,6 +271,11 @@ export interface ArchiverDataStore {
269271
addContractArtifact(address: AztecAddress, contract: ContractArtifact): Promise<void>;
270272
getContractArtifact(address: AztecAddress): Promise<ContractArtifact | undefined>;
271273

274+
// TODO: These function names are in memory only as they are for development/debugging. They require the full contract
275+
// artifact supplied to the node out of band. This should be reviewed and potentially removed as part of
276+
// the node api cleanup process.
277+
getContractFunctionName(address: AztecAddress, selector: FunctionSelector): Promise<string | undefined>;
278+
272279
/**
273280
* Estimates the size of the store in bytes.
274281
*/

yarn-project/archiver/src/archiver/archiver_store_test_suite.ts

+11-2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
L1_TO_L2_MSG_SUBTREE_HEIGHT,
1010
MAX_NULLIFIERS_PER_TX,
1111
SerializableContractInstance,
12+
computePublicBytecodeCommitment,
1213
} from '@aztec/circuits.js';
1314
import {
1415
makeContractClassPublic,
@@ -310,7 +311,11 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
310311

311312
beforeEach(async () => {
312313
contractClass = makeContractClassPublic();
313-
await store.addContractClasses([contractClass], blockNum);
314+
await store.addContractClasses(
315+
[contractClass],
316+
[computePublicBytecodeCommitment(contractClass.packedBytecode)],
317+
blockNum,
318+
);
314319
});
315320

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

325330
it('returns contract class if later "deployment" class was deleted', async () => {
326-
await store.addContractClasses([contractClass], blockNum + 1);
331+
await store.addContractClasses(
332+
[contractClass],
333+
[computePublicBytecodeCommitment(contractClass.packedBytecode)],
334+
blockNum + 1,
335+
);
327336
await store.deleteContractClasses([contractClass], blockNum + 1);
328337
await expect(store.getContractClass(contractClass.id)).resolves.toMatchObject(contractClass);
329338
});

yarn-project/archiver/src/archiver/kv_archiver_store/contract_class_store.ts

+14-1
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,30 @@ import { type AztecKVStore, type AztecMap } from '@aztec/kv-store';
1515
*/
1616
export class ContractClassStore {
1717
#contractClasses: AztecMap<string, Buffer>;
18+
#bytecodeCommitments: AztecMap<string, Buffer>;
1819

1920
constructor(private db: AztecKVStore) {
2021
this.#contractClasses = db.openMap('archiver_contract_classes');
22+
this.#bytecodeCommitments = db.openMap('archiver_bytecode_commitments');
2123
}
2224

23-
async addContractClass(contractClass: ContractClassPublic, blockNumber: number): Promise<void> {
25+
async addContractClass(
26+
contractClass: ContractClassPublic,
27+
bytecodeCommitment: Fr,
28+
blockNumber: number,
29+
): Promise<void> {
2430
await this.#contractClasses.setIfNotExists(
2531
contractClass.id.toString(),
2632
serializeContractClassPublic({ ...contractClass, l2BlockNumber: blockNumber }),
2733
);
34+
await this.#bytecodeCommitments.setIfNotExists(contractClass.id.toString(), bytecodeCommitment.toBuffer());
2835
}
2936

3037
async deleteContractClasses(contractClass: ContractClassPublic, blockNumber: number): Promise<void> {
3138
const restoredContractClass = this.#contractClasses.get(contractClass.id.toString());
3239
if (restoredContractClass && deserializeContractClassPublic(restoredContractClass).l2BlockNumber >= blockNumber) {
3340
await this.#contractClasses.delete(contractClass.id.toString());
41+
await this.#bytecodeCommitments.delete(contractClass.id.toString());
3442
}
3543
}
3644

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

50+
getBytecodeCommitment(id: Fr): Fr | undefined {
51+
const value = this.#bytecodeCommitments.get(id.toString());
52+
return value === undefined ? undefined : Fr.fromBuffer(value);
53+
}
54+
4255
getContractClassIds(): Fr[] {
4356
return Array.from(this.#contractClasses.keys()).map(key => Fr.fromString(key));
4457
}

yarn-project/archiver/src/archiver/kv_archiver_store/kv_archiver_store.ts

+31-6
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import {
1919
type Header,
2020
type UnconstrainedFunctionWithMembershipProof,
2121
} from '@aztec/circuits.js';
22-
import { type ContractArtifact } from '@aztec/foundation/abi';
22+
import { type ContractArtifact, FunctionSelector } from '@aztec/foundation/abi';
2323
import { type AztecAddress } from '@aztec/foundation/aztec-address';
2424
import { createDebugLogger } from '@aztec/foundation/log';
2525
import { type AztecKVStore } from '@aztec/kv-store';
@@ -46,6 +46,7 @@ export class KVArchiverDataStore implements ArchiverDataStore {
4646
#contractClassStore: ContractClassStore;
4747
#contractInstanceStore: ContractInstanceStore;
4848
#contractArtifactStore: ContractArtifactsStore;
49+
private functionNames = new Map<string, string>();
4950

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

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

66-
addContractArtifact(address: AztecAddress, contract: ContractArtifact): Promise<void> {
67-
return this.#contractArtifactStore.addContractArtifact(address, contract);
67+
// TODO: These function names are in memory only as they are for development/debugging. They require the full contract
68+
// artifact supplied to the node out of band. This should be reviewed and potentially removed as part of
69+
// the node api cleanup process.
70+
getContractFunctionName(address: AztecAddress, selector: FunctionSelector): Promise<string | undefined> {
71+
return Promise.resolve(this.functionNames.get(selector.toString()));
72+
}
73+
74+
async addContractArtifact(address: AztecAddress, contract: ContractArtifact): Promise<void> {
75+
await this.#contractArtifactStore.addContractArtifact(address, contract);
76+
// Building tup this map of selectors to function names save expensive re-hydration of contract artifacts later
77+
contract.functions.forEach(f => {
78+
this.functionNames.set(FunctionSelector.fromNameAndParameters(f.name, f.parameters).toString(), f.name);
79+
});
6880
}
6981

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

7890
getContractInstance(address: AztecAddress): Promise<ContractInstanceWithAddress | undefined> {
79-
return Promise.resolve(this.#contractInstanceStore.getContractInstance(address));
91+
const contract = this.#contractInstanceStore.getContractInstance(address);
92+
return Promise.resolve(contract);
8093
}
8194

82-
async addContractClasses(data: ContractClassPublic[], blockNumber: number): Promise<boolean> {
83-
return (await Promise.all(data.map(c => this.#contractClassStore.addContractClass(c, blockNumber)))).every(Boolean);
95+
async addContractClasses(
96+
data: ContractClassPublic[],
97+
bytecodeCommitments: Fr[],
98+
blockNumber: number,
99+
): Promise<boolean> {
100+
return (
101+
await Promise.all(
102+
data.map((c, i) => this.#contractClassStore.addContractClass(c, bytecodeCommitments[i], blockNumber)),
103+
)
104+
).every(Boolean);
84105
}
85106

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

113+
getBytecodeCommitment(contractClassId: Fr): Promise<Fr | undefined> {
114+
return Promise.resolve(this.#contractClassStore.getBytecodeCommitment(contractClassId));
115+
}
116+
92117
addFunctions(
93118
contractClassId: Fr,
94119
privateFunctions: ExecutablePrivateFunctionWithMembershipProof[],

0 commit comments

Comments
 (0)