diff --git a/yarn-project/aztec-node/src/factory.ts b/yarn-project/aztec-node/src/factory.ts index 52a94a1f91bf..4e25ed1b2c2c 100644 --- a/yarn-project/aztec-node/src/factory.ts +++ b/yarn-project/aztec-node/src/factory.ts @@ -2,7 +2,7 @@ import { createArchiver } from '@aztec/archiver'; import { BBCircuitVerifier, BatchChonkVerifier, QueuedIVCVerifier } from '@aztec/bb-prover'; import { TestCircuitVerifier } from '@aztec/bb-prover/test'; import { createBlobClientWithFileStores } from '@aztec/blob-client/client'; -import { Blob } from '@aztec/blob-lib'; +import { Blob, getKzg } from '@aztec/blob-lib'; import { EpochCache } from '@aztec/epoch-cache'; import { createEthereumChain } from '@aztec/ethereum/chain'; import { getPublicClient, makeL1HttpTransport } from '@aztec/ethereum/client'; @@ -94,6 +94,15 @@ export async function createAztecNodeService( const { BarretenbergSync } = await import('@aztec/bb.js'); await BarretenbergSync.initSingleton(); + // Warm the KZG trusted-setup singleton before any subsystem runs. getKzg() synchronously builds + // its precomputation tables on first use (~2s locally, blocking the event loop; 12-15s under + // production CPU limits). If that first use is instead the archiver reconstructing blobs or the + // proposal handler uploading them, the stalled loop overruns the gossipsub mcache window and + // attestation forwarding is skipped. Paying it here keeps it off the gossip path. Idempotent, so + // it is a no-op for sequencers (warmed in Sequencer.init) and for tests that pre-warm via + // warmBlobKzg. + getKzg(log); + const packageVersion = getPackageVersion(); const telemetry = deps.telemetry ?? getTelemetryClient(); const dateProvider = deps.dateProvider ?? new DateProvider(); diff --git a/yarn-project/blob-lib/src/kzg_context.ts b/yarn-project/blob-lib/src/kzg_context.ts index 3acb66ed3da0..54bc90be8839 100644 --- a/yarn-project/blob-lib/src/kzg_context.ts +++ b/yarn-project/blob-lib/src/kzg_context.ts @@ -1,3 +1,6 @@ +import type { Logger } from '@aztec/foundation/log'; +import { elapsedSync } from '@aztec/foundation/timer'; + import type { DasContextJs } from '@crate-crypto/node-eth-kzg'; import { createRequire } from 'module'; @@ -30,12 +33,17 @@ export function getBytesPerCommitment(): number { let kzgInstance: DasContextJs | undefined; /** - * Returns the lazily-initialized KZG context. - * The first call takes ~3 seconds to initialize the precomputation tables. + * Returns the lazily-initialized KZG context. The first call synchronously builds the + * precomputation tables (~2s locally, blocking the event loop; longer under constrained CPU), so + * callers on a latency-sensitive path should warm it ahead of time. Pass a logger to record how + * long that first build took. + * @param logger - Optional logger; when provided, the initial table build is timed and logged. */ -export function getKzg(): DasContextJs { +export function getKzg(logger?: Logger): DasContextJs { if (!kzgInstance) { - kzgInstance = loadNativeModule().DasContextJs.create({ usePrecomp: true }); + const [durationMs, instance] = elapsedSync(() => loadNativeModule().DasContextJs.create({ usePrecomp: true })); + kzgInstance = instance; + logger?.verbose(`Loaded KZG trusted setup`, { durationMs }); } return kzgInstance; }