diff --git a/yarn-project/aztec-node/src/aztec-node/server.ts b/yarn-project/aztec-node/src/aztec-node/server.ts index f2fb57e33c9b..6337ae1da725 100644 --- a/yarn-project/aztec-node/src/aztec-node/server.ts +++ b/yarn-project/aztec-node/src/aztec-node/server.ts @@ -31,7 +31,7 @@ import { } from '@aztec/node-lib/factories'; import { type P2P, type P2PClientDeps, createP2PClient, getDefaultAllowedSetupFunctions } from '@aztec/p2p'; import { ProtocolContractAddress } from '@aztec/protocol-contracts'; -import { BlockBuilder, GlobalVariableBuilder, SequencerClient, type SequencerPublisher } from '@aztec/sequencer-client'; +import { GlobalVariableBuilder, SequencerClient, type SequencerPublisher } from '@aztec/sequencer-client'; import { PublicProcessorFactory } from '@aztec/simulator/server'; import { AttestationsBlockWatcher, @@ -309,18 +309,10 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable { // We should really not be modifying the config object config.txPublicSetupAllowList = config.txPublicSetupAllowList ?? (await getDefaultAllowedSetupFunctions()); - // Create BlockBuilder for EpochPruneWatcher (slasher functionality) - const blockBuilder = new BlockBuilder( - { ...config, l1GenesisTime, slotDuration: Number(slotDuration) }, - worldStateSynchronizer, - archiver, - dateProvider, - telemetry, - ); - // Create FullNodeCheckpointsBuilder for validator and non-validator block proposal handling const validatorCheckpointsBuilder = new FullNodeCheckpointsBuilder( { ...config, l1GenesisTime, slotDuration: Number(slotDuration) }, + worldStateSynchronizer, archiver, dateProvider, telemetry, @@ -387,7 +379,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable { archiver, epochCache, p2pClient.getTxProvider(), - blockBuilder, + validatorCheckpointsBuilder, config, ); watchers.push(epochPruneWatcher); @@ -452,6 +444,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable { // Create and start the sequencer client const checkpointsBuilder = new CheckpointsBuilder( { ...config, l1GenesisTime, slotDuration: Number(slotDuration) }, + worldStateSynchronizer, archiver, dateProvider, telemetry, diff --git a/yarn-project/end-to-end/src/e2e_p2p/reex.test.ts b/yarn-project/end-to-end/src/e2e_p2p/reex.test.ts index 2afda2cc510e..6dc4301150d7 100644 --- a/yarn-project/end-to-end/src/e2e_p2p/reex.test.ts +++ b/yarn-project/end-to-end/src/e2e_p2p/reex.test.ts @@ -6,7 +6,6 @@ import { times } from '@aztec/foundation/collection'; import { sleep } from '@aztec/foundation/sleep'; import { unfreeze } from '@aztec/foundation/types'; import type { LibP2PService, P2PClient } from '@aztec/p2p'; -import type { BlockBuilder } from '@aztec/sequencer-client'; import type { CppPublicTxSimulator, PublicTxResult } from '@aztec/simulator/server'; import { BlockProposal } from '@aztec/stdlib/p2p'; import { ReExFailedTxsError, ReExStateMismatchError, ReExTimeoutError } from '@aztec/stdlib/validators'; @@ -170,30 +169,28 @@ describe('e2e_p2p_reex', () => { node: AztecNodeService, stub: (tx: Tx, originalSimulate: (tx: Tx) => Promise) => Promise, ) => { - const blockBuilder: BlockBuilder = (node as any).sequencer.sequencer.blockBuilder; + const blockBuilder: any = (node as any).sequencer.sequencer.blockBuilder; const originalCreateDeps = blockBuilder.makeBlockBuilderDeps.bind(blockBuilder); - jest - .spyOn(blockBuilder, 'makeBlockBuilderDeps') - .mockImplementation(async (...args: Parameters) => { - const deps = await originalCreateDeps(...args); - t.logger.warn('Creating mocked processor factory'); - const simulator: CppPublicTxSimulator = (deps.processor as any).publicTxSimulator; - const originalSimulate = simulator.simulate.bind(simulator); - // We only stub the simulate method if it's NOT the first time we see the tx - // so the proposer works fine, but we cause the failure in the validators. - jest.spyOn(simulator, 'simulate').mockImplementation((tx: Tx) => { - const txHash = tx.getTxHash().toString(); - if (seenTxs.has(txHash)) { - t.logger.warn('Calling stubbed simulate for tx', { txHash }); - return stub(tx, originalSimulate); - } else { - seenTxs.add(txHash); - t.logger.warn('Calling original simulate for tx', { txHash }); - return originalSimulate(tx); - } - }); - return deps; + jest.spyOn(blockBuilder, 'makeBlockBuilderDeps').mockImplementation(async (...args: any[]) => { + const deps = await originalCreateDeps(...args); + t.logger.warn('Creating mocked processor factory'); + const simulator: CppPublicTxSimulator = (deps.processor as any).publicTxSimulator; + const originalSimulate = simulator.simulate.bind(simulator); + // We only stub the simulate method if it's NOT the first time we see the tx + // so the proposer works fine, but we cause the failure in the validators. + jest.spyOn(simulator, 'simulate').mockImplementation((tx: Tx) => { + const txHash = tx.getTxHash().toString(); + if (seenTxs.has(txHash)) { + t.logger.warn('Calling stubbed simulate for tx', { txHash }); + return stub(tx, originalSimulate); + } else { + seenTxs.add(txHash); + t.logger.warn('Calling original simulate for tx', { txHash }); + return originalSimulate(tx); + } }); + return deps; + }); }; // Have the public tx processor take an extra long time to process the tx, so the validator times out diff --git a/yarn-project/prover-client/package.json b/yarn-project/prover-client/package.json index 0174d06c9f41..7f1691fbbaaf 100644 --- a/yarn-project/prover-client/package.json +++ b/yarn-project/prover-client/package.json @@ -4,7 +4,6 @@ "type": "module", "exports": { ".": "./dest/index.js", - "./block-factory": "./dest/block-factory/index.js", "./broker": "./dest/proving_broker/index.js", "./broker/config": "./dest/proving_broker/config.js", "./orchestrator": "./dest/orchestrator/index.js", diff --git a/yarn-project/prover-client/src/block-factory/index.ts b/yarn-project/prover-client/src/block-factory/index.ts deleted file mode 100644 index f0f05ac3081b..000000000000 --- a/yarn-project/prover-client/src/block-factory/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './light.js'; diff --git a/yarn-project/prover-client/src/block-factory/light.test.ts b/yarn-project/prover-client/src/block-factory/light.test.ts deleted file mode 100644 index d3917a99f04c..000000000000 --- a/yarn-project/prover-client/src/block-factory/light.test.ts +++ /dev/null @@ -1,424 +0,0 @@ -import { TestCircuitProver } from '@aztec/bb-prover'; -import { SpongeBlob, encodeBlockEndBlobData } from '@aztec/blob-lib'; -import { - ARCHIVE_HEIGHT, - CHONK_PROOF_LENGTH, - L1_TO_L2_MSG_SUBTREE_HEIGHT, - L1_TO_L2_MSG_SUBTREE_ROOT_SIBLING_PATH_LENGTH, - NESTED_RECURSIVE_PROOF_LENGTH, - NESTED_RECURSIVE_ROLLUP_HONK_PROOF_LENGTH, - NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP, - NUM_BASE_PARITY_PER_ROOT_PARITY, -} from '@aztec/constants'; -import { BlockNumber } from '@aztec/foundation/branded-types'; -import { padArrayEnd, times, timesParallel } from '@aztec/foundation/collection'; -import { Fr } from '@aztec/foundation/curves/bn254'; -import { type Tuple, assertLength } from '@aztec/foundation/serialize'; -import { getVkData } from '@aztec/noir-protocol-circuits-types/server/vks'; -import { getVKTreeRoot } from '@aztec/noir-protocol-circuits-types/vk-tree'; -import { ProtocolContractsList, protocolContractsHash } from '@aztec/protocol-contracts'; -import { computeFeePayerBalanceLeafSlot } from '@aztec/protocol-contracts/fee-juice'; -import { PublicDataWrite } from '@aztec/stdlib/avm'; -import { AztecAddress } from '@aztec/stdlib/aztec-address'; -import { GasFees } from '@aztec/stdlib/gas'; -import type { MerkleTreeWriteOperations, ServerCircuitProver } from '@aztec/stdlib/interfaces/server'; -import { - ParityBasePrivateInputs, - type ParityBaseProofData, - ParityPublicInputs, - ParityRootPrivateInputs, -} from '@aztec/stdlib/parity'; -import { ProofData, type RecursiveProof, makeEmptyRecursiveProof } from '@aztec/stdlib/proofs'; -import { - BlockRootEmptyTxFirstRollupPrivateInputs, - BlockRootFirstRollupPrivateInputs, - BlockRootSingleTxFirstRollupPrivateInputs, - CheckpointConstantData, - type PrivateBaseRollupHints, - PrivateTxBaseRollupPrivateInputs, - TxMergeRollupPrivateInputs, - type TxRollupPublicInputs, -} from '@aztec/stdlib/rollup'; -import { mockProcessedTx } from '@aztec/stdlib/testing'; -import { type AppendOnlyTreeSnapshot, MerkleTreeId, PublicDataTreeLeaf } from '@aztec/stdlib/trees'; -import { GlobalVariables, type ProcessedTx } from '@aztec/stdlib/tx'; -import { type MerkleTreeAdminDatabase, NativeWorldStateService } from '@aztec/world-state'; - -import { jest } from '@jest/globals'; - -import { - buildHeaderFromCircuitOutputs, - getRootTreeSiblingPath, - getSubtreeSiblingPath, - getTreeSnapshot, - insertSideEffectsAndBuildBaseRollupHints, -} from '../orchestrator/block-building-helpers.js'; -import { buildBlockWithCleanDB } from './light.js'; - -jest.setTimeout(50_000); - -describe('LightBlockBuilder', () => { - let simulator: ServerCircuitProver; - let globalVariables: GlobalVariables; - let l1ToL2Messages: Fr[]; - let vkTreeRoot: Fr; - - let db: MerkleTreeAdminDatabase; - let fork: MerkleTreeWriteOperations; - let expectsFork: MerkleTreeWriteOperations; - - let emptyProof: RecursiveProof; - let emptyRollupProof: RecursiveProof; - let emptyChonkProof: RecursiveProof; - - let feePayer: AztecAddress; - let feePayerSlot: Fr; - let feePayerBalance: Fr; - const gasFees = new GasFees(8, 9); - const expectedTxFee = new Fr(0x2200); - const proverId = new Fr(112233); - - beforeAll(() => { - simulator = new TestCircuitProver(); - vkTreeRoot = getVKTreeRoot(); - emptyProof = makeEmptyRecursiveProof(NESTED_RECURSIVE_PROOF_LENGTH); - emptyRollupProof = makeEmptyRecursiveProof(NESTED_RECURSIVE_ROLLUP_HONK_PROOF_LENGTH); - emptyChonkProof = makeEmptyRecursiveProof(CHONK_PROOF_LENGTH); - }); - - beforeEach(async () => { - feePayer = await AztecAddress.random(); - feePayerBalance = new Fr(10n ** 20n); - feePayerSlot = await computeFeePayerBalanceLeafSlot(feePayer); - const prefilledPublicData = [new PublicDataTreeLeaf(feePayerSlot, feePayerBalance)]; - - db = await NativeWorldStateService.tmp( - undefined /* rollupAddress */, - true /* cleanupTmpDir */, - prefilledPublicData, - ); - - l1ToL2Messages = times(7, i => new Fr(i + 1)); - fork = await db.fork(); - expectsFork = await db.fork(); - const initialHeader = fork.getInitialHeader(); - globalVariables = GlobalVariables.from({ - ...initialHeader.globalVariables, - gasFees, - blockNumber: BlockNumber(initialHeader.globalVariables.blockNumber + 1), - timestamp: initialHeader.globalVariables.timestamp + 1n, - }); - }); - - afterEach(async () => { - await fork.close(); - await expectsFork.close(); - }); - - afterAll(async () => { - await db.close(); - }); - - it('builds a 2 tx header', async () => { - const txs = await timesParallel(2, makeTx); - const header = await buildHeader(txs, l1ToL2Messages); - - const expectedHeader = await buildExpectedHeader(txs, l1ToL2Messages); - - expect(header.equals(expectedHeader)).toBe(true); - }); - - it('builds a 3 tx header', async () => { - const txs = await timesParallel(3, makeTx); - const header = await buildHeader(txs, l1ToL2Messages); - - const expectedHeader = await buildExpectedHeader(txs, l1ToL2Messages, async rollupOutputs => { - const merge = await getMergeOutput(rollupOutputs[0], rollupOutputs[1]); - return Promise.resolve([merge, rollupOutputs[2]]); - }); - - expect(header.equals(expectedHeader)).toBe(true); - }); - - it('builds a 4 tx header', async () => { - const txs = await timesParallel(4, makeTx); - const header = await buildHeader(txs, l1ToL2Messages); - - const expectedHeader = await buildExpectedHeader(txs, l1ToL2Messages, async rollupOutputs => { - const mergeLeft = await getMergeOutput(rollupOutputs[0], rollupOutputs[1]); - const mergeRight = await getMergeOutput(rollupOutputs[2], rollupOutputs[3]); - return [mergeLeft, mergeRight]; - }); - - expect(header.equals(expectedHeader)).toBe(true); - }); - - it('builds a 4 tx header with no l1 to l2 messages', async () => { - const l1ToL2Messages: Fr[] = []; - const txs = await timesParallel(4, makeTx); - const header = await buildHeader(txs, l1ToL2Messages); - - const expectedHeader = await buildExpectedHeader(txs, l1ToL2Messages, async rollupOutputs => { - const mergeLeft = await getMergeOutput(rollupOutputs[0], rollupOutputs[1]); - const mergeRight = await getMergeOutput(rollupOutputs[2], rollupOutputs[3]); - return [mergeLeft, mergeRight]; - }); - - expect(header.equals(expectedHeader)).toBe(true); - }); - - it('builds a 5 tx header', async () => { - const txs = await timesParallel(5, makeTx); - const header = await buildHeader(txs, l1ToL2Messages); - - const expectedHeader = await buildExpectedHeader(txs, l1ToL2Messages, async rollupOutputs => { - const merge10 = await getMergeOutput(rollupOutputs[0], rollupOutputs[1]); - const merge11 = await getMergeOutput(rollupOutputs[2], rollupOutputs[3]); - const merge20 = await getMergeOutput(merge10, merge11); - return [merge20, rollupOutputs[4]]; - }); - - expect(header.equals(expectedHeader)).toBe(true); - }); - - it('builds a single tx header', async () => { - const txs = await timesParallel(1, makeTx); - const header = await buildHeader(txs, l1ToL2Messages); - - const expectedHeader = await buildExpectedHeader(txs, l1ToL2Messages); - - expect(header.equals(expectedHeader)).toBe(true); - }); - - it('builds an empty header', async () => { - const txs: ProcessedTx[] = []; - const header = await buildHeader(txs, l1ToL2Messages); - - const expectedHeader = await buildExpectedHeader(txs, l1ToL2Messages); - - expect(header.equals(expectedHeader)).toBe(true); - }); - - const makeTx = (i: number) => { - feePayerBalance = new Fr(feePayerBalance.toBigInt() - expectedTxFee.toBigInt()); - const feePaymentPublicDataWrite = new PublicDataWrite(feePayerSlot, feePayerBalance); - - return mockProcessedTx({ - anchorBlockHeader: fork.getInitialHeader(), - globalVariables, - vkTreeRoot, - protocolContracts: ProtocolContractsList, - seed: i + 1, - feePayer, - feePaymentPublicDataWrite, - privateOnly: true, - }); - }; - - // Builds the block header using the ts block builder - const buildHeader = async (txs: ProcessedTx[], l1ToL2Messages: Fr[]) => { - const block = await buildBlockWithCleanDB(txs, globalVariables, l1ToL2Messages, fork); - - return block.header; - }; - - // Builds the block header using circuit outputs - // Requires a callback for manually assembling the merge rollup tree - const buildExpectedHeader = async ( - txs: ProcessedTx[], - l1ToL2Messages: Fr[], - getTopMerges?: (rollupOutputs: TxRollupPublicInputs[]) => Promise, - ) => { - if (txs.length <= 2) { - // No need to run a merge if there's 0-2 txs - getTopMerges = rollupOutputs => Promise.resolve(rollupOutputs); - } - - // Get the states before inserting new leaves. - const lastArchive = await getTreeSnapshot(MerkleTreeId.ARCHIVE, expectsFork); - const lastArchiveSiblingPath = await getRootTreeSiblingPath(MerkleTreeId.ARCHIVE, expectsFork); - const lastL1ToL2MessageSubtreeRootSiblingPath = padArrayEnd( - await getSubtreeSiblingPath(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, L1_TO_L2_MSG_SUBTREE_HEIGHT, expectsFork), - Fr.ZERO, - L1_TO_L2_MSG_SUBTREE_ROOT_SIBLING_PATH_LENGTH, - ); - const lastL1ToL2Snapshot = await getTreeSnapshot(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, expectsFork); - - const parityOutput = await getParityOutput(l1ToL2Messages); - const newL1ToL2Snapshot = await getTreeSnapshot(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, expectsFork); - - const spongeBlobState = SpongeBlob.init(); - const rollupOutputs = await getPrivateBaseRollupOutputs(txs, lastArchive, newL1ToL2Snapshot, spongeBlobState); - - const previousRollups = await getTopMerges!(rollupOutputs); - const rootOutput = await getBlockRootOutput( - previousRollups, - parityOutput, - lastArchive, - lastArchiveSiblingPath, - lastL1ToL2Snapshot, - lastL1ToL2MessageSubtreeRootSiblingPath, - ); - - // Absorb blob end states into the sponge blob. - const noteHashSnapshot = await getTreeSnapshot(MerkleTreeId.NOTE_HASH_TREE, expectsFork); - const nullifierSnapshot = await getTreeSnapshot(MerkleTreeId.NULLIFIER_TREE, expectsFork); - const publicDataSnapshot = await getTreeSnapshot(MerkleTreeId.PUBLIC_DATA_TREE, expectsFork); - const blockEndStates = encodeBlockEndBlobData({ - blockEndMarker: { - blockNumber: globalVariables.blockNumber, - timestamp: globalVariables.timestamp, - numTxs: txs.length, - }, - blockEndStateField: { - l1ToL2MessageNextAvailableLeafIndex: newL1ToL2Snapshot.nextAvailableLeafIndex, - noteHashNextAvailableLeafIndex: noteHashSnapshot.nextAvailableLeafIndex, - nullifierNextAvailableLeafIndex: nullifierSnapshot.nextAvailableLeafIndex, - publicDataNextAvailableLeafIndex: publicDataSnapshot.nextAvailableLeafIndex, - totalManaUsed: txs.reduce((acc, tx) => acc + BigInt(tx.gasUsed.totalGas.l2Gas), 0n), - }, - lastArchiveRoot: lastArchive.root, - noteHashRoot: noteHashSnapshot.root, - nullifierRoot: nullifierSnapshot.root, - publicDataRoot: publicDataSnapshot.root, - l1ToL2MessageRoot: newL1ToL2Snapshot.root, - }); - await spongeBlobState.absorb(blockEndStates); - - const expectedHeader = await buildHeaderFromCircuitOutputs(rootOutput); - expect(expectedHeader.spongeBlobHash).toEqual(await spongeBlobState.squeeze()); - - // Ensure that the expected mana used is the sum of the txs' gas used - const expectedManaUsed = txs.reduce((acc, tx) => acc + tx.gasUsed.totalGas.l2Gas, 0); - expect(expectedHeader.totalManaUsed.toNumber()).toBe(expectedManaUsed); - - await expectsFork.updateArchive(expectedHeader); - const newArchiveRoot = (await expectsFork.getTreeInfo(MerkleTreeId.ARCHIVE)).root; - expect(newArchiveRoot).toEqual(rootOutput.newArchive.root.toBuffer()); - - return expectedHeader; - }; - - const getPrivateBaseRollupOutputs = async ( - txs: ProcessedTx[], - lastArchive: AppendOnlyTreeSnapshot, - newL1ToL2Snapshot: AppendOnlyTreeSnapshot, - // Mutable state. - spongeBlobState: SpongeBlob, - ) => { - const rollupOutputs = []; - for (const tx of txs) { - const vkData = getVkData('HidingKernelToRollup'); - const hidingKernelProofData = new ProofData( - tx.data.toPrivateToRollupKernelCircuitPublicInputs(), - emptyChonkProof, - vkData, - ); - const hints = await insertSideEffectsAndBuildBaseRollupHints( - tx, - lastArchive, - newL1ToL2Snapshot, - spongeBlobState.clone(), - proverId, - expectsFork, - ); - await spongeBlobState.absorb(tx.txEffect.toBlobFields()); - const inputs = new PrivateTxBaseRollupPrivateInputs(hidingKernelProofData, hints as PrivateBaseRollupHints); - const result = await simulator.getPrivateTxBaseRollupProof(inputs); - // Update `expectedTxFee` if the fee changes. - expect(result.inputs.accumulatedFees).toEqual(expectedTxFee); - rollupOutputs.push(result.inputs); - } - return rollupOutputs; - }; - - const getMergeOutput = async (left: TxRollupPublicInputs, right: TxRollupPublicInputs) => { - const baseRollupVk = getVkData('PrivateTxBaseRollupArtifact'); - const leftInput = new ProofData(left, emptyRollupProof, baseRollupVk); - const rightInput = new ProofData(right, emptyRollupProof, baseRollupVk); - const inputs = new TxMergeRollupPrivateInputs([leftInput, rightInput]); - const result = await simulator.getTxMergeRollupProof(inputs); - return result.inputs; - }; - - const getParityOutput = async (msgs: Fr[]) => { - const l1ToL2Messages = padArrayEnd(msgs, Fr.ZERO, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP); - await expectsFork.appendLeaves(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, l1ToL2Messages); - - const parityBases: ParityBaseProofData[] = []; - const baseParityVk = getVkData('ParityBaseArtifact'); - for (let i = 0; i < NUM_BASE_PARITY_PER_ROOT_PARITY; i++) { - const input = ParityBasePrivateInputs.fromSlice(l1ToL2Messages, i, vkTreeRoot); - const { inputs } = await simulator.getBaseParityProof(input); - parityBases.push(new ProofData(inputs, emptyProof, baseParityVk)); - } - - const rootParityInput = new ParityRootPrivateInputs(assertLength(parityBases, NUM_BASE_PARITY_PER_ROOT_PARITY)); - const result = await simulator.getRootParityProof(rootParityInput); - return result.inputs; - }; - - const getBlockRootOutput = async ( - previousRollups: TxRollupPublicInputs[], - parityOutput: ParityPublicInputs, - lastArchive: AppendOnlyTreeSnapshot, - lastArchiveSiblingPath: Tuple, - lastL1ToL2Snapshot: AppendOnlyTreeSnapshot, - lastL1ToL2MessageSubtreeRootSiblingPath: Tuple, - ) => { - const mergeRollupVk = getVkData( - previousRollups.length === 1 ? 'PrivateTxBaseRollupArtifact' : 'TxMergeRollupArtifact', - ); - const previousRollupsProofs = previousRollups.map(r => new ProofData(r, emptyRollupProof, mergeRollupVk)); - - const rootParityVk = getVkData('ParityRootArtifact'); - const l1ToL2Roots = new ProofData(parityOutput, emptyProof, rootParityVk); - - // The sibling paths to insert the new leaf are the last sibling paths. - const newArchiveSiblingPath = lastArchiveSiblingPath; - const newL1ToL2MessageSubtreeRootSiblingPath = lastL1ToL2MessageSubtreeRootSiblingPath; - - if (previousRollups.length === 0) { - const previousBlockHeader = expectsFork.getInitialHeader(); - const constants = CheckpointConstantData.from({ - chainId: globalVariables.chainId, - version: globalVariables.version, - vkTreeRoot, - protocolContractsHash, - proverId, - slotNumber: globalVariables.slotNumber, - coinbase: globalVariables.coinbase, - feeRecipient: globalVariables.feeRecipient, - gasFees: globalVariables.gasFees, - }); - const inputs = BlockRootEmptyTxFirstRollupPrivateInputs.from({ - l1ToL2Roots, - previousState: previousBlockHeader.state, - previousArchive: lastArchive, - constants, - timestamp: globalVariables.timestamp, - newArchiveSiblingPath, - newL1ToL2MessageSubtreeRootSiblingPath, - }); - return (await simulator.getBlockRootEmptyTxFirstRollupProof(inputs)).inputs; - } else if (previousRollups.length === 1) { - const inputs = BlockRootSingleTxFirstRollupPrivateInputs.from({ - l1ToL2Roots, - previousRollup: previousRollupsProofs[0], - previousL1ToL2: lastL1ToL2Snapshot, - newArchiveSiblingPath, - newL1ToL2MessageSubtreeRootSiblingPath, - }); - return (await simulator.getBlockRootSingleTxFirstRollupProof(inputs)).inputs; - } else { - const inputs = BlockRootFirstRollupPrivateInputs.from({ - l1ToL2Roots, - previousRollups: [previousRollupsProofs[0], previousRollupsProofs[1]], - previousL1ToL2: lastL1ToL2Snapshot, - newArchiveSiblingPath, - newL1ToL2MessageSubtreeRootSiblingPath, - }); - return (await simulator.getBlockRootFirstRollupProof(inputs)).inputs; - } - }; -}); diff --git a/yarn-project/prover-client/src/block-factory/light.ts b/yarn-project/prover-client/src/block-factory/light.ts deleted file mode 100644 index 7ceb754a95ce..000000000000 --- a/yarn-project/prover-client/src/block-factory/light.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { SpongeBlob } from '@aztec/blob-lib'; -import { NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP } from '@aztec/constants'; -import { CheckpointNumber } from '@aztec/foundation/branded-types'; -import { padArrayEnd } from '@aztec/foundation/collection'; -import { Fr } from '@aztec/foundation/curves/bn254'; -import { createLogger } from '@aztec/foundation/log'; -import { L2BlockNew } from '@aztec/stdlib/block'; -import type { IBlockFactory, MerkleTreeWriteOperations } from '@aztec/stdlib/interfaces/server'; -import { MerkleTreeId } from '@aztec/stdlib/trees'; -import type { GlobalVariables, ProcessedTx } from '@aztec/stdlib/tx'; -import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client'; - -import { - buildHeaderAndBodyFromTxs, - getTreeSnapshot, - insertSideEffects, -} from '../orchestrator/block-building-helpers.js'; - -/** - * Builds a block and its header from a set of processed tx without running any circuits. - * - * NOTE: the onus is ON THE CALLER to update the db that is passed in with the notes hashes, nullifiers, etc - * PRIOR to calling `buildBlock`. - * - * Why? Because if you are, e.g. building a block in practice from TxObjects, you are using the - * PublicProcessor which will do this for you as it processes transactions. - * - * If you haven't already inserted the side effects, e.g. because you are in a testing context, you can use the helper - * function `buildBlockWithCleanDB`, which calls `insertSideEffects` for you. - * - * @deprecated Use LightweightCheckpointBuilder instead. This only works for one block per checkpoint. - */ -export class LightweightBlockFactory implements IBlockFactory { - private globalVariables?: GlobalVariables; - private l1ToL2Messages?: Fr[]; - private txs: ProcessedTx[] | undefined; - - private readonly logger = createLogger('lightweight-block-factory'); - - constructor( - private previousCheckpointOutHashes: Fr[], - private db: MerkleTreeWriteOperations, - private telemetry: TelemetryClient = getTelemetryClient(), - ) {} - - async startNewBlock(globalVariables: GlobalVariables, l1ToL2Messages: Fr[]): Promise { - this.logger.debug('Starting new block', { globalVariables: globalVariables.toInspect(), l1ToL2Messages }); - this.globalVariables = globalVariables; - this.l1ToL2Messages = padArrayEnd(l1ToL2Messages, Fr.ZERO, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP); - this.txs = undefined; - // Update L1 to L2 tree - await this.db.appendLeaves(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, this.l1ToL2Messages!); - } - - addTxs(txs: ProcessedTx[]): Promise { - // Most times, `addTxs` is only called once per block. - // So avoid copies. - if (this.txs === undefined) { - this.txs = txs; - } else { - this.txs.push(...txs); - } - return Promise.resolve(); - } - - setBlockCompleted(): Promise { - return this.buildBlock(); - } - - private async buildBlock(): Promise { - const lastArchive = await getTreeSnapshot(MerkleTreeId.ARCHIVE, this.db); - const state = await this.db.getStateReference(); - - const txs = this.txs ?? []; - const startSpongeBlob = SpongeBlob.init(); - - const { header, body } = await buildHeaderAndBodyFromTxs( - txs, - lastArchive, - state, - this.globalVariables!, - startSpongeBlob, - true, - ); - - header.state.validate(); - - await this.db.updateArchive(header); - const newArchive = await getTreeSnapshot(MerkleTreeId.ARCHIVE, this.db); - - // For LightweightBlockFactory, we assume one block per checkpoint - const checkpointNumber = CheckpointNumber.fromBlockNumber(header.globalVariables.blockNumber); - const indexWithinCheckpoint = 0; - - const block = new L2BlockNew(newArchive, header, body, checkpointNumber, indexWithinCheckpoint); - - this.logger.debug(`Built block ${block.number}`, { - globalVariables: this.globalVariables?.toInspect(), - archiveRoot: newArchive.root.toString(), - stateReference: header.state.toInspect(), - blockHash: (await block.hash()).toString(), - txs: block.body.txEffects.map(tx => tx.txHash.toString()), - }); - - return block; - } -} - -/** - * Inserts the processed transactions into the DB, then creates a block. - * @param db - A db fork to use for block building which WILL BE MODIFIED. - */ -export async function buildBlockWithCleanDB( - txs: ProcessedTx[], - globalVariables: GlobalVariables, - l1ToL2Messages: Fr[], - db: MerkleTreeWriteOperations, - telemetry: TelemetryClient = getTelemetryClient(), -) { - const builder = new LightweightBlockFactory([], db, telemetry); - await builder.startNewBlock(globalVariables, l1ToL2Messages); - - for (const tx of txs) { - await insertSideEffects(tx, db); - } - await builder.addTxs(txs); - - return await builder.setBlockCompleted(); -} diff --git a/yarn-project/sequencer-client/src/index.ts b/yarn-project/sequencer-client/src/index.ts index a0ef3ae401b6..2375b9013a90 100644 --- a/yarn-project/sequencer-client/src/index.ts +++ b/yarn-project/sequencer-client/src/index.ts @@ -1,12 +1,7 @@ export * from './client/index.js'; export * from './config.js'; export * from './publisher/index.js'; -export { - FullNodeBlockBuilder as BlockBuilder, - Sequencer, - SequencerState, - type SequencerEvents, -} from './sequencer/index.js'; +export { Sequencer, SequencerState, type SequencerEvents } from './sequencer/index.js'; // Used by the node to simulate public parts of transactions. Should these be moved to a shared library? // ISSUE(#9832) diff --git a/yarn-project/sequencer-client/src/sequencer/block_builder.test.ts b/yarn-project/sequencer-client/src/sequencer/block_builder.test.ts deleted file mode 100644 index 0985357642c0..000000000000 --- a/yarn-project/sequencer-client/src/sequencer/block_builder.test.ts +++ /dev/null @@ -1,247 +0,0 @@ -import { DefaultL1ContractsConfig } from '@aztec/ethereum/config'; -import { BlockNumber, SlotNumber } from '@aztec/foundation/branded-types'; -import { timesParallel } from '@aztec/foundation/collection'; -import { Fr } from '@aztec/foundation/curves/bn254'; -import { EthAddress } from '@aztec/foundation/eth-address'; -import { createLogger } from '@aztec/foundation/log'; -import { TestDateProvider } from '@aztec/foundation/timer'; -import type { PublicProcessor } from '@aztec/simulator/server'; -import { PublicDataWrite } from '@aztec/stdlib/avm'; -import { AztecAddress } from '@aztec/stdlib/aztec-address'; -import type { ContractDataSource } from '@aztec/stdlib/contract'; -import { GasFees } from '@aztec/stdlib/gas'; -import { - type PublicProcessorValidator, - WorldStateRunningState, - type WorldStateSynchronizer, - type WorldStateSynchronizerStatus, -} from '@aztec/stdlib/interfaces/server'; -import { makeStateReference, mockTxForRollup } from '@aztec/stdlib/testing'; -import { MerkleTreeId, type MerkleTreeWriteOperations } from '@aztec/stdlib/trees'; -import { - BlockHeader, - type FailedTx, - GlobalVariables, - NestedProcessReturnValues, - type ProcessedTx, - Tx, - makeProcessedTxFromPrivateOnlyTx, -} from '@aztec/stdlib/tx'; - -import { type MockProxy, mock, mockFn } from 'jest-mock-extended'; - -import { FullNodeBlockBuilder } from './block_builder.js'; - -const logger = createLogger('BlockBuilderTest'); - -describe('BlockBuilder', () => { - let blockBuilder: FullNodeBlockBuilder; - let newSlotNumber: number; - let initialBlockHeader: BlockHeader; - const chainId: number = 12345; - const version: number = 1; - let hash: string; - let lastBlockNumber: BlockNumber; - let newBlockNumber: BlockNumber; - let globalVariables: GlobalVariables; - let worldState: MockProxy; - let fork: MockProxy; - let contractDataSource: MockProxy; - let publicProcessor: MockProxy; - let validator: MockProxy; - - const { aztecSlotDuration: slotDuration, ethereumSlotDuration } = DefaultL1ContractsConfig; - - const coinbase = EthAddress.random(); - let feeRecipient: AztecAddress; - const gasFees = GasFees.empty(); - - const mockTxIterator = async function* (txs: Tx[]): AsyncIterableIterator { - for (const tx of txs) { - yield tx; - } - }; - - const makeTx = async (seed?: number) => { - const tx = await mockTxForRollup(seed); - tx.data.constants.txContext.chainId = new Fr(chainId); - return tx; - }; - - class TestBlockBuilder extends FullNodeBlockBuilder { - public override makeBlockBuilderDeps(_globalVariables: GlobalVariables) { - return Promise.resolve({ - publicProcessorDBFork: fork, - processor: publicProcessor, - validator, - }); - } - } - - beforeEach(async () => { - feeRecipient = await AztecAddress.random(); - hash = Fr.ZERO.toString(); - initialBlockHeader = BlockHeader.empty(); - lastBlockNumber = BlockNumber.ZERO; - newBlockNumber = BlockNumber(lastBlockNumber + 1); - newSlotNumber = newBlockNumber + 1; - globalVariables = new GlobalVariables( - new Fr(chainId), - new Fr(version), - newBlockNumber, - SlotNumber(newSlotNumber), - /*timestamp=*/ 0n, - coinbase, - feeRecipient, - gasFees, - ); - - const l1GenesisTime = BigInt(Math.floor(Date.now() / 1000)); - const l1Constants = { - l1GenesisTime, - slotDuration, - ethereumSlotDuration, - l1ChainId: chainId, - rollupVersion: version, - }; - - fork = mock({ - getInitialHeader: () => initialBlockHeader, - getTreeInfo: (treeId: MerkleTreeId) => - Promise.resolve({ treeId, root: Fr.random().toBuffer(), size: 1024n, depth: 10 }), - findLeafIndices: (_treeId: MerkleTreeId, _values: any[]) => Promise.resolve([undefined]), - getStateReference: () => Promise.resolve(makeStateReference()), - }); - - worldState = mock({ - fork: () => Promise.resolve(fork), - syncImmediate: () => Promise.resolve(lastBlockNumber), - getCommitted: () => fork, - status: mockFn().mockResolvedValue({ - state: WorldStateRunningState.IDLE, - syncSummary: { - latestBlockNumber: lastBlockNumber, - latestBlockHash: hash, - finalizedBlockNumber: BlockNumber.ZERO, - oldestHistoricBlockNumber: BlockNumber.ZERO, - treesAreSynched: true, - }, - } satisfies WorldStateSynchronizerStatus), - }); - - contractDataSource = mock(); - const dateProvider = new TestDateProvider(); - publicProcessor = mock(); - validator = mock(); - - publicProcessor.process.mockImplementation( - async ( - pendingTxsIterator: AsyncIterable | Iterable, - ): Promise<[ProcessedTx[], FailedTx[], Tx[], NestedProcessReturnValues[], number]> => { - const processedTxs: ProcessedTx[] = []; - const allTxs: Tx[] = []; - let totalBlobFields = 0; - - for await (const tx of pendingTxsIterator) { - allTxs.push(tx); - const processedTx = makeProcessedTxFromPrivateOnlyTx( - tx, - Fr.ZERO, - new PublicDataWrite(Fr.random(), Fr.random()), - globalVariables, - ); - processedTxs.push(processedTx); - totalBlobFields += processedTx.txEffect.getNumBlobFields(); - } - // Assuming all txs are processed successfully and none failed for this mock - return [processedTxs, [], allTxs, [], totalBlobFields]; - }, - ); - blockBuilder = new TestBlockBuilder(l1Constants, worldState, contractDataSource, dateProvider); - }); - - it('builds a block out of a single tx', async () => { - const tx = await makeTx(); - const iterator = mockTxIterator([tx]); - - const blockResult = await blockBuilder.buildBlock(iterator, [], [], globalVariables, {}); - expect(publicProcessor.process).toHaveBeenCalledTimes(1); - expect(publicProcessor.process).toHaveBeenCalledWith(iterator, {}, validator); - logger.info('Built Block', blockResult.block); - expect(blockResult.block.header.globalVariables.blockNumber).toBe(newBlockNumber); - expect(blockResult.block.header.globalVariables.slotNumber).toBe(newSlotNumber); - expect(blockResult.block.header.globalVariables.coinbase.toString()).toBe(coinbase.toString()); - expect(blockResult.block.header.globalVariables.feeRecipient.toString()).toBe(feeRecipient.toString()); - expect(blockResult.block.header.globalVariables.gasFees).toEqual(GasFees.empty()); - expect(blockResult.block.header.globalVariables.chainId.toNumber()).toBe(chainId); - expect(blockResult.block.header.globalVariables.version.toNumber()).toBe(version); - expect(blockResult.block.body.txEffects.length).toBe(1); - expect(blockResult.block.body.txEffects[0].txHash).toBe(tx.getTxHash()); - }); - - it('builds a block with the correct options', async () => { - const txs = await timesParallel(5, i => makeTx(i * 0x10000)); - const deadline = new Date(Date.now() + 1000); - await blockBuilder.buildBlock(txs, [], [], globalVariables, { - maxTransactions: 4, - deadline, - }); - - expect(publicProcessor.process).toHaveBeenCalledWith( - txs, - { - maxTransactions: 4, - deadline, - }, - validator, - ); - }); - - it('builds a block for validation ignoring limits', async () => { - const txs = await timesParallel(5, i => makeTx(i * 0x10000)); - await blockBuilder.buildBlock(txs, [], [], globalVariables, {}); - - expect(publicProcessor.process).toHaveBeenCalledWith(txs, {}, validator); - }); - - it('builds a block out of several txs rejecting invalid txs', async () => { - const txs = await Promise.all([makeTx(0x10000), makeTx(0x20000), makeTx(0x30000)]); - const validTxs = [txs[0], txs[2]]; - const invalidTx = txs[1]; - const validTxHashes = await Promise.all(validTxs.map(tx => tx.getTxHash())); - - publicProcessor.process.mockImplementation( - async ( - pendingTxsIterator: AsyncIterable | Iterable, - ): Promise<[ProcessedTx[], FailedTx[], Tx[], NestedProcessReturnValues[], number]> => { - const processedTxs: ProcessedTx[] = []; - const usedTxs: Tx[] = []; - const failedTxs: FailedTx[] = []; - let totalBlobFields = 0; - - for await (const tx of pendingTxsIterator) { - if (validTxHashes.includes(tx.getTxHash())) { - usedTxs.push(tx); - const processedTx = makeProcessedTxFromPrivateOnlyTx( - tx, - Fr.ZERO, - new PublicDataWrite(Fr.random(), Fr.random()), - globalVariables, - ); - - processedTxs.push(processedTx); - totalBlobFields += processedTx.txEffect.getNumBlobFields(); - } else { - failedTxs.push({ tx, error: new Error() }); - } - } - // Assuming all txs are processed successfully and none failed for this mock - return [processedTxs, failedTxs, usedTxs, [], totalBlobFields]; - }, - ); - - const blockResult = await blockBuilder.buildBlock(txs, [], [], globalVariables, {}); - expect(blockResult.failedTxs).toEqual([{ tx: invalidTx, error: new Error() }]); - expect(blockResult.usedTxs).toEqual(validTxs); - }); -}); diff --git a/yarn-project/sequencer-client/src/sequencer/block_builder.ts b/yarn-project/sequencer-client/src/sequencer/block_builder.ts deleted file mode 100644 index 8a850947bfb1..000000000000 --- a/yarn-project/sequencer-client/src/sequencer/block_builder.ts +++ /dev/null @@ -1,220 +0,0 @@ -import { MerkleTreeId } from '@aztec/aztec.js/trees'; -import { BlockNumber } from '@aztec/foundation/branded-types'; -import { merge, pick } from '@aztec/foundation/collection'; -import type { Fr } from '@aztec/foundation/curves/bn254'; -import { createLogger } from '@aztec/foundation/log'; -import { retryUntil } from '@aztec/foundation/retry'; -import { bufferToHex } from '@aztec/foundation/string'; -import { DateProvider, Timer, elapsed } from '@aztec/foundation/timer'; -import { getDefaultAllowedSetupFunctions } from '@aztec/p2p/msg_validators'; -import { LightweightBlockFactory } from '@aztec/prover-client/block-factory'; -import { - GuardedMerkleTreeOperations, - PublicContractsDB, - PublicProcessor, - createPublicTxSimulatorForBlockBuilding, -} from '@aztec/simulator/server'; -import type { ContractDataSource } from '@aztec/stdlib/contract'; -import { type L1RollupConstants, getTimestampForSlot } from '@aztec/stdlib/epoch-helpers'; -import { Gas } from '@aztec/stdlib/gas'; -import type { - BuildBlockResult, - FullNodeBlockBuilderConfig, - IFullNodeBlockBuilder, - MerkleTreeWriteOperations, - PublicProcessorLimits, - PublicProcessorValidator, - WorldStateSynchronizer, -} from '@aztec/stdlib/interfaces/server'; -import { GlobalVariables, Tx } from '@aztec/stdlib/tx'; -import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client'; -import { createValidatorForBlockBuilding } from '@aztec/validator-client'; - -const log = createLogger('block-builder'); - -/** Builds a block out of pending txs */ -async function buildBlock( - pendingTxs: Iterable | AsyncIterable, - l1ToL2Messages: Fr[], - previousCheckpointOutHashes: Fr[], - newGlobalVariables: GlobalVariables, - opts: PublicProcessorLimits = {}, - worldStateFork: MerkleTreeWriteOperations, - processor: PublicProcessor, - validator: PublicProcessorValidator, - l1Constants: Pick, - dateProvider: DateProvider, - telemetryClient: TelemetryClient = getTelemetryClient(), -): Promise { - const blockBuildingTimer = new Timer(); - const blockNumber = newGlobalVariables.blockNumber; - const slot = newGlobalVariables.slotNumber; - const msgCount = l1ToL2Messages.length; - const stateReference = await worldStateFork.getStateReference(); - const archiveTree = await worldStateFork.getTreeInfo(MerkleTreeId.ARCHIVE); - - log.verbose(`Building block ${blockNumber} for slot ${slot}`, { - slot, - slotStart: new Date(Number(getTimestampForSlot(slot, l1Constants)) * 1000), - now: new Date(dateProvider.now()), - blockNumber, - msgCount, - initialStateReference: stateReference.toInspect(), - initialArchiveRoot: bufferToHex(archiveTree.root), - opts, - }); - const blockFactory = new LightweightBlockFactory(previousCheckpointOutHashes, worldStateFork, telemetryClient); - await blockFactory.startNewBlock(newGlobalVariables, l1ToL2Messages); - - const [publicProcessorDuration, [processedTxs, failedTxs, usedTxs, _, usedTxBlobFields]] = await elapsed(() => - processor.process(pendingTxs, opts, validator), - ); - - // All real transactions have been added, set the block as full and pad if needed - await blockFactory.addTxs(processedTxs); - const block = await blockFactory.setBlockCompleted(); - - // How much public gas was processed - const publicGas = processedTxs.reduce((acc, tx) => acc.add(tx.gasUsed.publicGas), Gas.empty()); - - const res = { - block, - publicGas, - publicProcessorDuration, - numMsgs: l1ToL2Messages.length, - numTxs: processedTxs.length, - failedTxs: failedTxs, - blockBuildingTimer, - usedTxs, - usedTxBlobFields, - }; - log.trace('Built block', res.block.header); - return res; -} - -const FullNodeBlockBuilderConfigKeys = [ - 'l1GenesisTime', - 'slotDuration', - 'l1ChainId', - 'rollupVersion', - 'txPublicSetupAllowList', - 'fakeProcessingDelayPerTxMs', - 'fakeThrowAfterProcessingTxCount', -] as const; - -// TODO(palla/mbps): Try killing this in favor of the CheckpointsBuilder -export class FullNodeBlockBuilder implements IFullNodeBlockBuilder { - constructor( - private config: FullNodeBlockBuilderConfig, - private worldState: WorldStateSynchronizer, - private contractDataSource: ContractDataSource, - private dateProvider: DateProvider, - private telemetryClient: TelemetryClient = getTelemetryClient(), - ) {} - - public getConfig(): FullNodeBlockBuilderConfig { - return pick(this.config, ...FullNodeBlockBuilderConfigKeys); - } - - public updateConfig(config: Partial) { - this.config = merge(this.config, pick(config, ...FullNodeBlockBuilderConfigKeys)); - } - - public async makeBlockBuilderDeps(globalVariables: GlobalVariables, fork: MerkleTreeWriteOperations) { - const txPublicSetupAllowList = this.config.txPublicSetupAllowList ?? (await getDefaultAllowedSetupFunctions()); - const contractsDB = new PublicContractsDB(this.contractDataSource); - const guardedFork = new GuardedMerkleTreeOperations(fork); - - const publicTxSimulator = createPublicTxSimulatorForBlockBuilding( - guardedFork, - contractsDB, - globalVariables, - this.telemetryClient, - ); - - const processor = new PublicProcessor( - globalVariables, - guardedFork, - contractsDB, - publicTxSimulator, - this.dateProvider, - this.telemetryClient, - undefined, - this.config, - ); - - const validator = createValidatorForBlockBuilding( - fork, - this.contractDataSource, - globalVariables, - txPublicSetupAllowList, - ); - - return { - processor, - validator, - }; - } - - private async syncToPreviousBlock(parentBlockNumber: BlockNumber, timeout: number | undefined) { - await retryUntil( - () => this.worldState.syncImmediate(parentBlockNumber, true).then(syncedTo => syncedTo >= parentBlockNumber), - 'sync to previous block', - timeout, - 0.1, - ); - log.debug(`Synced to previous block ${parentBlockNumber}`); - } - - async buildBlock( - pendingTxs: Iterable | AsyncIterable, - l1ToL2Messages: Fr[], - previousCheckpointOutHashes: Fr[], - globalVariables: GlobalVariables, - opts: PublicProcessorLimits, - suppliedFork?: MerkleTreeWriteOperations, - ): Promise { - const parentBlockNumber = BlockNumber(globalVariables.blockNumber - 1); - const syncTimeout = opts.deadline ? (opts.deadline.getTime() - this.dateProvider.now()) / 1000 : undefined; - await this.syncToPreviousBlock(parentBlockNumber, syncTimeout); - const fork = suppliedFork ?? (await this.worldState.fork(parentBlockNumber)); - - try { - const { processor, validator } = await this.makeBlockBuilderDeps(globalVariables, fork); - const res = await buildBlock( - pendingTxs, - l1ToL2Messages, - previousCheckpointOutHashes, - globalVariables, - opts, - fork, - processor, - validator, - this.config, - this.dateProvider, - this.telemetryClient, - ); - return res; - } finally { - // If the fork was supplied, we don't close it. - // Otherwise, we wait a bit to close the fork we just created, - // since the processor may still be working on a dangling tx - // which was interrupted due to the processingDeadline being hit. - if (!suppliedFork) { - // eslint-disable-next-line @typescript-eslint/no-misused-promises - setTimeout(async () => { - try { - await fork.close(); - } catch (err) { - // This can happen if the sequencer is stopped before we hit this timeout. - log.warn(`Error closing forks for block processing`, err); - } - }, 5000); - } - } - } - - getFork(blockNumber: BlockNumber): Promise { - return this.worldState.fork(blockNumber); - } -} diff --git a/yarn-project/sequencer-client/src/sequencer/index.ts b/yarn-project/sequencer-client/src/sequencer/index.ts index af17a233c568..9f9721707764 100644 --- a/yarn-project/sequencer-client/src/sequencer/index.ts +++ b/yarn-project/sequencer-client/src/sequencer/index.ts @@ -1,4 +1,3 @@ -export * from './block_builder.js'; export * from './checkpoint_proposal_job.js'; export * from './checkpoint_voter.js'; export * from './config.js'; diff --git a/yarn-project/sequencer-client/src/test/mock_checkpoint_builder.ts b/yarn-project/sequencer-client/src/test/mock_checkpoint_builder.ts index b017fde8fb9a..32cc74a88e34 100644 --- a/yarn-project/sequencer-client/src/test/mock_checkpoint_builder.ts +++ b/yarn-project/sequencer-client/src/test/mock_checkpoint_builder.ts @@ -1,25 +1,26 @@ import { type BlockNumber, CheckpointNumber } from '@aztec/foundation/branded-types'; import { Fr } from '@aztec/foundation/curves/bn254'; import { Timer } from '@aztec/foundation/timer'; -import type { FunctionsOf } from '@aztec/foundation/types'; import { L2BlockNew } from '@aztec/stdlib/block'; import { Checkpoint } from '@aztec/stdlib/checkpoint'; import { Gas } from '@aztec/stdlib/gas'; -import type { FullNodeBlockBuilderConfig, PublicProcessorLimits } from '@aztec/stdlib/interfaces/server'; +import type { + BuildBlockInCheckpointResult, + FullNodeBlockBuilderConfig, + ICheckpointBlockBuilder, + ICheckpointsBuilder, + MerkleTreeWriteOperations, + PublicProcessorLimits, +} from '@aztec/stdlib/interfaces/server'; import { CheckpointHeader } from '@aztec/stdlib/rollup'; import { makeAppendOnlyTreeSnapshot } from '@aztec/stdlib/testing'; import type { CheckpointGlobalVariables, Tx } from '@aztec/stdlib/tx'; -import type { - BuildBlockInCheckpointResult, - CheckpointBuilder, - FullNodeCheckpointsBuilder, -} from '@aztec/validator-client'; /** * A fake CheckpointBuilder for testing that implements the same interface as the real one. * Can be seeded with blocks to return sequentially on each `buildBlock` call. */ -export class MockCheckpointBuilder implements FunctionsOf { +export class MockCheckpointBuilder implements ICheckpointBlockBuilder { private blocks: L2BlockNew[] = []; private builtBlocks: L2BlockNew[] = []; private usedTxsPerBlock: Tx[][] = []; @@ -181,7 +182,7 @@ export class MockCheckpointBuilder implements FunctionsOf { * as FullNodeCheckpointsBuilder. Returns MockCheckpointBuilder instances. * Does NOT use jest mocks - this is a proper test double. */ -export class MockCheckpointsBuilder implements FunctionsOf { +export class MockCheckpointsBuilder implements ICheckpointsBuilder { private checkpointBuilder: MockCheckpointBuilder | undefined; /** Track calls for assertions */ @@ -244,8 +245,8 @@ export class MockCheckpointsBuilder implements FunctionsOf { + _fork: MerkleTreeWriteOperations, + ): Promise { this.startCheckpointCalls.push({ checkpointNumber, constants, l1ToL2Messages, previousCheckpointOutHashes }); if (!this.checkpointBuilder) { @@ -253,7 +254,7 @@ export class MockCheckpointsBuilder implements FunctionsOf { + ): Promise { this.openCheckpointCalls.push({ checkpointNumber, constants, @@ -277,7 +278,11 @@ export class MockCheckpointsBuilder implements FunctionsOf { + throw new Error('MockCheckpointsBuilder.getFork not implemented'); } /** Reset for reuse in another test */ diff --git a/yarn-project/slasher/src/watchers/epoch_prune_watcher.test.ts b/yarn-project/slasher/src/watchers/epoch_prune_watcher.test.ts index faf1d39a56fc..ef58292e5f09 100644 --- a/yarn-project/slasher/src/watchers/epoch_prune_watcher.test.ts +++ b/yarn-project/slasher/src/watchers/epoch_prune_watcher.test.ts @@ -5,8 +5,8 @@ import { sleep } from '@aztec/foundation/sleep'; import { L2BlockNew, type L2BlockSourceEventEmitter, L2BlockSourceEvents } from '@aztec/stdlib/block'; import type { L1RollupConstants } from '@aztec/stdlib/epoch-helpers'; import type { - BuildBlockResult, - IFullNodeBlockBuilder, + ICheckpointBlockBuilder, + ICheckpointsBuilder, ITxProvider, MerkleTreeWriteOperations, } from '@aztec/stdlib/interfaces/server'; @@ -28,7 +28,8 @@ describe('EpochPruneWatcher', () => { let l1ToL2MessageSource: MockProxy; let epochCache: MockProxy; let txProvider: MockProxy>; - let blockBuilder: MockProxy; + let checkpointsBuilder: MockProxy; + let checkpointBuilder: MockProxy; let fork: MockProxy; let ts: bigint; @@ -43,9 +44,11 @@ describe('EpochPruneWatcher', () => { l1ToL2MessageSource.getL1ToL2Messages.mockResolvedValue([]); epochCache = mock(); txProvider = mock>(); - blockBuilder = mock(); + checkpointsBuilder = mock(); + checkpointBuilder = mock(); fork = mock(); - blockBuilder.getFork.mockResolvedValue(fork); + checkpointsBuilder.getFork.mockResolvedValue(fork); + checkpointsBuilder.startCheckpoint.mockResolvedValue(checkpointBuilder); ts = BigInt(Math.ceil(Date.now() / 1000)); l1Constants = { @@ -59,7 +62,7 @@ describe('EpochPruneWatcher', () => { epochCache.getL1Constants.mockReturnValue(l1Constants); - watcher = new EpochPruneWatcher(l2BlockSource, l1ToL2MessageSource, epochCache, txProvider, blockBuilder, { + watcher = new EpochPruneWatcher(l2BlockSource, l1ToL2MessageSource, epochCache, txProvider, checkpointsBuilder, { slashPrunePenalty: validEpochPrunedPenalty, slashDataWithholdingPenalty: dataWithholdingPenalty, }); @@ -130,11 +133,11 @@ describe('EpochPruneWatcher', () => { ); const tx = Tx.random(); txProvider.getAvailableTxs.mockResolvedValue({ txs: [tx], missingTxs: [] }); - blockBuilder.buildBlock.mockResolvedValue({ + checkpointBuilder.buildBlock.mockResolvedValue({ block: block, failedTxs: [], numTxs: 1, - } as unknown as BuildBlockResult); + } as any); const committee: Hex[] = [ '0x0000000000000000000000000000000000000abc', @@ -170,7 +173,13 @@ describe('EpochPruneWatcher', () => { }, ] satisfies WantToSlashArgs[]); - expect(blockBuilder.buildBlock).toHaveBeenCalledWith([tx], [], [], block.header.globalVariables, {}, fork); + expect(checkpointsBuilder.startCheckpoint).toHaveBeenCalled(); + expect(checkpointBuilder.buildBlock).toHaveBeenCalledWith( + [tx], + block.header.globalVariables.blockNumber, + block.header.globalVariables.timestamp, + {}, + ); }); it('should not slash if the data is available but the epoch could not have been proven', async () => { @@ -193,11 +202,11 @@ describe('EpochPruneWatcher', () => { ); const tx = Tx.random(); txProvider.getAvailableTxs.mockResolvedValue({ txs: [tx], missingTxs: [] }); - blockBuilder.buildBlock.mockResolvedValue({ + checkpointBuilder.buildBlock.mockResolvedValue({ block: blockFromBuilder, failedTxs: [], numTxs: 1, - } as unknown as BuildBlockResult); + } as any); const committee: Hex[] = [ '0x0000000000000000000000000000000000000abc', @@ -220,7 +229,13 @@ describe('EpochPruneWatcher', () => { expect(emitSpy).not.toHaveBeenCalled(); - expect(blockBuilder.buildBlock).toHaveBeenCalledWith([tx], [], [], blockFromL1.header.globalVariables, {}, fork); + expect(checkpointsBuilder.startCheckpoint).toHaveBeenCalled(); + expect(checkpointBuilder.buildBlock).toHaveBeenCalledWith( + [tx], + blockFromL1.header.globalVariables.blockNumber, + blockFromL1.header.globalVariables.timestamp, + {}, + ); }); }); diff --git a/yarn-project/slasher/src/watchers/epoch_prune_watcher.ts b/yarn-project/slasher/src/watchers/epoch_prune_watcher.ts index 9c170c85d071..c980d49240c4 100644 --- a/yarn-project/slasher/src/watchers/epoch_prune_watcher.ts +++ b/yarn-project/slasher/src/watchers/epoch_prune_watcher.ts @@ -12,13 +12,14 @@ import { } from '@aztec/stdlib/block'; import { getEpochAtSlot } from '@aztec/stdlib/epoch-helpers'; import type { - IFullNodeBlockBuilder, + ICheckpointsBuilder, ITxProvider, MerkleTreeWriteOperations, SlasherConfig, } from '@aztec/stdlib/interfaces/server'; import { type L1ToL2MessageSource, computeCheckpointOutHash } from '@aztec/stdlib/messaging'; import { OffenseType, getOffenseTypeName } from '@aztec/stdlib/slashing'; +import type { CheckpointGlobalVariables } from '@aztec/stdlib/tx'; import { ReExFailedTxsError, ReExStateMismatchError, @@ -53,7 +54,7 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter private l1ToL2MessageSource: L1ToL2MessageSource, private epochCache: EpochCache, private txProvider: Pick, - private blockBuilder: IFullNodeBlockBuilder, + private checkpointsBuilder: ICheckpointsBuilder, penalties: EpochPruneWatcherPenalties, ) { super(); @@ -126,7 +127,7 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter } let previousCheckpointOutHashes: Fr[] = []; - const fork = await this.blockBuilder.getFork(BlockNumber(blocks[0].header.globalVariables.blockNumber - 1)); + const fork = await this.checkpointsBuilder.getFork(BlockNumber(blocks[0].header.globalVariables.blockNumber - 1)); try { for (const block of blocks) { await this.validateBlock(block, previousCheckpointOutHashes, fork); @@ -158,14 +159,27 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter const checkpointNumber = CheckpointNumber.fromBlockNumber(blockFromL1.number); const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(checkpointNumber); - const { block, failedTxs, numTxs } = await this.blockBuilder.buildBlock( - txs, + const gv = blockFromL1.header.globalVariables; + const constants: CheckpointGlobalVariables = { + chainId: gv.chainId, + version: gv.version, + slotNumber: gv.slotNumber, + coinbase: gv.coinbase, + feeRecipient: gv.feeRecipient, + gasFees: gv.gasFees, + }; + + // Use checkpoint builder to validate the block + const checkpointBuilder = await this.checkpointsBuilder.startCheckpoint( + checkpointNumber, + constants, l1ToL2Messages, previousCheckpointOutHashes, - blockFromL1.header.globalVariables, - {}, fork, ); + + const { block, failedTxs, numTxs } = await checkpointBuilder.buildBlock(txs, gv.blockNumber, gv.timestamp, {}); + if (numTxs !== txs.length) { // This should be detected by state mismatch, but this makes it easier to debug. throw new ValidatorError(`Built block with ${numTxs} txs, expected ${txs.length}`); diff --git a/yarn-project/stdlib/src/interfaces/block-builder.ts b/yarn-project/stdlib/src/interfaces/block-builder.ts index 99314cf05fd4..f32475853677 100644 --- a/yarn-project/stdlib/src/interfaces/block-builder.ts +++ b/yarn-project/stdlib/src/interfaces/block-builder.ts @@ -1,17 +1,16 @@ -import type { BlockNumber } from '@aztec/foundation/branded-types'; +import type { BlockNumber, CheckpointNumber } from '@aztec/foundation/branded-types'; import type { Fr } from '@aztec/foundation/curves/bn254'; -import type { Timer } from '@aztec/foundation/timer'; import type { L2BlockNew } from '../block/l2_block_new.js'; import type { ChainConfig, SequencerConfig } from '../config/chain-config.js'; import type { L1RollupConstants } from '../epoch-helpers/index.js'; import type { Gas } from '../gas/gas.js'; -import type { MerkleTreeWriteOperations } from '../trees/index.js'; import type { BlockHeader } from '../tx/block_header.js'; -import type { GlobalVariables } from '../tx/global_variables.js'; +import type { CheckpointGlobalVariables, GlobalVariables } from '../tx/global_variables.js'; import type { FailedTx, ProcessedTx } from '../tx/processed_tx.js'; import { Tx } from '../tx/tx.js'; import type { TxValidator } from '../tx/validator/tx_validator.js'; +import type { MerkleTreeWriteOperations } from './merkle_tree_operations.js'; import type { ProcessedTxHandler } from './processed-tx-handler.js'; /** The interface to a block builder. Generates an L2 block out of a set of processed txs. */ @@ -47,17 +46,6 @@ export interface PublicProcessorValidator { preprocessValidator?: TxValidator; nullifierCache?: { addNullifiers: (nullifiers: Buffer[]) => void }; } -export interface BuildBlockResult { - block: L2BlockNew; - publicGas: Gas; - publicProcessorDuration: number; - numMsgs: number; - numTxs: number; - failedTxs: FailedTx[]; - blockBuildingTimer: Timer; - usedTxs: Tx[]; - usedTxBlobFields: number; -} export type FullNodeBlockBuilderConfig = Pick & Pick & @@ -73,19 +61,36 @@ export const FullNodeBlockBuilderConfigKeys: (keyof FullNodeBlockBuilderConfig)[ 'fakeThrowAfterProcessingTxCount', ] as const; -export interface IFullNodeBlockBuilder { - getConfig(): FullNodeBlockBuilderConfig; - - updateConfig(config: Partial): void; +/** Result of building a block within a checkpoint. */ +export interface BuildBlockInCheckpointResult { + block: L2BlockNew; + publicGas: Gas; + publicProcessorDuration: number; + numTxs: number; + failedTxs: FailedTx[]; + usedTxs: Tx[]; + usedTxBlobFields: number; +} +/** Interface for building blocks within a checkpoint context. */ +export interface ICheckpointBlockBuilder { buildBlock( - txs: Iterable | AsyncIterable, - l1ToL2Messages: Fr[], - previousCheckpointOutHashes: Fr[], - globalVariables: GlobalVariables, - options: PublicProcessorLimits, - fork?: MerkleTreeWriteOperations, - ): Promise; + pendingTxs: Iterable | AsyncIterable, + blockNumber: BlockNumber, + timestamp: bigint, + opts: PublicProcessorLimits, + ): Promise; +} +/** Interface for creating checkpoint builders. */ +export interface ICheckpointsBuilder { getFork(blockNumber: BlockNumber): Promise; + + startCheckpoint( + checkpointNumber: CheckpointNumber, + constants: CheckpointGlobalVariables, + l1ToL2Messages: Fr[], + previousCheckpointOutHashes: Fr[], + fork: MerkleTreeWriteOperations, + ): Promise; } diff --git a/yarn-project/validator-client/README.md b/yarn-project/validator-client/README.md index d33273f7f5c5..4943f1642732 100644 --- a/yarn-project/validator-client/README.md +++ b/yarn-project/validator-client/README.md @@ -230,7 +230,7 @@ Tests typically mock these dependencies: let epochCache: MockProxy; let blockSource: MockProxy; let txProvider: MockProxy; -let blockBuilder: MockProxy; +let checkpointsBuilder: MockProxy; let p2pClient: MockProxy; beforeEach(() => { diff --git a/yarn-project/validator-client/src/checkpoint_builder.ts b/yarn-project/validator-client/src/checkpoint_builder.ts index 549ff480c64e..8238adeaf0ce 100644 --- a/yarn-project/validator-client/src/checkpoint_builder.ts +++ b/yarn-project/validator-client/src/checkpoint_builder.ts @@ -15,37 +15,39 @@ import { import { L2BlockNew } from '@aztec/stdlib/block'; import { Checkpoint } from '@aztec/stdlib/checkpoint'; import type { ContractDataSource } from '@aztec/stdlib/contract'; +import type { L1RollupConstants } from '@aztec/stdlib/epoch-helpers'; import { Gas } from '@aztec/stdlib/gas'; import { + type BuildBlockInCheckpointResult, type FullNodeBlockBuilderConfig, FullNodeBlockBuilderConfigKeys, + type ICheckpointBlockBuilder, + type ICheckpointsBuilder, type MerkleTreeWriteOperations, type PublicProcessorLimits, + type WorldStateSynchronizer, } from '@aztec/stdlib/interfaces/server'; import { MerkleTreeId } from '@aztec/stdlib/trees'; -import { type CheckpointGlobalVariables, type FailedTx, GlobalVariables, StateReference, Tx } from '@aztec/stdlib/tx'; +import { type CheckpointGlobalVariables, GlobalVariables, StateReference, Tx } from '@aztec/stdlib/tx'; import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client'; import { createValidatorForBlockBuilding } from './tx_validator/tx_validator_factory.js'; +// Re-export for backward compatibility +export type { BuildBlockInCheckpointResult } from '@aztec/stdlib/interfaces/server'; + const log = createLogger('checkpoint-builder'); -export interface BuildBlockInCheckpointResult { - block: L2BlockNew; - publicGas: Gas; - publicProcessorDuration: number; - numTxs: number; - failedTxs: FailedTx[]; +/** Result of building a block within a checkpoint. Extends the base interface with timer. */ +export interface BuildBlockInCheckpointResultWithTimer extends BuildBlockInCheckpointResult { blockBuildingTimer: Timer; - usedTxs: Tx[]; - usedTxBlobFields: number; } /** * Builder for a single checkpoint. Handles building blocks within the checkpoint * and completing it. */ -export class CheckpointBuilder { +export class CheckpointBuilder implements ICheckpointBlockBuilder { constructor( private checkpointBuilder: LightweightCheckpointBuilder, private fork: MerkleTreeWriteOperations, @@ -67,7 +69,7 @@ export class CheckpointBuilder { blockNumber: BlockNumber, timestamp: bigint, opts: PublicProcessorLimits & { expectedEndState?: StateReference }, - ): Promise { + ): Promise { const blockBuildingTimer = new Timer(); const slot = this.checkpointBuilder.constants.slotNumber; @@ -172,12 +174,11 @@ export class CheckpointBuilder { } } -/** - * Factory for creating checkpoint builders. - */ -export class FullNodeCheckpointsBuilder { +/** Factory for creating checkpoint builders. */ +export class FullNodeCheckpointsBuilder implements ICheckpointsBuilder { constructor( - private config: FullNodeBlockBuilderConfig, + private config: FullNodeBlockBuilderConfig & Pick, + private worldState: WorldStateSynchronizer, private contractDataSource: ContractDataSource, private dateProvider: DateProvider, private telemetryClient: TelemetryClient = getTelemetryClient(), @@ -275,4 +276,9 @@ export class FullNodeCheckpointsBuilder { this.telemetryClient, ); } + + /** Returns a fork of the world state at the given block number. */ + getFork(blockNumber: BlockNumber): Promise { + return this.worldState.fork(blockNumber); + } } diff --git a/yarn-project/validator-client/src/validator.test.ts b/yarn-project/validator-client/src/validator.test.ts index 280e477d9d40..5c6bc15a23c5 100644 --- a/yarn-project/validator-client/src/validator.test.ts +++ b/yarn-project/validator-client/src/validator.test.ts @@ -44,7 +44,7 @@ import { type MockProxy, mock } from 'jest-mock-extended'; import { type PrivateKeyAccount, generatePrivateKey, privateKeyToAccount } from 'viem/accounts'; import type { - BuildBlockInCheckpointResult, + BuildBlockInCheckpointResultWithTimer, CheckpointBuilder, FullNodeCheckpointsBuilder, } from './checkpoint_builder.js'; @@ -277,7 +277,7 @@ describe('ValidatorClient', () => { let proposal: BlockProposal; let blockNumber: BlockNumber; let sender: PeerId; - let blockBuildResult: BuildBlockInCheckpointResult; + let blockBuildResult: BuildBlockInCheckpointResultWithTimer; let mockCheckpointBuilder: MockProxy; const makeTxFromHash = (txHash: TxHash) => ({ getTxHash: () => txHash, txHash }) as Tx;