From 831caa14dc18147e70942e3bd89f491b646bf4de Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Tue, 5 Dec 2023 17:21:59 +0100 Subject: [PATCH] test: add performance benchmarks for the phase0 block attributes (#6145) * Add opPool benchmark * Add benchmark for empty block body * Optimize a skipping condition * Fix the identifier for a graph * Add detailed steps progress for block production * Update the usage of new metric * Add block produciton test code * Add dashboard panels for the block processing * Revert test run code * Fix a type in perf test * Fix dashboard * Fix the code feedback * Convert multi-label metric to single label * Remove the buckets * Add special bucket values --- dashboards/lodestar_block_production.json | 354 +++++++++++++++++- .../src/api/impl/validator/index.ts | 4 +- .../beacon-node/src/chain/opPools/opPool.ts | 31 +- .../chain/produceBlock/produceBlockBody.ts | 25 +- .../beacon-node/src/metrics/metrics/beacon.ts | 32 ++ packages/beacon-node/test/fixtures/capella.ts | 24 ++ packages/beacon-node/test/fixtures/phase0.ts | 98 +++++ .../test/perf/chain/opPools/opPool.test.ts | 107 ++++++ .../produceBlock/produceBlockBody.test.ts | 87 +++++ 9 files changed, 755 insertions(+), 7 deletions(-) create mode 100644 packages/beacon-node/test/fixtures/capella.ts create mode 100644 packages/beacon-node/test/fixtures/phase0.ts create mode 100644 packages/beacon-node/test/perf/chain/opPools/opPool.test.ts create mode 100644 packages/beacon-node/test/perf/chain/produceBlock/produceBlockBody.test.ts diff --git a/dashboards/lodestar_block_production.json b/dashboards/lodestar_block_production.json index 7bb0b4a2db7e..b999e47a33d4 100644 --- a/dashboards/lodestar_block_production.json +++ b/dashboards/lodestar_block_production.json @@ -53,6 +53,358 @@ ], "liveNow": false, "panels": [ + { + "type": "timeseries", + "title": "Full block production avg time with steps", + "gridPos": { + "x": 0, + "y": 1, + "w": 12, + "h": 8 + }, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "id": 546, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "refId": "proposerSlashing", + "expr": "rate(beacon_block_production_execution_steps_seconds{step=\"proposerSlashing\"}[$rate_interval])\n/\nrate(beacon_block_production_execution_steps_seconds{step=\"proposerSlashing\"}[$rate_interval])", + "range": true, + "instant": false, + "hide": false, + "editorMode": "code", + "legendFormat": "{{step}}", + "exemplar": false + }, + { + "refId": "attesterSlashings", + "expr": "rate(beacon_block_production_execution_steps_seconds{step=\"attesterSlashings\"}[$rate_interval])\n/\nrate(beacon_block_production_execution_steps_seconds{step=\"attesterSlashings\"}[$rate_interval])", + "range": true, + "instant": false, + "datasource": { + "uid": "${DS_PROMETHEUS}", + "type": "prometheus" + }, + "hide": false, + "editorMode": "code", + "legendFormat": "{{step}}" + }, + { + "refId": "voluntaryExits", + "expr": "rate(beacon_block_production_execution_steps_seconds{step=\"voluntaryExits\"}[$rate_interval])\n/\nrate(beacon_block_production_execution_steps_seconds{step=\"voluntaryExits\"}[$rate_interval])", + "range": true, + "instant": false, + "datasource": { + "uid": "${DS_PROMETHEUS}", + "type": "prometheus" + }, + "hide": false, + "editorMode": "code", + "legendFormat": "{{step}}" + }, + { + "refId": "blsToExecutionChanges", + "expr": "rate(beacon_block_production_execution_steps_seconds{step=\"blsToExecutionChanges\"}[$rate_interval])\n/\nrate(beacon_block_production_execution_steps_seconds{step=\"blsToExecutionChanges\"}[$rate_interval])", + "range": true, + "instant": false, + "datasource": { + "uid": "${DS_PROMETHEUS}", + "type": "prometheus" + }, + "hide": false, + "editorMode": "code", + "legendFormat": "{{step}}" + }, + { + "refId": "attestations", + "expr": "rate(beacon_block_production_execution_steps_seconds{step=\"attestations\"}[$rate_interval])\n/\nrate(beacon_block_production_execution_steps_seconds{step=\"attestations\"}[$rate_interval])", + "range": true, + "instant": false, + "datasource": { + "uid": "${DS_PROMETHEUS}", + "type": "prometheus" + }, + "hide": false, + "editorMode": "code", + "legendFormat": "{{step}}" + }, + { + "refId": "eth1DataAndDeposits", + "expr": "rate(beacon_block_production_execution_steps_seconds{step=\"eth1DataAndDeposits\"}[$rate_interval])\n/\nrate(beacon_block_production_execution_steps_seconds{step=\"eth1DataAndDeposits\"}[$rate_interval])", + "range": true, + "instant": false, + "datasource": { + "uid": "${DS_PROMETHEUS}", + "type": "prometheus" + }, + "hide": false, + "editorMode": "code", + "legendFormat": "{{step}}" + }, + { + "refId": "syncAggregate", + "expr": "rate(beacon_block_production_execution_steps_seconds{step=\"syncAggregate\"}[$rate_interval])\n/\nrate(beacon_block_production_execution_steps_seconds{step=\"syncAggregate\"}[$rate_interval])", + "range": true, + "instant": false, + "datasource": { + "uid": "${DS_PROMETHEUS}", + "type": "prometheus" + }, + "hide": false, + "editorMode": "code", + "legendFormat": "{{step}}" + }, + { + "refId": "executionPayload", + "expr": "rate(beacon_block_production_execution_steps_seconds{step=\"executionPayload\"}[$rate_interval])\n/\nrate(beacon_block_production_execution_steps_seconds{step=\"executionPayload\"}[$rate_interval])", + "range": true, + "instant": false, + "datasource": { + "uid": "${DS_PROMETHEUS}", + "type": "prometheus" + }, + "hide": false, + "editorMode": "code", + "legendFormat": "{{step}}" + } + ], + "options": { + "tooltip": { + "mode": "multi", + "sort": "none" + }, + "legend": { + "showLegend": true, + "displayMode": "list", + "placement": "bottom", + "calcs": [] + } + }, + "fieldConfig": { + "defaults": { + "custom": { + "drawStyle": "line", + "lineInterpolation": "linear", + "barAlignment": 0, + "lineWidth": 1, + "fillOpacity": 30, + "gradientMode": "opacity", + "spanNulls": false, + "insertNulls": false, + "showPoints": "auto", + "pointSize": 5, + "stacking": { + "mode": "normal", + "group": "A" + }, + "axisPlacement": "auto", + "axisLabel": "", + "axisColorMode": "text", + "scaleDistribution": { + "type": "linear" + }, + "axisCenteredZero": false, + "hideFrom": { + "tooltip": false, + "viz": false, + "legend": false + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "color": { + "mode": "palette-classic" + }, + "mappings": [], + "unit": "s" + }, + "overrides": [] + }, + "transformations": [] + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "custom": { + "drawStyle": "line", + "lineInterpolation": "linear", + "barAlignment": 0, + "lineWidth": 1, + "fillOpacity": 30, + "gradientMode": "opacity", + "spanNulls": false, + "insertNulls": false, + "showPoints": "auto", + "pointSize": 5, + "stacking": { + "mode": "normal", + "group": "A" + }, + "axisPlacement": "auto", + "axisLabel": "", + "axisColorMode": "text", + "scaleDistribution": { + "type": "linear" + }, + "axisCenteredZero": false, + "hideFrom": { + "tooltip": false, + "viz": false, + "legend": false + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "color": { + "mode": "palette-classic" + }, + "mappings": [], + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "x": 12, + "y": 1, + "w": 12, + "h": 8 + }, + "id": 547, + "options": { + "tooltip": { + "mode": "multi", + "sort": "none" + }, + "legend": { + "showLegend": true, + "displayMode": "list", + "placement": "bottom", + "calcs": [] + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "refId": "proposerSlashing", + "expr": "rate(beacon_block_production_builder_steps_seconds{step=\"proposerSlashing\"}[$rate_interval])\n/\nrate(beacon_block_production_builder_steps_seconds{step=\"proposerSlashing\"}[$rate_interval])", + "range": true, + "instant": false, + "hide": false, + "editorMode": "code", + "legendFormat": "{{step}}", + "exemplar": false + }, + { + "refId": "attesterSlashings", + "expr": "rate(beacon_block_production_builder_steps_seconds{step=\"attesterSlashings\"}[$rate_interval])\n/\nrate(beacon_block_production_builder_steps_seconds{step=\"attesterSlashings\"}[$rate_interval])", + "range": true, + "instant": false, + "datasource": { + "uid": "${DS_PROMETHEUS}", + "type": "prometheus" + }, + "hide": false, + "editorMode": "code", + "legendFormat": "{{step}}" + }, + { + "refId": "voluntaryExits", + "expr": "rate(beacon_block_production_builder_steps_seconds{step=\"voluntaryExits\"}[$rate_interval])\n/\nrate(beacon_block_production_builder_steps_seconds{step=\"voluntaryExits\"}[$rate_interval])", + "range": true, + "instant": false, + "datasource": { + "uid": "${DS_PROMETHEUS}", + "type": "prometheus" + }, + "hide": false, + "editorMode": "code", + "legendFormat": "{{step}}" + }, + { + "refId": "blsToExecutionChanges", + "expr": "rate(beacon_block_production_builder_steps_seconds{step=\"blsToExecutionChanges\"}[$rate_interval])\n/\nrate(beacon_block_production_builder_steps_seconds{step=\"blsToExecutionChanges\"}[$rate_interval])", + "range": true, + "instant": false, + "datasource": { + "uid": "${DS_PROMETHEUS}", + "type": "prometheus" + }, + "hide": false, + "editorMode": "code", + "legendFormat": "{{step}}" + }, + { + "refId": "attestations", + "expr": "rate(beacon_block_production_builder_steps_seconds{step=\"attestations\"}[$rate_interval])\n/\nrate(beacon_block_production_builder_steps_seconds{step=\"attestations\"}[$rate_interval])", + "range": true, + "instant": false, + "datasource": { + "uid": "${DS_PROMETHEUS}", + "type": "prometheus" + }, + "hide": false, + "editorMode": "code", + "legendFormat": "{{step}}" + }, + { + "refId": "eth1DataAndDeposits", + "expr": "rate(beacon_block_production_builder_steps_seconds{step=\"eth1DataAndDeposits\"}[$rate_interval])\n/\nrate(beacon_block_production_builder_steps_seconds{step=\"eth1DataAndDeposits\"}[$rate_interval])", + "range": true, + "instant": false, + "datasource": { + "uid": "${DS_PROMETHEUS}", + "type": "prometheus" + }, + "hide": false, + "editorMode": "code", + "legendFormat": "{{step}}" + }, + { + "refId": "syncAggregate", + "expr": "rate(beacon_block_production_builder_steps_seconds{step=\"syncAggregate\"}[$rate_interval])\n/\nrate(beacon_block_production_builder_steps_seconds{step=\"syncAggregate\"}[$rate_interval])", + "range": true, + "instant": false, + "datasource": { + "uid": "${DS_PROMETHEUS}", + "type": "prometheus" + }, + "hide": false, + "editorMode": "code", + "legendFormat": "{{step}}" + }, + { + "refId": "executionPayload", + "expr": "rate(beacon_block_production_builder_steps_seconds{step=\"executionPayload\"}[$rate_interval])\n/\nrate(beacon_block_production_builder_steps_seconds{step=\"executionPayload\"}[$rate_interval])", + "range": true, + "instant": false, + "datasource": { + "uid": "${DS_PROMETHEUS}", + "type": "prometheus" + }, + "hide": false, + "editorMode": "code", + "legendFormat": "{{step}}" + } + ], + "title": "Blinded block production avg time with steps", + "type": "timeseries", + "transformations": [] + }, { "collapsed": false, "datasource": { @@ -309,7 +661,7 @@ "expr": "rate(beacon_block_production_seconds_sum[$rate_interval])\n/\nrate(beacon_block_production_seconds_count[$rate_interval])", "format": "heatmap", "interval": "", - "legendFormat": "time", + "legendFormat": "{{instance}} - {{source}}", "refId": "A" } ], diff --git a/packages/beacon-node/src/api/impl/validator/index.ts b/packages/beacon-node/src/api/impl/validator/index.ts index 2269c17c9841..ef0114e577f0 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/src/chain/opPools/opPool.ts b/packages/beacon-node/src/chain/opPools/opPool.ts index c058404834f0..cee8d0614c30 100644 --- a/packages/beacon-node/src/chain/opPools/opPool.ts +++ b/packages/beacon-node/src/chain/opPools/opPool.ts @@ -17,6 +17,8 @@ import { import {Epoch, phase0, capella, ssz, ValidatorIndex} from "@lodestar/types"; import {IBeaconDb} from "../../db/index.js"; import {SignedBLSToExecutionChangeVersioned} from "../../util/types.js"; +import {BlockType} from "../interface.js"; +import {Metrics} from "../../metrics/metrics.js"; import {isValidBlsToExecutionChangeForBlockInclusion} from "./utils.js"; type HexRoot = string; @@ -165,7 +167,9 @@ export class OpPool { * slashings included earlier in the block. */ getSlashingsAndExits( - state: CachedBeaconStateAllForks + state: CachedBeaconStateAllForks, + blockType: BlockType, + metrics: Metrics | null ): [ phase0.AttesterSlashing[], phase0.ProposerSlashing[], @@ -178,6 +182,12 @@ export class OpPool { const toBeSlashedIndices = new Set(); const proposerSlashings: phase0.ProposerSlashing[] = []; + const stepsMetrics = + blockType === BlockType.Full + ? metrics?.executionBlockProductionTimeSteps + : metrics?.builderBlockProductionTimeSteps; + + const endProposerSlashing = stepsMetrics?.startTimer(); for (const proposerSlashing of this.proposerSlashings.values()) { const index = proposerSlashing.signedHeader1.message.proposerIndex; const validator = state.validators.getReadonly(index); @@ -190,19 +200,23 @@ export class OpPool { } } } + endProposerSlashing?.({ + step: "proposerSlashing", + }); + const endAttesterSlashings = stepsMetrics?.startTimer(); const attesterSlashings: phase0.AttesterSlashing[] = []; attesterSlashing: for (const attesterSlashing of this.attesterSlashings.values()) { /** Indices slashable in this attester slashing */ const slashableIndices = new Set(); for (let i = 0; i < attesterSlashing.intersectingIndices.length; i++) { const index = attesterSlashing.intersectingIndices[i]; - const validator = state.validators.getReadonly(index); - // If we already have a slashing for this index, we can continue on to the next slashing if (toBeSlashedIndices.has(index)) { continue attesterSlashing; } + + const validator = state.validators.getReadonly(index); if (isSlashableAtEpoch(validator, stateEpoch)) { slashableIndices.add(index); } @@ -220,7 +234,11 @@ export class OpPool { } } } + endAttesterSlashings?.({ + step: "attesterSlashings", + }); + const endVoluntaryExits = stepsMetrics?.startTimer(); const voluntaryExits: phase0.SignedVoluntaryExit[] = []; for (const voluntaryExit of this.voluntaryExits.values()) { if ( @@ -237,7 +255,11 @@ export class OpPool { } } } + endVoluntaryExits?.({ + step: "voluntaryExits", + }); + const endBlsToExecutionChanges = stepsMetrics?.startTimer(); const blsToExecutionChanges: capella.SignedBLSToExecutionChange[] = []; for (const blsToExecutionChange of this.blsToExecutionChanges.values()) { if (isValidBlsToExecutionChangeForBlockInclusion(state, blsToExecutionChange.data)) { @@ -247,6 +269,9 @@ export class OpPool { } } } + endBlsToExecutionChanges?.({ + step: "blsToExecutionChanges", + }); return [attesterSlashings, proposerSlashings, voluntaryExits, blsToExecutionChanges]; } diff --git a/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts b/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts index acefbbf765a1..1c522c54a93d 100644 --- a/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts +++ b/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts @@ -123,10 +123,25 @@ export async function produceBlockBody( // } // } + const stepsMetrics = + blockType === BlockType.Full + ? this.metrics?.executionBlockProductionTimeSteps + : this.metrics?.builderBlockProductionTimeSteps; + const [attesterSlashings, proposerSlashings, voluntaryExits, blsToExecutionChanges] = - this.opPool.getSlashingsAndExits(currentState); + this.opPool.getSlashingsAndExits(currentState, blockType, this.metrics); + + const endAttestations = stepsMetrics?.startTimer(); const attestations = this.aggregatedAttestationPool.getAttestationsForBlock(this.forkChoice, currentState); + endAttestations?.({ + step: "attestations", + }); + + const endEth1DataAndDeposits = stepsMetrics?.startTimer(); const {eth1Data, deposits} = await this.eth1.getEth1DataAndDeposits(currentState); + endEth1DataAndDeposits?.({ + step: "eth1DataAndDeposits", + }); const blockBody: phase0.BeaconBlockBody = { randaoReveal, @@ -141,6 +156,7 @@ export async function produceBlockBody( const blockEpoch = computeEpochAtSlot(blockSlot); + const endSyncAggregate = stepsMetrics?.startTimer(); if (blockEpoch >= this.config.ALTAIR_FORK_EPOCH) { const syncAggregate = this.syncContributionAndProofPool.getAggregate(parentSlot, parentBlockRoot); this.metrics?.production.producedSyncAggregateParticipants.observe( @@ -148,6 +164,9 @@ export async function produceBlockBody( ); (blockBody as altair.BeaconBlockBody).syncAggregate = syncAggregate; } + endSyncAggregate?.({ + step: "syncAggregate", + }); Object.assign(logMeta, { attestations: attestations.length, @@ -157,6 +176,7 @@ export async function produceBlockBody( proposerSlashings: proposerSlashings.length, }); + const endExecutionPayload = stepsMetrics?.startTimer(); if (isForkExecution(fork)) { const safeBlockHash = this.forkChoice.getJustifiedBlock().executionPayloadBlockHash ?? ZERO_HASH_HEX; const finalizedBlockHash = this.forkChoice.getFinalizedBlock().executionPayloadBlockHash ?? ZERO_HASH_HEX; @@ -359,6 +379,9 @@ export async function produceBlockBody( blobsResult = {type: BlobsResultType.preDeneb}; executionPayloadValue = BigInt(0); } + endExecutionPayload?.({ + step: "executionPayload", + }); if (ForkSeq[fork] >= ForkSeq.capella) { // TODO: blsToExecutionChanges should be passed in the produceBlock call diff --git a/packages/beacon-node/src/metrics/metrics/beacon.ts b/packages/beacon-node/src/metrics/metrics/beacon.ts index 2b763599f6e1..8d9094f19a25 100644 --- a/packages/beacon-node/src/metrics/metrics/beacon.ts +++ b/packages/beacon-node/src/metrics/metrics/beacon.ts @@ -121,6 +121,38 @@ export function createBeaconMetrics(register: RegistryMetricCreator) { buckets: [0.1, 1, 2, 4, 10], labelNames: ["source"], }), + executionBlockProductionTimeSteps: register.histogram<"step">({ + name: "beacon_block_production_execution_steps_seconds", + help: "Detailed steps runtime of execution block production", + buckets: [0.01, 0.1, 0.2, 0.5, 1], + /** + * - proposerSlashing + * - attesterSlashings + * - voluntaryExits + * - blsToExecutionChanges + * - attestations + * - eth1DataAndDeposits + * - syncAggregate + * - executionPayload + */ + labelNames: ["step"], + }), + builderBlockProductionTimeSteps: register.histogram<"step">({ + name: "beacon_block_production_builder_steps_seconds", + help: "Detailed steps runtime of builder block production", + buckets: [0.01, 0.1, 0.2, 0.5, 1], + /** + * - proposerSlashing + * - attesterSlashings + * - voluntaryExits + * - blsToExecutionChanges + * - attestations + * - eth1DataAndDeposits + * - syncAggregate + * - executionPayload + */ + labelNames: ["step"], + }), blockProductionRequests: register.gauge<"source">({ name: "beacon_block_production_requests_total", help: "Count of all block production requests", 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..6e420f0e1011 --- /dev/null +++ b/packages/beacon-node/test/perf/chain/opPools/opPool.test.ts @@ -0,0 +1,107 @@ +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"; +import {BlockType} from "../../../../src/chain/interface.js"; + +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, BlockType.Full, null); + }, + }); + + 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, BlockType.Full, null); + }, + }); +}); + +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; +} diff --git a/packages/beacon-node/test/perf/chain/produceBlock/produceBlockBody.test.ts b/packages/beacon-node/test/perf/chain/produceBlock/produceBlockBody.test.ts new file mode 100644 index 000000000000..1892281cfe7c --- /dev/null +++ b/packages/beacon-node/test/perf/chain/produceBlock/produceBlockBody.test.ts @@ -0,0 +1,87 @@ +import {fromHexString} from "@chainsafe/ssz"; +import {itBench} from "@dapplion/benchmark"; +import {config} from "@lodestar/config/default"; +import {LevelDbController} from "@lodestar/db"; +import {SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY} from "@lodestar/params"; +import {defaultOptions as defaultValidatorOptions} from "@lodestar/validator"; +import {CachedBeaconStateAltair} from "@lodestar/state-transition"; +// eslint-disable-next-line import/no-relative-packages +import {generatePerfTestCachedStateAltair} from "../../../../../state-transition/test/perf/util.js"; +import {BeaconChain} from "../../../../src/chain/index.js"; +import {BlockType, produceBlockBody} from "../../../../src/chain/produceBlock/produceBlockBody.js"; +import {Eth1ForBlockProductionDisabled} from "../../../../src/eth1/index.js"; +import {ExecutionEngineDisabled} from "../../../../src/execution/engine/index.js"; +import {BeaconDb} from "../../../../src/index.js"; +import {testLogger} from "../../../utils/logger.js"; + +const logger = testLogger(); +const numberOfValidators = 1024; + +describe("produceBlockBody", () => { + const stateOg = generatePerfTestCachedStateAltair({goBackOneSlot: false, vc: numberOfValidators}); + + let db: BeaconDb; + let chain: BeaconChain; + let state: CachedBeaconStateAltair; + + before(async () => { + db = new BeaconDb(config, await LevelDbController.create({name: ".tmpdb"}, {logger})); + state = stateOg.clone(); + chain = new BeaconChain( + { + proposerBoostEnabled: true, + computeUnrealized: false, + safeSlotsToImportOptimistically: SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY, + disableArchiveOnCheckpoint: true, + suggestedFeeRecipient: defaultValidatorOptions.suggestedFeeRecipient, + skipCreateStateCacheIfAvailable: true, + archiveStateEpochFrequency: 1024, + minSameMessageSignatureSetsToBatch: 32, + }, + { + config: state.config, + db, + logger, + // eslint-disable-next-line @typescript-eslint/no-empty-function + processShutdownCallback: () => {}, + metrics: null, + anchorState: state, + eth1: new Eth1ForBlockProductionDisabled(), + executionEngine: new ExecutionEngineDisabled(), + } + ); + }); + + after(async () => { + // If before blocks fail, db won't be declared + if (db !== undefined) await db.close(); + if (chain !== undefined) await chain.close(); + }); + + itBench({ + id: "proposeBlockBody type=full, size=empty", + minRuns: 5, + maxMs: Infinity, + timeoutBench: 60 * 1000, + beforeEach: async () => { + const head = chain.forkChoice.getHead(); + const proposerIndex = state.epochCtx.getBeaconProposer(state.slot); + const proposerPubKey = state.epochCtx.index2pubkey[proposerIndex].toBytes(); + + return {chain, state, head, proposerIndex, proposerPubKey}; + }, + fn: async ({chain, state, head, proposerIndex, proposerPubKey}) => { + const slot = state.slot; + + await produceBlockBody.call(chain, BlockType.Full, state, { + parentSlot: slot, + slot: slot + 1, + graffiti: Buffer.alloc(32), + randaoReveal: Buffer.alloc(96), + parentBlockRoot: fromHexString(head.blockRoot), + proposerIndex, + proposerPubKey, + }); + }, + }); +});