diff --git a/packages/beacon-node/src/api/impl/validator/index.ts b/packages/beacon-node/src/api/impl/validator/index.ts index c0230a21bcc6..d67c3b1f7a5e 100644 --- a/packages/beacon-node/src/api/impl/validator/index.ts +++ b/packages/beacon-node/src/api/impl/validator/index.ts @@ -72,7 +72,7 @@ const SYNC_TOLERANCE_EPOCHS = 1; * Cutoff time to wait for execution and builder block production apis to resolve * Post this time, race execution and builder to pick whatever resolves first * - * Emprically the builder block resolves in ~1.5+ seconds, and executon should resolve <1 sec. + * Empirically the builder block resolves in ~1.5+ seconds, and execution should resolve <1 sec. * So lowering the cutoff to 2 sec from 3 seconds to publish faster for successful proposal * as proposals post 4 seconds into the slot seems to be not being included */ @@ -437,7 +437,7 @@ export function getValidatorApi({ chain.executionBuilder !== undefined && builderSelection !== routes.validator.BuilderSelection.ExecutionOnly; - logger.verbose("produceBlockV3 assembling block", { + logger.verbose("Assembling block with produceBlockV3 ", { fork, builderSelection, slot, diff --git a/packages/beacon-node/test/fixtures/capella.ts b/packages/beacon-node/test/fixtures/capella.ts new file mode 100644 index 000000000000..fe9b0206efb1 --- /dev/null +++ b/packages/beacon-node/test/fixtures/capella.ts @@ -0,0 +1,24 @@ +import {CachedBeaconStateAltair} from "@lodestar/state-transition"; +import {capella} from "@lodestar/types"; + +export function generateBlsToExecutionChanges( + state: CachedBeaconStateAltair, + count: number +): capella.SignedBLSToExecutionChange[] { + const result: capella.SignedBLSToExecutionChange[] = []; + + for (const validatorIndex of state.epochCtx.proposers) { + result.push({ + message: { + fromBlsPubkey: state.epochCtx.index2pubkey[validatorIndex].toBytes(), + toExecutionAddress: Buffer.alloc(20), + validatorIndex, + }, + signature: Buffer.alloc(96), + }); + + if (result.length >= count) return result; + } + + return result; +} diff --git a/packages/beacon-node/test/fixtures/phase0.ts b/packages/beacon-node/test/fixtures/phase0.ts new file mode 100644 index 000000000000..a273f55e967d --- /dev/null +++ b/packages/beacon-node/test/fixtures/phase0.ts @@ -0,0 +1,98 @@ +import {SLOTS_PER_EPOCH} from "@lodestar/params"; +import { + CachedBeaconStateAltair, + computeEpochAtSlot, + computeStartSlotAtEpoch, + getBlockRootAtSlot, +} from "@lodestar/state-transition"; +import {phase0} from "@lodestar/types"; + +export function generateIndexedAttestations( + state: CachedBeaconStateAltair, + count: number +): phase0.IndexedAttestation[] { + const result: phase0.IndexedAttestation[] = []; + + for (let epochSlot = 0; epochSlot < SLOTS_PER_EPOCH; epochSlot++) { + const slot = state.slot - 1 - epochSlot; + const epoch = computeEpochAtSlot(slot); + const committeeCount = state.epochCtx.getCommitteeCountPerSlot(epoch); + + for (let committeeIndex = 0; committeeIndex < committeeCount; committeeIndex++) { + result.push({ + attestingIndices: state.epochCtx.getBeaconCommittee(slot, committeeIndex), + data: { + slot: slot, + index: committeeIndex, + beaconBlockRoot: getBlockRootAtSlot(state, slot), + source: { + epoch: state.currentJustifiedCheckpoint.epoch, + root: state.currentJustifiedCheckpoint.root, + }, + target: { + epoch: epoch, + root: getBlockRootAtSlot(state, computeStartSlotAtEpoch(epoch)), + }, + }, + signature: Buffer.alloc(96), + }); + + if (result.length >= count) return result; + } + } + + return result; +} + +export function generateBeaconBlockHeader(state: CachedBeaconStateAltair, count: number): phase0.BeaconBlockHeader[] { + const headers: phase0.BeaconBlockHeader[] = []; + + for (let i = 1; i <= count; i++) { + const slot = state.slot - i; + const epoch = computeEpochAtSlot(slot); + const epochStartSlot = computeStartSlotAtEpoch(epoch); + const parentRoot = getBlockRootAtSlot(state, slot - 1); + const stateRoot = getBlockRootAtSlot(state, epochStartSlot); + const bodyRoot = getBlockRootAtSlot(state, epochStartSlot + 1); + const header: phase0.BeaconBlockHeader = { + slot, + proposerIndex: state.epochCtx.proposers[slot % SLOTS_PER_EPOCH], + parentRoot, + stateRoot, + bodyRoot, + }; + + headers.push(header); + } + return headers; +} + +export function generateSignedBeaconBlockHeader( + state: CachedBeaconStateAltair, + count: number +): phase0.SignedBeaconBlockHeader[] { + const headers = generateBeaconBlockHeader(state, count); + + return headers.map((header) => ({ + message: header, + signature: Buffer.alloc(96), + })); +} + +export function generateVoluntaryExits(state: CachedBeaconStateAltair, count: number): phase0.SignedVoluntaryExit[] { + const result: phase0.SignedVoluntaryExit[] = []; + + for (const validatorIndex of state.epochCtx.proposers) { + result.push({ + message: { + epoch: state.currentJustifiedCheckpoint.epoch, + validatorIndex, + }, + signature: Buffer.alloc(96), + }); + + if (result.length >= count) return result; + } + + return result; +} diff --git a/packages/beacon-node/test/perf/chain/opPools/opPool.test.ts b/packages/beacon-node/test/perf/chain/opPools/opPool.test.ts new file mode 100644 index 000000000000..95f8ea6ba5a3 --- /dev/null +++ b/packages/beacon-node/test/perf/chain/opPools/opPool.test.ts @@ -0,0 +1,109 @@ +import {itBench} from "@dapplion/benchmark"; +import { + MAX_ATTESTER_SLASHINGS, + MAX_BLS_TO_EXECUTION_CHANGES, + MAX_PROPOSER_SLASHINGS, + MAX_VOLUNTARY_EXITS, +} from "@lodestar/params"; +import {CachedBeaconStateAltair} from "@lodestar/state-transition"; +import {ssz} from "@lodestar/types"; +// eslint-disable-next-line import/no-relative-packages +import {generatePerfTestCachedStateAltair} from "../../../../../state-transition/test/perf/util.js"; +import {OpPool} from "../../../../src/chain/opPools/opPool.js"; +import {generateBlsToExecutionChanges} from "../../../fixtures/capella.js"; +import { + generateIndexedAttestations, + generateSignedBeaconBlockHeader, + generateVoluntaryExits, +} from "../../../fixtures/phase0.js"; + +// Aug 11 2021 +// getAttestationsForBlock +// ✓ getAttestationsForBlock 4.410948 ops/s 226.7086 ms/op - 64 runs 51.8 s +describe("opPool", () => { + let originalState: CachedBeaconStateAltair; + + before(function () { + this.timeout(2 * 60 * 1000); // Generating the states for the first time is very slow + + originalState = generatePerfTestCachedStateAltair({goBackOneSlot: true}); + }); + + itBench({ + id: "getSlashingsAndExits - default max", + beforeEach: () => { + const pool = new OpPool(); + fillAttesterSlashing(pool, originalState, MAX_ATTESTER_SLASHINGS); + fillProposerSlashing(pool, originalState, MAX_PROPOSER_SLASHINGS); + fillVoluntaryExits(pool, originalState, MAX_VOLUNTARY_EXITS); + fillBlsToExecutionChanges(pool, originalState, MAX_BLS_TO_EXECUTION_CHANGES); + + return pool; + }, + fn: (pool) => { + pool.getSlashingsAndExits(originalState); + }, + }); + + itBench({ + id: "getSlashingsAndExits - 2k", + beforeEach: () => { + const pool = new OpPool(); + const maxItemsInPool = 2_000; + + fillAttesterSlashing(pool, originalState, maxItemsInPool); + fillProposerSlashing(pool, originalState, maxItemsInPool); + fillVoluntaryExits(pool, originalState, maxItemsInPool); + fillBlsToExecutionChanges(pool, originalState, maxItemsInPool); + + return pool; + }, + fn: (pool) => { + pool.getSlashingsAndExits(originalState); + }, + }); +}); + +function fillAttesterSlashing(pool: OpPool, state: CachedBeaconStateAltair, count: number): OpPool { + for (const attestation of generateIndexedAttestations(state, count)) { + pool.insertAttesterSlashing({ + attestation1: ssz.phase0.IndexedAttestationBigint.fromJson(ssz.phase0.IndexedAttestation.toJson(attestation)), + attestation2: ssz.phase0.IndexedAttestationBigint.fromJson(ssz.phase0.IndexedAttestation.toJson(attestation)), + }); + } + + return pool; +} + +function fillProposerSlashing(pool: OpPool, state: CachedBeaconStateAltair, count: number): OpPool { + for (const blockHeader of generateSignedBeaconBlockHeader(state, count)) { + pool.insertProposerSlashing({ + signedHeader1: ssz.phase0.SignedBeaconBlockHeaderBigint.fromJson( + ssz.phase0.SignedBeaconBlockHeader.toJson(blockHeader) + ), + signedHeader2: ssz.phase0.SignedBeaconBlockHeaderBigint.fromJson( + ssz.phase0.SignedBeaconBlockHeader.toJson(blockHeader) + ), + }); + } + + return pool; +} + +function fillVoluntaryExits(pool: OpPool, state: CachedBeaconStateAltair, count: number): OpPool { + for (const exit of generateVoluntaryExits(state, count)) { + pool.insertVoluntaryExit(exit); + } + + return pool; +} + +// This does not set the `withdrawalCredentials` for the validator +// So it will be in the pool but not returned from `getSlashingsAndExits` +function fillBlsToExecutionChanges(pool: OpPool, state: CachedBeaconStateAltair, count: number): OpPool { + for (const blsToExecution of generateBlsToExecutionChanges(state, count)) { + pool.insertBlsToExecutionChange(blsToExecution); + } + + return pool; +}