diff --git a/packages/beacon-node/test/fixtures/altair.ts b/packages/beacon-node/test/fixtures/altair.ts new file mode 100644 index 000000000000..19b5bad859f1 --- /dev/null +++ b/packages/beacon-node/test/fixtures/altair.ts @@ -0,0 +1,28 @@ +import {CachedBeaconStateAllForks} from "@lodestar/state-transition"; +import {ssz, altair} from "@lodestar/types"; +import {BlockGenerationOptionsPhase0, generatePhase0BeaconBlocks} from "./phase0.js"; +import {generateSignature} from "./utils.js"; + +export function generateSyncAggregate( + state: CachedBeaconStateAllForks, + block: altair.BeaconBlock +): altair.SyncAggregate { + return { + syncCommitteeBits: ssz.altair.SyncCommitteeBits.defaultValue(), + syncCommitteeSignature: generateSignature(), + }; +} + +export interface BlockGenerationOptionsAltair extends BlockGenerationOptionsPhase0 {} + +export function generateAltairBeaconBlocks( + state: CachedBeaconStateAllForks, + count: number, + opts?: BlockGenerationOptionsAltair +): altair.BeaconBlock[] { + const blocks = generatePhase0BeaconBlocks(state, count, opts) as altair.BeaconBlock[]; + for (const block of blocks) { + block.body.syncAggregate = generateSyncAggregate(state, block); + } + return blocks; +} diff --git a/packages/beacon-node/test/fixtures/bellatrix.ts b/packages/beacon-node/test/fixtures/bellatrix.ts new file mode 100644 index 000000000000..e698c36b108a --- /dev/null +++ b/packages/beacon-node/test/fixtures/bellatrix.ts @@ -0,0 +1,36 @@ +import {ssz, bellatrix} from "@lodestar/types"; +import {CachedBeaconStateAllForks} from "@lodestar/state-transition"; +import {BlockGenerationOptionsAltair, generateAltairBeaconBlocks} from "./altair.js"; + +export function generateBellatrixExecutionPayload(): bellatrix.ExecutionPayload { + return { + baseFeePerGas: BigInt(0), + blockHash: new Uint8Array(), + blockNumber: 0, + extraData: new Uint8Array(), + feeRecipient: new Uint8Array(), + gasLimit: 0, + gasUsed: 0, + logsBloom: new Uint8Array(), + parentHash: new Uint8Array(), + prevRandao: new Uint8Array(), + receiptsRoot: new Uint8Array(), + stateRoot: new Uint8Array(), + timestamp: 0, + transactions: [ssz.bellatrix.Transaction.defaultValue()], + }; +} + +export interface BlockGenerationOptionsBellatrix extends BlockGenerationOptionsAltair {} + +export function generateBellatrixBeaconBlocks( + state: CachedBeaconStateAllForks, + count: number, + opts?: BlockGenerationOptionsBellatrix +): bellatrix.BeaconBlock[] { + const blocks = generateAltairBeaconBlocks(state, count, opts) as bellatrix.BeaconBlock[]; + for (const block of blocks) { + block.body.executionPayload = generateBellatrixExecutionPayload(); + } + return blocks; +} diff --git a/packages/beacon-node/test/fixtures/capella.ts b/packages/beacon-node/test/fixtures/capella.ts index fe9b0206efb1..55e191d73f88 100644 --- a/packages/beacon-node/test/fixtures/capella.ts +++ b/packages/beacon-node/test/fixtures/capella.ts @@ -1,8 +1,16 @@ -import {CachedBeaconStateAltair} from "@lodestar/state-transition"; -import {capella} from "@lodestar/types"; +import {CachedBeaconStateAllForks} from "@lodestar/state-transition"; +import {ssz, bellatrix, capella} from "@lodestar/types"; +import {BlockGenerationOptionsBellatrix, generateBellatrixBeaconBlocks} from "./bellatrix.js"; + +export function generateCapellaExecutionPayload(payload: bellatrix.ExecutionPayload): capella.ExecutionPayload { + return { + ...payload, + withdrawals: [ssz.capella.Withdrawal.defaultValue()], + }; +} export function generateBlsToExecutionChanges( - state: CachedBeaconStateAltair, + state: CachedBeaconStateAllForks, count: number ): capella.SignedBLSToExecutionChange[] { const result: capella.SignedBLSToExecutionChange[] = []; @@ -22,3 +30,18 @@ export function generateBlsToExecutionChanges( return result; } + +export interface BlockGenerationOptionsCapella extends BlockGenerationOptionsBellatrix {} + +export function generateCapellaBeaconBlocks( + state: CachedBeaconStateAllForks, + count: number, + opts?: BlockGenerationOptionsCapella +): capella.BeaconBlock[] { + const blocks = generateBellatrixBeaconBlocks(state, count, opts) as capella.BeaconBlock[]; + for (const block of blocks) { + block.body.executionPayload = generateCapellaExecutionPayload(block.body.executionPayload); + block.body.blsToExecutionChanges = generateBlsToExecutionChanges(state, count); + } + return blocks; +} diff --git a/packages/beacon-node/test/fixtures/deneb.ts b/packages/beacon-node/test/fixtures/deneb.ts new file mode 100644 index 000000000000..37c78b4d2a58 --- /dev/null +++ b/packages/beacon-node/test/fixtures/deneb.ts @@ -0,0 +1,34 @@ +import crypto from "node:crypto"; +import {CachedBeaconStateAllForks} from "@lodestar/state-transition"; +import {capella, deneb} from "@lodestar/types"; +import {MAX_BLOBS_PER_BLOCK} from "@lodestar/params"; +import {generateCapellaBeaconBlocks, BlockGenerationOptionsCapella} from "./capella.js"; + +export function generateDenebExecutionPayload(payload: capella.ExecutionPayload): deneb.ExecutionPayload { + return { + ...payload, + blobGasUsed: BigInt(0), + excessBlobGas: BigInt(0), + }; +} + +export function generateKzgCommitments(count: number): deneb.BlobKzgCommitments { + return Array.from({length: count}, () => Uint8Array.from(crypto.randomBytes(48))); +} + +export interface BlockGenerationOptionsDeneb extends BlockGenerationOptionsCapella { + numKzgCommitments: number; +} + +export function generateDenebBeaconBlocks( + state: CachedBeaconStateAllForks, + count: number, + opts?: BlockGenerationOptionsDeneb +): capella.BeaconBlock[] { + const blocks = generateCapellaBeaconBlocks(state, count, opts) as deneb.BeaconBlock[]; + for (const block of blocks) { + block.body.executionPayload = generateDenebExecutionPayload(block.body.executionPayload); + block.body.blobKzgCommitments = generateKzgCommitments(opts?.numKzgCommitments ?? MAX_BLOBS_PER_BLOCK); + } + return blocks; +} diff --git a/packages/beacon-node/test/fixtures/electra.ts b/packages/beacon-node/test/fixtures/electra.ts new file mode 100644 index 000000000000..167a2908afb4 --- /dev/null +++ b/packages/beacon-node/test/fixtures/electra.ts @@ -0,0 +1,26 @@ +import {deneb, electra} from "@lodestar/types"; +import {CachedBeaconStateAllForks} from "@lodestar/state-transition"; +import {BlockGenerationOptionsDeneb, generateDenebBeaconBlocks} from "./deneb.js"; + +export function generateElectraExecutionPayload(payload: deneb.ExecutionPayload): electra.ExecutionPayload { + return { + ...payload, + depositRequests: [], + withdrawalRequests: [], + consolidationRequests: [], + }; +} + +export interface BlockGenerationOptionsElectra extends BlockGenerationOptionsDeneb {} + +export function generateElectraBeaconBlocks( + state: CachedBeaconStateAllForks, + count: number, + opts?: BlockGenerationOptionsElectra +): electra.BeaconBlock[] { + const blocks = generateDenebBeaconBlocks(state, count, opts) as electra.BeaconBlock[]; + for (const block of blocks) { + block.body.executionPayload = generateElectraExecutionPayload(block.body.executionPayload); + } + return blocks; +} diff --git a/packages/beacon-node/test/fixtures/phase0.ts b/packages/beacon-node/test/fixtures/phase0.ts index 56b419a824e7..5bdc77888f8e 100644 --- a/packages/beacon-node/test/fixtures/phase0.ts +++ b/packages/beacon-node/test/fixtures/phase0.ts @@ -1,14 +1,84 @@ -import {SLOTS_PER_EPOCH} from "@lodestar/params"; +import crypto from "node:crypto"; import { - CachedBeaconStateAltair, + MAX_ATTESTER_SLASHINGS, + MAX_DEPOSITS, + MAX_PROPOSER_SLASHINGS, + MAX_VOLUNTARY_EXITS, + SLOTS_PER_EPOCH, +} from "@lodestar/params"; +import { + CachedBeaconStateAllForks, computeEpochAtSlot, computeStartSlotAtEpoch, getBlockRootAtSlot, + getRandaoMix, } from "@lodestar/state-transition"; -import {phase0} from "@lodestar/types"; +import {phase0, ssz} from "@lodestar/types"; +import {getDefaultGraffiti} from "../../src/util/graffiti.js"; +import {getLodestarClientVersion} from "../../src/util/metadata.js"; +import {getDepositsWithProofs} from "../../src/eth1/utils/deposits.js"; +import {generateKey, generateSignature, signContainer} from "./utils.js"; + +export function generateAttestationData( + state: CachedBeaconStateAllForks, + committeeIndex: number +): phase0.AttestationData { + const slot = state.slot; + const epoch = computeEpochAtSlot(slot); + return { + slot, + index: committeeIndex, + beaconBlockRoot: getBlockRootAtSlot(state, slot), + source: { + epoch: state.currentJustifiedCheckpoint.epoch, + root: state.currentJustifiedCheckpoint.root, + }, + target: { + epoch: epoch, + root: getBlockRootAtSlot(state, computeStartSlotAtEpoch(epoch)), + }, + }; +} + +export function generateAttestation( + state: CachedBeaconStateAllForks, + committeeIndex: number, + indexed: T +): T extends true ? phase0.IndexedAttestation : phase0.Attestation { + const slot = state.slot; + const attestation = { + data: generateAttestationData(state, committeeIndex), + signature: generateSignature(), + } as unknown as T extends true ? phase0.IndexedAttestation : phase0.Attestation; + + if (indexed) { + (attestation as phase0.IndexedAttestation).attestingIndices = Array.from( + state.epochCtx.getBeaconCommittee(slot, committeeIndex) + ); + } else { + // TODO: (@matthewkeil) add some mock data here so its not all zeros + (attestation as phase0.Attestation).aggregationBits = ssz.phase0.CommitteeBits.defaultValue(); + } + + return attestation; +} + +export function generateAttestations(state: CachedBeaconStateAllForks): phase0.Attestation[] { + const epoch = computeEpochAtSlot(state.slot); + const committeeCount = state.epochCtx.getCommitteeCountPerSlot(epoch); + const attestations: phase0.Attestation[] = []; + for (let committeeIndex = 0; committeeIndex < committeeCount; committeeIndex++) { + attestations.push( + ...Array.from({length: state.epochCtx.getBeaconCommittee(state.slot, committeeIndex).length}, () => + generateAttestation(state, committeeIndex, false) + ) + ); + } + return attestations; +} export function generateIndexedAttestations( - state: CachedBeaconStateAltair, + state: CachedBeaconStateAllForks, count: number ): phase0.IndexedAttestation[] { const result: phase0.IndexedAttestation[] = []; @@ -19,24 +89,7 @@ export function generateIndexedAttestations( const committeeCount = state.epochCtx.getCommitteeCountPerSlot(epoch); for (let committeeIndex = 0; committeeIndex < committeeCount; committeeIndex++) { - result.push({ - attestingIndices: Array.from(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), - }); - + result.push(generateAttestation(state, committeeIndex, true)); if (result.length >= count) return result; } } @@ -44,7 +97,10 @@ export function generateIndexedAttestations( return result; } -export function generateBeaconBlockHeader(state: CachedBeaconStateAltair, count: number): phase0.BeaconBlockHeader[] { +export function generateBeaconBlockHeaders( + state: CachedBeaconStateAllForks, + count: number +): phase0.BeaconBlockHeader[] { const headers: phase0.BeaconBlockHeader[] = []; for (let i = 1; i <= count; i++) { @@ -67,28 +123,28 @@ export function generateBeaconBlockHeader(state: CachedBeaconStateAltair, count: return headers; } -export function generateSignedBeaconBlockHeader( - state: CachedBeaconStateAltair, +export function generateSignedBeaconBlockHeaders( + state: CachedBeaconStateAllForks, count: number ): phase0.SignedBeaconBlockHeader[] { - const headers = generateBeaconBlockHeader(state, count); - - return headers.map((header) => ({ - message: header, - signature: Buffer.alloc(96), - })); + return generateBeaconBlockHeaders(state, count).map(signContainer); } -export function generateVoluntaryExits(state: CachedBeaconStateAltair, count: number): phase0.SignedVoluntaryExit[] { +export function generateVoluntaryExits( + state: CachedBeaconStateAllForks, + count: number = MAX_VOLUNTARY_EXITS +): phase0.SignedVoluntaryExit[] { const result: phase0.SignedVoluntaryExit[] = []; + if (count > MAX_VOLUNTARY_EXITS) count = MAX_VOLUNTARY_EXITS; + for (const validatorIndex of state.epochCtx.proposers) { result.push({ message: { epoch: state.currentJustifiedCheckpoint.epoch, validatorIndex, }, - signature: Buffer.alloc(96), + signature: generateSignature(), }); if (result.length >= count) return result; @@ -96,3 +152,121 @@ export function generateVoluntaryExits(state: CachedBeaconStateAltair, count: nu return result; } + +export function generateAttesterSlashings(attestations: phase0.IndexedAttestation[]): phase0.AttesterSlashing[] { + const slashings: phase0.AttesterSlashing[] = []; + for (const attestation of attestations) { + slashings.push({ + attestation1: ssz.phase0.IndexedAttestationBigint.fromJson(ssz.phase0.IndexedAttestation.toJson(attestation)), + attestation2: ssz.phase0.IndexedAttestationBigint.fromJson(ssz.phase0.IndexedAttestation.toJson(attestation)), + }); + + if (slashings.length >= MAX_ATTESTER_SLASHINGS) { + return slashings; + } + } + return slashings; +} + +export function generateProposerSlashings(blockHeaders: phase0.SignedBeaconBlockHeader[]): phase0.ProposerSlashing[] { + const slashings: phase0.ProposerSlashing[] = []; + for (const blockHeader of blockHeaders) { + const signedHeader2 = ssz.phase0.SignedBeaconBlockHeaderBigint.fromJson( + ssz.phase0.SignedBeaconBlockHeader.toJson(blockHeader) + ); + signedHeader2.message.bodyRoot = crypto.randomBytes(32); + + slashings.push({ + signedHeader1: ssz.phase0.SignedBeaconBlockHeaderBigint.fromJson( + ssz.phase0.SignedBeaconBlockHeader.toJson(blockHeader) + ), + signedHeader2, + }); + + if (slashings.length >= MAX_PROPOSER_SLASHINGS) { + return slashings; + } + } + return slashings; +} + +export function generateDepositEvents(count: number = MAX_DEPOSITS): phase0.DepositEvent[] { + const deposits: phase0.DepositEvent[] = []; + if (count > MAX_DEPOSITS) count = MAX_DEPOSITS; + for (let i = 0; i < count; i++) { + deposits.push({ + blockNumber: 1, + index: 1, + depositData: { + pubkey: generateKey(), + amount: 32 * 10 ** 9, + withdrawalCredentials: Buffer.alloc(32, 0x77), + signature: generateSignature(), + }, + }); + } + return deposits; +} + +export function generateDeposits(state: CachedBeaconStateAllForks, count: number = MAX_DEPOSITS): phase0.Deposit[] { + const depositEvents = generateDepositEvents(count); + // TODO: (@matthewkeil) how do you set the deposit root as root node? + const depositRootTree = ssz.phase0.DepositDataRootList.toViewDU([state.eth1Data.depositRoot]); + return getDepositsWithProofs(depositEvents, depositRootTree, state.eth1Data); +} + +export interface BlockGenerationOptionsPhase0 { + numAttesterSlashings?: number; + numProposerSlashings?: number; + numVoluntaryExits?: number; + numDeposits?: number; +} + +export function generatePhase0BeaconBlocks( + state: CachedBeaconStateAllForks, + count: number, + opts?: BlockGenerationOptionsPhase0 +): phase0.BeaconBlock[] { + const headers = generateBeaconBlockHeaders(state, count); + const attesterSlashings: phase0.AttesterSlashing[] = []; + const proposerSlashings: phase0.ProposerSlashing[] = []; + + if (opts?.numProposerSlashings !== undefined) { + if (opts.numProposerSlashings > headers.length) { + opts.numProposerSlashings = headers.length; + } + proposerSlashings.push( + ...generateProposerSlashings(headers.slice(0, opts.numProposerSlashings).map(signContainer)) + ); + } + + if (opts?.numAttesterSlashings !== undefined) { + const indexedAttestations = generateIndexedAttestations(state, opts.numAttesterSlashings); + attesterSlashings.push(...generateAttesterSlashings(indexedAttestations)); + } + + const blocks: phase0.BeaconBlock[] = []; + for (const header of headers) { + // @ts-expect-error can delete + delete header.bodyRoot; + const block: phase0.BeaconBlock = { + ...header, + body: { + eth1Data: { + blockHash: state.eth1Data.blockHash, + depositCount: state.eth1Data.depositCount, + depositRoot: state.eth1Data.depositRoot, + }, + graffiti: Uint8Array.from(Buffer.from(getDefaultGraffiti(getLodestarClientVersion(), null, {}), "utf8")), + randaoReveal: getRandaoMix(state, state.epochCtx.epoch), + attestations: generateAttestations(state), + attesterSlashings, + deposits: generateDeposits(state, opts?.numDeposits), + proposerSlashings, + voluntaryExits: generateVoluntaryExits(state, opts?.numVoluntaryExits), + }, + }; + blocks.push(block); + } + return blocks; +} diff --git a/packages/beacon-node/test/fixtures/utils.ts b/packages/beacon-node/test/fixtures/utils.ts new file mode 100644 index 000000000000..33b182692579 --- /dev/null +++ b/packages/beacon-node/test/fixtures/utils.ts @@ -0,0 +1,18 @@ +export function generateKey(): Buffer { + return Buffer.alloc(48, 0xaa); +} + +export function generateSignature(): Buffer { + return Buffer.alloc(96, 0xaa); +} + +export interface SignedContainer { + message: T; + signature: Uint8Array; +} +export function signContainer(container: T): SignedContainer { + return { + message: container, + signature: generateSignature(), + }; +} diff --git a/packages/beacon-node/test/perf/chain/opPools/opPool.test.ts b/packages/beacon-node/test/perf/chain/opPools/opPool.test.ts index 6e420f0e1011..c5016171b289 100644 --- a/packages/beacon-node/test/perf/chain/opPools/opPool.test.ts +++ b/packages/beacon-node/test/perf/chain/opPools/opPool.test.ts @@ -13,7 +13,7 @@ import {OpPool} from "../../../../src/chain/opPools/opPool.js"; import {generateBlsToExecutionChanges} from "../../../fixtures/capella.js"; import { generateIndexedAttestations, - generateSignedBeaconBlockHeader, + generateSignedBeaconBlockHeaders, generateVoluntaryExits, } from "../../../fixtures/phase0.js"; import {BlockType} from "../../../../src/chain/interface.js"; @@ -74,7 +74,7 @@ function fillAttesterSlashing(pool: OpPool, state: CachedBeaconStateAltair, coun } function fillProposerSlashing(pool: OpPool, state: CachedBeaconStateAltair, count: number): OpPool { - for (const blockHeader of generateSignedBeaconBlockHeader(state, count)) { + for (const blockHeader of generateSignedBeaconBlockHeaders(state, count)) { pool.insertProposerSlashing({ signedHeader1: ssz.phase0.SignedBeaconBlockHeaderBigint.fromJson( ssz.phase0.SignedBeaconBlockHeader.toJson(blockHeader)