Skip to content

Commit

Permalink
feat: Lazy wasm pt.1 (#11371)
Browse files Browse the repository at this point in the history
Starts the transition towards no top level await when importing wasm in
any client bundle, so that browsers don't have to load +8MB right away.

This PR introduces a `BarretembergLazy` class that behaves as
`Barretenberg` (in the sense that methods are async), but it's intended
to be initialized with a single thread to perform common operations,
such as Field math or hashes. Since it's a big change, this PR focuses
on a single thing: making `Fr.sqrt()` async. This of course had terrible
ramifications, since that operation is used to assert validity of
Grumpkin points that ultimately is what our `AztecAddresses` are. This
leaked into the `AztecAddress.random()` method, so most tests in the
repo are affected.

However and even though this PR seems gigantic, that's the only thing
that has changed, and as stated affects mostly tests. After this, more
asynchronification on the client will happen, but hopefully will result
in smaller diffs. `BarretenbergSync` will probably be kept around for
server stuff, but `bb.js` will be split so that it's not imported all
the time.
  • Loading branch information
Thunkar authored Jan 21, 2025
1 parent 79f810d commit 864bc6f
Show file tree
Hide file tree
Showing 131 changed files with 1,092 additions and 837 deletions.
28 changes: 27 additions & 1 deletion barretenberg/ts/src/barretenberg/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { BarretenbergApi, BarretenbergApiSync } from '../barretenberg_api/index.
import { createMainWorker } from '../barretenberg_wasm/barretenberg_wasm_main/factory/node/index.js';
import { BarretenbergWasmMain, BarretenbergWasmMainWorker } from '../barretenberg_wasm/barretenberg_wasm_main/index.js';
import { getRemoteBarretenbergWasm } from '../barretenberg_wasm/helpers/index.js';
import { BarretenbergWasmWorker, fetchModuleAndThreads } from '../barretenberg_wasm/index.js';
import { BarretenbergWasm, BarretenbergWasmWorker, fetchModuleAndThreads } from '../barretenberg_wasm/index.js';
import createDebug from 'debug';
import { Crs, GrumpkinCrs } from '../crs/index.js';
import { RawBuffer } from '../types/raw_buffer.js';
Expand Down Expand Up @@ -123,6 +123,32 @@ export class BarretenbergSync extends BarretenbergApiSync {
}
}

let barrentenbergLazySingleton: BarretenbergLazy;

export class BarretenbergLazy extends BarretenbergApi {
private constructor(wasm: BarretenbergWasmMain) {
super(wasm);
}

private static async new() {
const wasm = new BarretenbergWasmMain();
const { module, threads } = await fetchModuleAndThreads(1);
await wasm.init(module, threads);
return new BarretenbergLazy(wasm);
}

static async getSingleton() {
if (!barrentenbergLazySingleton) {
barrentenbergLazySingleton = await BarretenbergLazy.new();
}
return barrentenbergLazySingleton;
}

getWasm() {
return this.wasm;
}
}

// If we're in ESM environment, use top level await. CJS users need to call it manually.
// Need to ignore for cjs build.
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
Expand Down
3 changes: 2 additions & 1 deletion barretenberg/ts/src/barretenberg_api/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// WARNING: FILE CODE GENERATED BY BINDGEN UTILITY. DO NOT EDIT!
/* eslint-disable @typescript-eslint/no-unused-vars */
import { BarretenbergWasmMain } from '../barretenberg_wasm/barretenberg_wasm_main/index.js';
import { BarretenbergWasmWorker, BarretenbergWasm } from '../barretenberg_wasm/index.js';
import {
BufferDeserializer,
Expand All @@ -13,7 +14,7 @@ import {
import { Fr, Fq, Point, Buffer32, Buffer128, Ptr } from '../types/index.js';

export class BarretenbergApi {
constructor(protected wasm: BarretenbergWasmWorker) {}
constructor(protected wasm: BarretenbergWasmWorker | BarretenbergWasmMain) {}

async pedersenCommit(inputsBuffer: Fr[], ctxIndex: number): Promise<Point> {
const inArgs = [inputsBuffer, ctxIndex].map(serializeBufferable);
Expand Down
1 change: 1 addition & 0 deletions barretenberg/ts/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export {
BackendOptions,
Barretenberg,
BarretenbergSync,
BarretenbergLazy,
BarretenbergVerifier,
UltraPlonkBackend,
UltraHonkBackend,
Expand Down
4 changes: 2 additions & 2 deletions yarn-project/archiver/src/archiver/archiver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ describe('Archiver', () => {

const GENESIS_ROOT = new Fr(GENESIS_ARCHIVE_ROOT).toString();

beforeEach(() => {
beforeEach(async () => {
logger = createLogger('archiver:test');
now = +new Date();
publicClient = mock<PublicClient<HttpTransport, Chain>>({
Expand Down Expand Up @@ -117,7 +117,7 @@ describe('Archiver', () => {
l1Constants,
);

blocks = blockNumbers.map(x => L2Block.random(x, txsPerBlock, x + 1, 2));
blocks = await Promise.all(blockNumbers.map(x => L2Block.random(x, txsPerBlock, x + 1, 2)));
blocks.forEach(block => {
block.body.txEffects.forEach((txEffect, i) => {
txEffect.privateLogs = Array(getNumPrivateLogsForTx(block.number, i))
Expand Down
35 changes: 18 additions & 17 deletions yarn-project/archiver/src/archiver/archiver_store_test_suite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
makeExecutablePrivateFunctionWithMembershipProof,
makeUnconstrainedFunctionWithMembershipProof,
} from '@aztec/circuits.js/testing';
import { times } from '@aztec/foundation/collection';
import { times, timesParallel } from '@aztec/foundation/collection';
import { randomInt } from '@aztec/foundation/crypto';

import { type ArchiverDataStore, type ArchiverL1SynchPoint } from './archiver_store.js';
Expand Down Expand Up @@ -51,9 +51,9 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
},
});

beforeEach(() => {
beforeEach(async () => {
store = getStore();
blocks = times(10, i => makeL1Published(L2Block.random(i + 1), i + 10));
blocks = await timesParallel(10, async i => makeL1Published(await L2Block.random(i + 1), i + 10));
});

describe('addBlocks', () => {
Expand Down Expand Up @@ -81,7 +81,7 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
});

it('can unwind multiple empty blocks', async () => {
const emptyBlocks = times(10, i => makeL1Published(L2Block.random(i + 1, 0), i + 10));
const emptyBlocks = await timesParallel(10, async i => makeL1Published(await L2Block.random(i + 1, 0), i + 10));
await store.addBlocks(emptyBlocks);
expect(await store.getSynchedL2BlockNumber()).toBe(10);

Expand Down Expand Up @@ -276,7 +276,8 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
const blockNum = 10;

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

Expand All @@ -285,7 +286,7 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
});

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

it('returns undefined if previously stored contract instances was deleted', async () => {
Expand Down Expand Up @@ -408,12 +409,12 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
});
};

const mockBlockWithLogs = (blockNumber: number): L1Published<L2Block> => {
const block = L2Block.random(blockNumber);
const mockBlockWithLogs = async (blockNumber: number): Promise<L1Published<L2Block>> => {
const block = await L2Block.random(blockNumber);
block.header.globalVariables.blockNumber = new Fr(blockNumber);

block.body.txEffects = times(numTxsPerBlock, (txIndex: number) => {
const txEffect = TxEffect.random();
block.body.txEffects = await timesParallel(numTxsPerBlock, async (txIndex: number) => {
const txEffect = await TxEffect.random();
txEffect.privateLogs = mockPrivateLogs(blockNumber, txIndex);
txEffect.publicLogs = mockPublicLogs(blockNumber, txIndex);
return txEffect;
Expand All @@ -426,7 +427,7 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
};

beforeEach(async () => {
blocks = times(numBlocks, (index: number) => mockBlockWithLogs(index));
blocks = await timesParallel(numBlocks, (index: number) => mockBlockWithLogs(index));

await store.addBlocks(blocks);
await store.addLogs(blocks.map(b => b.data));
Expand Down Expand Up @@ -482,7 +483,7 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch

// Create a block containing logs that have the same tag as the blocks before.
const newBlockNumber = numBlocks;
const newBlock = mockBlockWithLogs(newBlockNumber);
const newBlock = await mockBlockWithLogs(newBlockNumber);
const newLog = newBlock.data.body.txEffects[1].privateLogs[1];
newLog.fields[0] = tags[0];
newBlock.data.body.txEffects[1].privateLogs[1] = newLog;
Expand Down Expand Up @@ -545,7 +546,7 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch

// Create a block containing these invalid logs
const newBlockNumber = numBlocks;
const newBlock = mockBlockWithLogs(newBlockNumber);
const newBlock = await mockBlockWithLogs(newBlockNumber);
newBlock.data.body.txEffects[0].publicLogs = invalidLogs;
await store.addBlocks([newBlock]);
await store.addLogs([newBlock.data]);
Expand All @@ -565,8 +566,8 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
let blocks: L1Published<L2Block>[];

beforeEach(async () => {
blocks = times(numBlocks, (index: number) => ({
data: L2Block.random(index + 1, txsPerBlock, numPublicFunctionCalls, numPublicLogs),
blocks = await timesParallel(numBlocks, async (index: number) => ({
data: await L2Block.random(index + 1, txsPerBlock, numPublicFunctionCalls, numPublicLogs),
l1: { blockNumber: BigInt(index), blockHash: `0x${index}`, timestamp: BigInt(index) },
}));

Expand Down Expand Up @@ -748,8 +749,8 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
const numBlocks = 10;
const nullifiersPerBlock = new Map<number, Fr[]>();

beforeEach(() => {
blocks = times(numBlocks, (index: number) => L2Block.random(index + 1, 1));
beforeEach(async () => {
blocks = await timesParallel(numBlocks, (index: number) => L2Block.random(index + 1, 1));

blocks.forEach((block, blockIndex) => {
nullifiersPerBlock.set(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { L2Block } from '@aztec/circuit-types';
import { times } from '@aztec/foundation/collection';
import { timesParallel } from '@aztec/foundation/collection';

import { type ArchiverDataStore } from '../archiver_store.js';
import { describeArchiverDataStore } from '../archiver_store_test_suite.js';
Expand All @@ -18,8 +18,8 @@ describe('MemoryArchiverStore', () => {
it('does not return more than "maxLogs" logs', async () => {
const maxLogs = 5;
archiverStore = new MemoryArchiverStore(maxLogs);
const blocks = times(10, (index: number) => ({
data: L2Block.random(index + 1, 4, 3, 2),
const blocks = await timesParallel(10, async (index: number) => ({
data: await L2Block.random(index + 1, 4, 3, 2),
l1: { blockNumber: BigInt(index), blockHash: `0x${index}`, timestamp: BigInt(index) },
}));

Expand Down
1 change: 1 addition & 0 deletions yarn-project/archiver/src/test/mock_archiver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,6 @@ export class MockPrefilledArchiver extends MockArchiver {

const fromBlock = this.l2Blocks.length;
this.addBlocks(this.precomputed.slice(fromBlock, fromBlock + numBlocks));
return Promise.resolve();
}
}
4 changes: 2 additions & 2 deletions yarn-project/archiver/src/test/mock_l2_block_source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ export class MockL2BlockSource implements L2BlockSource {

private log = createLogger('archiver:mock_l2_block_source');

public createBlocks(numBlocks: number) {
public async createBlocks(numBlocks: number) {
for (let i = 0; i < numBlocks; i++) {
const blockNum = this.l2Blocks.length + 1;
const block = L2Block.random(blockNum);
const block = await L2Block.random(blockNum);
this.l2Blocks.push(block);
}

Expand Down
31 changes: 16 additions & 15 deletions yarn-project/aztec.js/src/contract/contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,19 +49,6 @@ describe('Contract Class', () => {
governanceProposerAddress: EthAddress.random(),
slashFactoryAddress: EthAddress.random(),
};
const mockNodeInfo: NodeInfo = {
nodeVersion: 'vx.x.x',
l1ChainId: 1,
protocolVersion: 2,
l1ContractAddresses: l1Addresses,
enr: undefined,
protocolContractAddresses: {
classRegisterer: AztecAddress.random(),
feeJuice: AztecAddress.random(),
instanceDeployer: AztecAddress.random(),
multiCallEntrypoint: AztecAddress.random(),
},
};

const defaultArtifact: ContractArtifact = {
name: 'FooContract',
Expand Down Expand Up @@ -141,11 +128,25 @@ describe('Contract Class', () => {
notes: {},
};

beforeEach(() => {
contractAddress = AztecAddress.random();
beforeEach(async () => {
contractAddress = await AztecAddress.random();
account = CompleteAddress.random();
contractInstance = { address: contractAddress } as ContractInstanceWithAddress;

const mockNodeInfo: NodeInfo = {
nodeVersion: 'vx.x.x',
l1ChainId: 1,
protocolVersion: 2,
l1ContractAddresses: l1Addresses,
enr: undefined,
protocolContractAddresses: {
classRegisterer: await AztecAddress.random(),
feeJuice: await AztecAddress.random(),
instanceDeployer: await AztecAddress.random(),
multiCallEntrypoint: await AztecAddress.random(),
},
};

wallet = mock<Wallet>();
wallet.simulateTx.mockResolvedValue(mockTxSimulationResult);
wallet.createTxExecutionRequest.mockResolvedValue(mockTxRequest);
Expand Down
4 changes: 2 additions & 2 deletions yarn-project/aztec.js/src/contract/get_gas_limits.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import { getGasLimits } from './get_gas_limits.js';
describe('getGasLimits', () => {
let txSimulationResult: TxSimulationResult;

beforeEach(() => {
txSimulationResult = mockSimulatedTx();
beforeEach(async () => {
txSimulationResult = await mockSimulatedTx();

const tx = mockTxForRollup();
tx.data.gasUsed = Gas.from({ daGas: 100, l2Gas: 200 });
Expand Down
14 changes: 10 additions & 4 deletions yarn-project/bb-prover/src/avm_proving.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ describe('AVM WitGen, "check circuit" tests', () => {
it(
'call the max number of unique contract classes',
async () => {
const contractDataSource = new MockedAvmTestContractDataSource();
const contractDataSource = await MockedAvmTestContractDataSource.create();
// args is initialized to MAX_PUBLIC_CALLS_TO_UNIQUE_CONTRACT_CLASS_IDS contract addresses with unique class IDs
const args = Array.from(contractDataSource.contractInstances.values())
.map(instance => instance.address.toField())
Expand All @@ -130,7 +130,7 @@ describe('AVM WitGen, "check circuit" tests', () => {
it(
'attempt too many calls to unique contract class ids',
async () => {
const contractDataSource = new MockedAvmTestContractDataSource();
const contractDataSource = await MockedAvmTestContractDataSource.create();
// args is initialized to MAX_PUBLIC_CALLS_TO_UNIQUE_CONTRACT_CLASS_IDS+1 contract addresses with unique class IDs
// should fail because we are trying to call MAX+1 unique class IDs
const args = Array.from(contractDataSource.contractInstances.values()).map(instance =>
Expand Down Expand Up @@ -301,8 +301,11 @@ async function proveAndVerifyAvmTestContractSimple(
args: Fr[] = [],
expectRevert = false,
skipContractDeployments = false,
contractDataSource = new MockedAvmTestContractDataSource(skipContractDeployments),
contractDataSource?: MockedAvmTestContractDataSource,
) {
if (!contractDataSource) {
contractDataSource = await MockedAvmTestContractDataSource.create(skipContractDeployments);
}
await proveAndVerifyAvmTestContract(
checkCircuitOnly,
/*setupFunctionNames=*/ [],
Expand Down Expand Up @@ -330,8 +333,11 @@ async function proveAndVerifyAvmTestContract(
teardownArgs: Fr[] = [],
expectRevert = false,
skipContractDeployments = false,
contractDataSource = new MockedAvmTestContractDataSource(skipContractDeployments),
contractDataSource?: MockedAvmTestContractDataSource,
) {
if (!contractDataSource) {
contractDataSource = await MockedAvmTestContractDataSource.create(skipContractDeployments);
}
const avmCircuitInputs = await simulateAvmTestContractGenerateCircuitInputs(
setupFunctionNames,
setupArgs,
Expand Down
12 changes: 6 additions & 6 deletions yarn-project/circuit-types/src/body.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import { Fr } from '@aztec/circuits.js';
import { Body } from './body.js';

describe('Body', () => {
it('converts to and from buffer', () => {
const body = Body.random();
it('converts to and from buffer', async () => {
const body = await Body.random();
const buf = body.toBuffer();
expect(Body.fromBuffer(buf)).toEqual(body);
});

it('converts to and from fields', () => {
const body = Body.random();
it('converts to and from fields', async () => {
const body = await Body.random();
const fields = body.toBlobFields();
// TODO(#8954): When logs are refactored into fields, we won't need to inject them here
expect(Body.fromBlobFields(fields, body.contractClassLogs)).toEqual(body);
Expand All @@ -22,8 +22,8 @@ describe('Body', () => {
expect(Body.fromBlobFields(fields)).toEqual(body);
});

it('fails with invalid fields', () => {
const body = Body.random();
it('fails with invalid fields', async () => {
const body = await Body.random();
const fields = body.toBlobFields();
// Replace the initial field with an invalid encoding
fields[0] = new Fr(12);
Expand Down
7 changes: 5 additions & 2 deletions yarn-project/circuit-types/src/body.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { type Fr } from '@aztec/circuits.js';
import { timesParallel } from '@aztec/foundation/collection';
import { type ZodFor } from '@aztec/foundation/schemas';
import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize';

Expand Down Expand Up @@ -88,8 +89,10 @@ export class Body {
return new ContractClass2BlockL2Logs(logs);
}

static random(txsPerBlock = 4, numPublicCallsPerTx = 3, numPublicLogsPerCall = 1) {
const txEffects = [...new Array(txsPerBlock)].map(_ => TxEffect.random(numPublicCallsPerTx, numPublicLogsPerCall));
static async random(txsPerBlock = 4, numPublicCallsPerTx = 3, numPublicLogsPerCall = 1) {
const txEffects = await timesParallel(txsPerBlock, () =>
TxEffect.random(numPublicCallsPerTx, numPublicLogsPerCall),
);

return new Body(txEffects);
}
Expand Down
Loading

0 comments on commit 864bc6f

Please sign in to comment.