From 39a579ebd9be9f8051e332a7dbb61234edcca5b7 Mon Sep 17 00:00:00 2001 From: twoeths Date: Wed, 27 Nov 2024 04:13:17 +0700 Subject: [PATCH 01/34] feat: implement SingleAttestation (#7126) * feat: refactor SeenAttestationDatas for SinlgeAttestation * feat: add SingleAttestation type * feat: ssz utils for SingleAttestation * feat: implement SingleAttestation for network processor and gossip queue * fix: add SingleAttestation for phase0 and altair * fix: define and publish SingleAttestation for all forks * Fix electra SingleAttestation type mapping * Update api and eventstream * Update validator client * Update attestation unit test variables * chore: SeenAttestationDatas unit tests * chore: sszBytes unit tests * Use CommitteeIndex type * refactor: get/set functions of SeenAttestationDatas * Always emit single_attestation event * Validation use new SeenAttDataKey * validateAttestationNoSignatureCheck first draft * Add aggregation and committee bits to cache * AttestationPool accepts SingleAttestation * Update SingleAttestation event stream * Update aggregate validation * Polish * Lint * fix check-types * Remove committee bit cache * Update attestation pool unit tests * Lint * Remove unused committeeBits from attestation data cache * Fix spec reference comment * fix: getSeenAttDataKeyFromSignedAggregateAndProof * Update beacon-api spec tests to run against v3.0.0-alpha.9 --------- Co-authored-by: Nico Flaig Co-authored-by: NC <17676176+ensi321@users.noreply.github.com> --- packages/api/src/beacon/routes/beacon/pool.ts | 43 ++-- packages/api/src/beacon/routes/events.ts | 6 + .../api/test/unit/beacon/oapiSpec.test.ts | 2 +- .../api/test/unit/beacon/testData/events.ts | 13 ++ .../src/api/impl/beacon/pool/index.ts | 44 +++- .../src/chain/errors/attestationError.ts | 7 +- .../src/chain/opPools/attestationPool.ts | 44 ++-- .../chain/seenCache/seenAttestationData.ts | 74 ++++-- .../src/chain/validation/aggregateAndProof.ts | 6 +- .../src/chain/validation/attestation.ts | 218 +++++++++++------- .../src/network/gossip/interface.ts | 6 +- .../beacon-node/src/network/gossip/topic.ts | 19 +- packages/beacon-node/src/network/interface.ts | 3 +- packages/beacon-node/src/network/network.ts | 3 +- .../network/processor/extractSlotRootFns.ts | 11 +- .../src/network/processor/gossipHandlers.ts | 32 ++- .../network/processor/gossipQueues/index.ts | 5 +- .../src/network/processor/index.ts | 2 +- .../src/network/processor/types.ts | 3 +- packages/beacon-node/src/util/sszBytes.ts | 132 +++++++++-- .../test/memory/seenAttestationData.ts | 2 +- .../chain/opPools/attestationPool.test.ts | 47 ++-- .../seenCache/seenAttestationData.test.ts | 28 ++- .../attestation/validateAttestation.test.ts | 4 +- .../test/unit/util/sszBytes.test.ts | 123 ++++++++-- packages/types/src/electra/sszTypes.ts | 12 + packages/types/src/electra/types.ts | 1 + packages/types/src/types.ts | 7 + packages/types/src/utils/typeguards.ts | 7 + .../validator/src/services/attestation.ts | 12 +- .../validator/src/services/validatorStore.ts | 8 +- .../test/unit/services/attestation.test.ts | 29 ++- 32 files changed, 693 insertions(+), 260 deletions(-) diff --git a/packages/api/src/beacon/routes/beacon/pool.ts b/packages/api/src/beacon/routes/beacon/pool.ts index 4d909c2aac7b..9a65bd489a81 100644 --- a/packages/api/src/beacon/routes/beacon/pool.ts +++ b/packages/api/src/beacon/routes/beacon/pool.ts @@ -1,7 +1,16 @@ import {ValueOf} from "@chainsafe/ssz"; import {ChainForkConfig} from "@lodestar/config"; -import {isForkPostElectra} from "@lodestar/params"; -import {AttesterSlashing, CommitteeIndex, Slot, capella, electra, phase0, ssz} from "@lodestar/types"; +import {ForkPostElectra, ForkPreElectra, isForkPostElectra} from "@lodestar/params"; +import { + AttesterSlashing, + CommitteeIndex, + SingleAttestation, + Slot, + capella, + electra, + phase0, + ssz, +} from "@lodestar/types"; import { ArrayOf, EmptyArgs, @@ -20,6 +29,8 @@ import {MetaHeader, VersionCodec, VersionMeta} from "../../../utils/metadata.js" // See /packages/api/src/routes/index.ts for reasoning and instructions to add new routes +const SingleAttestationListTypePhase0 = ArrayOf(ssz.phase0.Attestation); +const SingleAttestationListTypeElectra = ArrayOf(ssz.electra.SingleAttestation); const AttestationListTypePhase0 = ArrayOf(ssz.phase0.Attestation); const AttestationListTypeElectra = ArrayOf(ssz.electra.Attestation); const AttesterSlashingListTypePhase0 = ArrayOf(ssz.phase0.AttesterSlashing); @@ -142,7 +153,7 @@ export type Endpoints = { */ submitPoolAttestations: Endpoint< "POST", - {signedAttestations: AttestationListPhase0}, + {signedAttestations: SingleAttestation[]}, {body: unknown}, EmptyResponseData, EmptyMeta @@ -158,7 +169,7 @@ export type Endpoints = { */ submitPoolAttestationsV2: Endpoint< "POST", - {signedAttestations: AttestationList}, + {signedAttestations: SingleAttestation[]}, {body: unknown; headers: {[MetaHeader.Version]: string}}, EmptyResponseData, EmptyMeta @@ -316,10 +327,10 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions ({body: AttestationListTypePhase0.toJson(signedAttestations)}), - parseReqJson: ({body}) => ({signedAttestations: AttestationListTypePhase0.fromJson(body)}), - writeReqSsz: ({signedAttestations}) => ({body: AttestationListTypePhase0.serialize(signedAttestations)}), - parseReqSsz: ({body}) => ({signedAttestations: AttestationListTypePhase0.deserialize(body)}), + writeReqJson: ({signedAttestations}) => ({body: SingleAttestationListTypePhase0.toJson(signedAttestations)}), + parseReqJson: ({body}) => ({signedAttestations: SingleAttestationListTypePhase0.fromJson(body)}), + writeReqSsz: ({signedAttestations}) => ({body: SingleAttestationListTypePhase0.serialize(signedAttestations)}), + parseReqSsz: ({body}) => ({signedAttestations: SingleAttestationListTypePhase0.deserialize(body)}), schema: { body: Schema.ObjectArray, }, @@ -334,8 +345,8 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions[]) + : SingleAttestationListTypePhase0.toJson(signedAttestations as SingleAttestation[]), headers: {[MetaHeader.Version]: fork}, }; }, @@ -343,16 +354,16 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions { const fork = config.getForkName(signedAttestations[0]?.data.slot ?? 0); return { body: isForkPostElectra(fork) - ? AttestationListTypeElectra.serialize(signedAttestations as AttestationListElectra) - : AttestationListTypePhase0.serialize(signedAttestations as AttestationListPhase0), + ? SingleAttestationListTypeElectra.serialize(signedAttestations as SingleAttestation[]) + : SingleAttestationListTypePhase0.serialize(signedAttestations as SingleAttestation[]), headers: {[MetaHeader.Version]: fork}, }; }, @@ -360,8 +371,8 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions + ); + } else { + chain.emitter.emit(routes.events.EventType.attestation, attestation as SingleAttestation); + chain.emitter.emit( + routes.events.EventType.singleAttestation, + toElectraSingleAttestation( + attestation as SingleAttestation, + indexedAttestation.attestingIndices[0] + ) + ); + } const sentPeers = await network.publishBeaconAttestation(attestation, subnet); metrics?.onPoolSubmitUnaggregatedAttestation(seenTimestampSec, indexedAttestation, subnet, sentPeers); diff --git a/packages/beacon-node/src/chain/errors/attestationError.ts b/packages/beacon-node/src/chain/errors/attestationError.ts index 618a334928ae..1f907be96e7c 100644 --- a/packages/beacon-node/src/chain/errors/attestationError.ts +++ b/packages/beacon-node/src/chain/errors/attestationError.ts @@ -135,6 +135,10 @@ export enum AttestationErrorCode { * Electra: Invalid attestationData index: is non-zero */ NON_ZERO_ATTESTATION_DATA_INDEX = "ATTESTATION_ERROR_NON_ZERO_ATTESTATION_DATA_INDEX", + /** + * Electra: Attester not in committee + */ + ATTESTER_NOT_IN_COMMITTEE = "ATTESTATION_ERROR_ATTESTER_NOT_IN_COMMITTEE", } export type AttestationErrorType = @@ -170,7 +174,8 @@ export type AttestationErrorType = | {code: AttestationErrorCode.INVALID_SERIALIZED_BYTES} | {code: AttestationErrorCode.TOO_MANY_SKIPPED_SLOTS; headBlockSlot: Slot; attestationSlot: Slot} | {code: AttestationErrorCode.NOT_EXACTLY_ONE_COMMITTEE_BIT_SET} - | {code: AttestationErrorCode.NON_ZERO_ATTESTATION_DATA_INDEX}; + | {code: AttestationErrorCode.NON_ZERO_ATTESTATION_DATA_INDEX} + | {code: AttestationErrorCode.ATTESTER_NOT_IN_COMMITTEE}; export class AttestationError extends GossipActionError { getMetadata(): Record { diff --git a/packages/beacon-node/src/chain/opPools/attestationPool.ts b/packages/beacon-node/src/chain/opPools/attestationPool.ts index 9809c9c6304a..30f3738bb273 100644 --- a/packages/beacon-node/src/chain/opPools/attestationPool.ts +++ b/packages/beacon-node/src/chain/opPools/attestationPool.ts @@ -1,8 +1,8 @@ import {Signature, aggregateSignatures} from "@chainsafe/blst"; import {BitArray} from "@chainsafe/ssz"; import {ChainForkConfig} from "@lodestar/config"; -import {isForkPostElectra} from "@lodestar/params"; -import {Attestation, RootHex, Slot, isElectraAttestation} from "@lodestar/types"; +import {MAX_COMMITTEES_PER_SLOT, isForkPostElectra} from "@lodestar/params"; +import {Attestation, RootHex, SingleAttestation, Slot, isElectraSingleAttestation} from "@lodestar/types"; import {assert, MapDef} from "@lodestar/utils"; import {IClock} from "../../util/clock.js"; import {InsertOutcome, OpPoolError, OpPoolErrorCode} from "./types.js"; @@ -105,7 +105,12 @@ export class AttestationPool { * - Valid committeeIndex * - Valid data */ - add(committeeIndex: CommitteeIndex, attestation: Attestation, attDataRootHex: RootHex): InsertOutcome { + add( + committeeIndex: CommitteeIndex, + attestation: SingleAttestation, + attDataRootHex: RootHex, + aggregationBits: BitArray | null + ): InsertOutcome { const slot = attestation.data.slot; const fork = this.config.getForkName(slot); const lowestPermissibleSlot = this.lowestPermissibleSlot; @@ -129,9 +134,9 @@ export class AttestationPool { if (isForkPostElectra(fork)) { // Electra only: this should not happen because attestation should be validated before reaching this assert.notNull(committeeIndex, "Committee index should not be null in attestation pool post-electra"); - assert.true(isElectraAttestation(attestation), "Attestation should be type electra.Attestation"); + assert.true(isElectraSingleAttestation(attestation), "Attestation should be type electra.SingleAttestation"); } else { - assert.true(!isElectraAttestation(attestation), "Attestation should be type phase0.Attestation"); + assert.true(!isElectraSingleAttestation(attestation), "Attestation should be type phase0.Attestation"); committeeIndex = null; // For pre-electra, committee index info is encoded in attDataRootIndex } @@ -144,10 +149,10 @@ export class AttestationPool { const aggregate = aggregateByIndex.get(committeeIndex); if (aggregate) { // Aggregate mutating - return aggregateAttestationInto(aggregate, attestation); + return aggregateAttestationInto(aggregate, attestation, aggregationBits); } // Create new aggregate - aggregateByIndex.set(committeeIndex, attestationToAggregate(attestation)); + aggregateByIndex.set(committeeIndex, attestationToAggregate(attestation, aggregationBits)); return InsertOutcome.NewData; } @@ -216,8 +221,19 @@ export class AttestationPool { /** * Aggregate a new attestation into `aggregate` mutating it */ -function aggregateAttestationInto(aggregate: AggregateFast, attestation: Attestation): InsertOutcome { - const bitIndex = attestation.aggregationBits.getSingleTrueBit(); +function aggregateAttestationInto( + aggregate: AggregateFast, + attestation: SingleAttestation, + aggregationBits: BitArray | null +): InsertOutcome { + let bitIndex: number | null; + + if (isElectraSingleAttestation(attestation)) { + assert.notNull(aggregationBits, "aggregationBits missing post-electra"); + bitIndex = aggregationBits.getSingleTrueBit(); + } else { + bitIndex = attestation.aggregationBits.getSingleTrueBit(); + } // Should never happen, attestations are verified against this exact condition before assert.notNull(bitIndex, "Invalid attestation in pool, not exactly one bit set"); @@ -234,13 +250,13 @@ function aggregateAttestationInto(aggregate: AggregateFast, attestation: Attesta /** * Format `contribution` into an efficient `aggregate` to add more contributions in with aggregateContributionInto() */ -function attestationToAggregate(attestation: Attestation): AggregateFast { - if (isElectraAttestation(attestation)) { +function attestationToAggregate(attestation: SingleAttestation, aggregationBits: BitArray | null): AggregateFast { + if (isElectraSingleAttestation(attestation)) { + assert.notNull(aggregationBits, "aggregationBits missing post-electra to generate aggregate"); return { data: attestation.data, - // clone because it will be mutated - aggregationBits: attestation.aggregationBits.clone(), - committeeBits: attestation.committeeBits, + aggregationBits, + committeeBits: BitArray.fromSingleBit(MAX_COMMITTEES_PER_SLOT, attestation.committeeIndex), signature: signatureFromBytesNoCheck(attestation.signature), }; } diff --git a/packages/beacon-node/src/chain/seenCache/seenAttestationData.ts b/packages/beacon-node/src/chain/seenCache/seenAttestationData.ts index 8e0dfcb3bd96..498c8f36ef8e 100644 --- a/packages/beacon-node/src/chain/seenCache/seenAttestationData.ts +++ b/packages/beacon-node/src/chain/seenCache/seenAttestationData.ts @@ -4,17 +4,14 @@ import {MapDef} from "@lodestar/utils"; import {Metrics} from "../../metrics/metrics.js"; import {InsertOutcome} from "../opPools/types.js"; -export type SeenAttDataKey = AttDataBase64 | AttDataCommitteeBitsBase64; -// pre-electra, AttestationData is used to cache attestations +export type SeenAttDataKey = AttDataBase64; +// AttestationData is used to cache attestations type AttDataBase64 = string; -// electra, AttestationData + CommitteeBits are used to cache attestations -type AttDataCommitteeBitsBase64 = string; export type AttestationDataCacheEntry = { // part of shuffling data, so this does not take memory committeeValidatorIndices: Uint32Array; - // undefined for phase0 Attestation - committeeBits?: BitArray; + // TODO: remove this? this is available in SingleAttestation committeeIndex: CommitteeIndex; // IndexedAttestationData signing root, 32 bytes signingRoot: Uint8Array; @@ -24,6 +21,8 @@ export type AttestationDataCacheEntry = { // for example in a mainnet node subscribing to all subnets, attestations are processed up to 20k per slot attestationData: phase0.AttestationData; subnet: number; + // aggregationBits only populates post-electra. Pre-electra can use get it directly from attestationOrBytes + aggregationBits: BitArray | null; }; export enum RejectReason { @@ -35,6 +34,10 @@ export enum RejectReason { already_known = "already_known", } +// For pre-electra, there is no committeeIndex in SingleAttestation, so we hard code it to 0 +// AttDataBase64 has committeeIndex instead +export const PRE_ELECTRA_SINGLE_ATTESTATION_COMMITTEE_INDEX = 0; + /** * There are maximum 64 committees per slot, assuming 1 committee may have up to 3 different data due to some nodes * are not up to date, we can have up to 192 different attestation data per slot. @@ -53,8 +56,14 @@ const DEFAULT_CACHE_SLOT_DISTANCE = 2; * Having this cache help saves a lot of cpu time since most of the gossip attestations are on the same slot. */ export class SeenAttestationDatas { - private cacheEntryByAttDataBase64BySlot = new MapDef>( - () => new Map() + private cacheEntryByAttDataByIndexBySlot = new MapDef< + Slot, + MapDef> + >( + () => + new MapDef>( + () => new Map() + ) ); private lowestPermissibleSlot = 0; @@ -67,31 +76,47 @@ export class SeenAttestationDatas { metrics?.seenCache.attestationData.totalSlot.addCollect(() => this.onScrapeLodestarMetrics(metrics)); } - // TODO: Move InsertOutcome type definition to a common place - add(slot: Slot, attDataKey: SeenAttDataKey, cacheEntry: AttestationDataCacheEntry): InsertOutcome { + /** + * Add an AttestationDataCacheEntry to the cache. + * - preElectra: add(slot, PRE_ELECTRA_SINGLE_ATTESTATION_COMMITTEE_INDEX, attDataBase64, cacheEntry) + * - electra: add(slot, committeeIndex, attDataBase64, cacheEntry) + */ + add( + slot: Slot, + committeeIndex: CommitteeIndex, + attDataBase64: AttDataBase64, + cacheEntry: AttestationDataCacheEntry + ): InsertOutcome { if (slot < this.lowestPermissibleSlot) { this.metrics?.seenCache.attestationData.reject.inc({reason: RejectReason.too_old}); return InsertOutcome.Old; } - const cacheEntryByAttDataBase64 = this.cacheEntryByAttDataBase64BySlot.getOrDefault(slot); - if (cacheEntryByAttDataBase64.has(attDataKey)) { + const cacheEntryByAttDataByIndex = this.cacheEntryByAttDataByIndexBySlot.getOrDefault(slot); + const cacheEntryByAttData = cacheEntryByAttDataByIndex.getOrDefault(committeeIndex); + if (cacheEntryByAttData.has(attDataBase64)) { this.metrics?.seenCache.attestationData.reject.inc({reason: RejectReason.already_known}); return InsertOutcome.AlreadyKnown; } - if (cacheEntryByAttDataBase64.size >= this.maxCacheSizePerSlot) { + if (cacheEntryByAttData.size >= this.maxCacheSizePerSlot) { this.metrics?.seenCache.attestationData.reject.inc({reason: RejectReason.reached_limit}); return InsertOutcome.ReachLimit; } - cacheEntryByAttDataBase64.set(attDataKey, cacheEntry); + cacheEntryByAttData.set(attDataBase64, cacheEntry); return InsertOutcome.NewData; } - get(slot: Slot, attDataBase64: SeenAttDataKey): AttestationDataCacheEntry | null { - const cacheEntryByAttDataBase64 = this.cacheEntryByAttDataBase64BySlot.get(slot); - const cacheEntry = cacheEntryByAttDataBase64?.get(attDataBase64); + /** + * Get an AttestationDataCacheEntry from the cache. + * - preElectra: get(slot, PRE_ELECTRA_SINGLE_ATTESTATION_COMMITTEE_INDEX, attDataBase64) + * - electra: get(slot, committeeIndex, attDataBase64) + */ + get(slot: Slot, committeeIndex: CommitteeIndex, attDataBase64: SeenAttDataKey): AttestationDataCacheEntry | null { + const cacheEntryByAttDataByIndex = this.cacheEntryByAttDataByIndexBySlot.get(slot); + const cacheEntryByAttData = cacheEntryByAttDataByIndex?.get(committeeIndex); + const cacheEntry = cacheEntryByAttData?.get(attDataBase64); if (cacheEntry) { this.metrics?.seenCache.attestationData.hit.inc(); } else { @@ -102,20 +127,23 @@ export class SeenAttestationDatas { onSlot(clockSlot: Slot): void { this.lowestPermissibleSlot = Math.max(clockSlot - this.cacheSlotDistance, 0); - for (const slot of this.cacheEntryByAttDataBase64BySlot.keys()) { + for (const slot of this.cacheEntryByAttDataByIndexBySlot.keys()) { if (slot < this.lowestPermissibleSlot) { - this.cacheEntryByAttDataBase64BySlot.delete(slot); + this.cacheEntryByAttDataByIndexBySlot.delete(slot); } } } private onScrapeLodestarMetrics(metrics: Metrics): void { - metrics?.seenCache.attestationData.totalSlot.set(this.cacheEntryByAttDataBase64BySlot.size); + metrics?.seenCache.attestationData.totalSlot.set(this.cacheEntryByAttDataByIndexBySlot.size); // tracking number of attestation data at current slot may not be correct if scrape time is not at the end of slot // so we track it at the previous slot const previousSlot = this.lowestPermissibleSlot + this.cacheSlotDistance - 1; - metrics?.seenCache.attestationData.countPerSlot.set( - this.cacheEntryByAttDataBase64BySlot.get(previousSlot)?.size ?? 0 - ); + const cacheEntryByAttDataByIndex = this.cacheEntryByAttDataByIndexBySlot.get(previousSlot); + let count = 0; + for (const cacheEntryByAttDataBase64 of cacheEntryByAttDataByIndex?.values() ?? []) { + count += cacheEntryByAttDataBase64.size; + } + metrics?.seenCache.attestationData.countPerSlot.set(count); } } diff --git a/packages/beacon-node/src/chain/validation/aggregateAndProof.ts b/packages/beacon-node/src/chain/validation/aggregateAndProof.ts index 781e63cf623a..8f4a3dd53993 100644 --- a/packages/beacon-node/src/chain/validation/aggregateAndProof.ts +++ b/packages/beacon-node/src/chain/validation/aggregateAndProof.ts @@ -71,9 +71,6 @@ async function validateAggregateAndProof( const attData = aggregate.data; const attSlot = attData.slot; - const seenAttDataKey = serializedData ? getSeenAttDataKeyFromSignedAggregateAndProof(fork, serializedData) : null; - const cachedAttData = seenAttDataKey ? chain.seenAttestationDatas.get(attSlot, seenAttDataKey) : null; - let attIndex: number | null; if (ForkSeq[fork] >= ForkSeq.electra) { attIndex = (aggregate as electra.Attestation).committeeBits.getSingleTrueBit(); @@ -89,6 +86,9 @@ async function validateAggregateAndProof( attIndex = attData.index; } + const seenAttDataKey = serializedData ? getSeenAttDataKeyFromSignedAggregateAndProof(fork, serializedData) : null; + const cachedAttData = seenAttDataKey ? chain.seenAttestationDatas.get(attSlot, attIndex, seenAttDataKey) : null; + const attEpoch = computeEpochAtSlot(attSlot); const attTarget = attData.target; const targetEpoch = attTarget.epoch; diff --git a/packages/beacon-node/src/chain/validation/attestation.ts b/packages/beacon-node/src/chain/validation/attestation.ts index e49a3f79450c..305cbba3fbde 100644 --- a/packages/beacon-node/src/chain/validation/attestation.ts +++ b/packages/beacon-node/src/chain/validation/attestation.ts @@ -5,6 +5,8 @@ import { ATTESTATION_SUBNET_COUNT, DOMAIN_BEACON_ATTESTER, ForkName, + ForkPostElectra, + ForkPreElectra, ForkSeq, SLOTS_PER_EPOCH, isForkPostElectra, @@ -20,35 +22,41 @@ import { createSingleSignatureSetFromComponents, } from "@lodestar/state-transition"; import { - Attestation, CommitteeIndex, Epoch, IndexedAttestation, Root, RootHex, + SingleAttestation, Slot, + ValidatorIndex, electra, - isElectraAttestation, + isElectraSingleAttestation, phase0, ssz, } from "@lodestar/types"; -import {toRootHex} from "@lodestar/utils"; +import {assert, toRootHex} from "@lodestar/utils"; import {MAXIMUM_GOSSIP_CLOCK_DISPARITY_SEC} from "../../constants/index.js"; -import {sszDeserializeAttestation} from "../../network/gossip/topic.js"; +import {sszDeserializeSingleAttestation} from "../../network/gossip/topic.js"; import {getShufflingDependentRoot} from "../../util/dependentRoot.js"; import { getAggregationBitsFromAttestationSerialized, getAttDataFromSignedAggregateAndProofElectra, getAttDataFromSignedAggregateAndProofPhase0, - getCommitteeBitsFromAttestationSerialized, + getBeaconAttestationGossipIndex, getCommitteeBitsFromSignedAggregateAndProofElectra, + getCommitteeIndexFromSingleAttestationSerialized, getSignatureFromAttestationSerialized, } from "../../util/sszBytes.js"; import {Result, wrapError} from "../../util/wrapError.js"; import {AttestationError, AttestationErrorCode, GossipAction} from "../errors/index.js"; import {IBeaconChain} from "../interface.js"; import {RegenCaller} from "../regen/index.js"; -import {AttestationDataCacheEntry, SeenAttDataKey} from "../seenCache/seenAttestationData.js"; +import { + AttestationDataCacheEntry, + PRE_ELECTRA_SINGLE_ATTESTATION_COMMITTEE_INDEX, + SeenAttDataKey, +} from "../seenCache/seenAttestationData.js"; export type BatchResult = { results: Result[]; @@ -56,17 +64,18 @@ export type BatchResult = { }; export type AttestationValidationResult = { - attestation: Attestation; + attestation: SingleAttestation; indexedAttestation: IndexedAttestation; subnet: number; attDataRootHex: RootHex; committeeIndex: CommitteeIndex; + aggregationBits: BitArray | null; // Field populated post-electra only }; export type AttestationOrBytes = ApiAttestation | GossipAttestation; /** attestation from api */ -export type ApiAttestation = {attestation: Attestation; serializedData: null}; +export type ApiAttestation = {attestation: SingleAttestation; serializedData: null}; /** attestation from gossip */ export type GossipAttestation = { @@ -224,7 +233,7 @@ export async function validateApiAttestation( } /** - * Only deserialize the attestation if needed, use the cached AttestationData instead + * Only deserialize the single attestation if needed, use the cached AttestationData instead * This is to avoid deserializing similar attestation multiple times which could help the gc */ async function validateAttestationNoSignatureCheck( @@ -245,16 +254,20 @@ async function validateAttestationNoSignatureCheck( // Run the checks that happen before an indexed attestation is constructed. let attestationOrCache: - | {attestation: Attestation; cache: null} + | {attestation: SingleAttestation; cache: null} | {attestation: null; cache: AttestationDataCacheEntry; serializedData: Uint8Array}; let attDataKey: SeenAttDataKey | null = null; if (attestationOrBytes.serializedData) { // gossip const attSlot = attestationOrBytes.attSlot; - attDataKey = getSeenAttDataKeyFromGossipAttestation(fork, attestationOrBytes); - const cachedAttData = attDataKey !== null ? chain.seenAttestationDatas.get(attSlot, attDataKey) : null; + attDataKey = getSeenAttDataKeyFromGossipAttestation(attestationOrBytes); + const committeeIndexForLookup = isForkPostElectra(fork) + ? (getCommitteeIndexFromAttestationOrBytes(fork, attestationOrBytes) ?? 0) + : PRE_ELECTRA_SINGLE_ATTESTATION_COMMITTEE_INDEX; + const cachedAttData = + attDataKey !== null ? chain.seenAttestationDatas.get(attSlot, committeeIndexForLookup, attDataKey) : null; if (cachedAttData === null) { - const attestation = sszDeserializeAttestation(fork, attestationOrBytes.serializedData); + const attestation = sszDeserializeSingleAttestation(fork, attestationOrBytes.serializedData); // only deserialize on the first AttestationData that's not cached attestationOrCache = {attestation, cache: null}; } else { @@ -275,21 +288,11 @@ async function validateAttestationNoSignatureCheck( const targetEpoch = attTarget.epoch; let committeeIndex: number | null; if (attestationOrCache.attestation) { - if (isElectraAttestation(attestationOrCache.attestation)) { + if (isElectraSingleAttestation(attestationOrCache.attestation)) { // api or first time validation of a gossip attestation - const {committeeBits} = attestationOrCache.attestation; - // throw in both in case of undefined and null - if (committeeBits == null) { - throw new AttestationError(GossipAction.REJECT, {code: AttestationErrorCode.INVALID_SERIALIZED_BYTES}); - } - - committeeIndex = committeeBits.getSingleTrueBit(); - // [REJECT] len(committee_indices) == 1, where committee_indices = get_committee_indices(aggregate) - if (committeeIndex === null) { - throw new AttestationError(GossipAction.REJECT, {code: AttestationErrorCode.NOT_EXACTLY_ONE_COMMITTEE_BIT_SET}); - } + committeeIndex = attestationOrCache.attestation.committeeIndex; - // [REJECT] aggregate.data.index == 0 + // [REJECT] attestation.data.index == 0 if (attData.index !== 0) { throw new AttestationError(GossipAction.REJECT, {code: AttestationErrorCode.NON_ZERO_ATTESTATION_DATA_INDEX}); } @@ -321,23 +324,31 @@ async function validateAttestationNoSignatureCheck( verifyPropagationSlotRange(fork, chain, attestationOrCache.attestation.data.slot); } - // [REJECT] The attestation is unaggregated -- that is, it has exactly one participating validator - // (len([bit for bit in attestation.aggregation_bits if bit]) == 1, i.e. exactly 1 bit is set). - // > TODO: Do this check **before** getting the target state but don't recompute zipIndexes - const aggregationBits = attestationOrCache.attestation - ? attestationOrCache.attestation.aggregationBits - : getAggregationBitsFromAttestationSerialized(fork, attestationOrCache.serializedData); - if (aggregationBits === null) { - throw new AttestationError(GossipAction.REJECT, { - code: AttestationErrorCode.INVALID_SERIALIZED_BYTES, - }); - } + let aggregationBits: BitArray | null = null; + if (!isForkPostElectra(fork)) { + // [REJECT] The attestation is unaggregated -- that is, it has exactly one participating validator + // (len([bit for bit in attestation.aggregation_bits if bit]) == 1, i.e. exactly 1 bit is set). + // > TODO: Do this check **before** getting the target state but don't recompute zipIndexes + aggregationBits = attestationOrCache.attestation + ? (attestationOrCache.attestation as SingleAttestation).aggregationBits + : getAggregationBitsFromAttestationSerialized(attestationOrCache.serializedData); + if (aggregationBits === null) { + throw new AttestationError(GossipAction.REJECT, { + code: AttestationErrorCode.INVALID_SERIALIZED_BYTES, + }); + } - const bitIndex = aggregationBits.getSingleTrueBit(); - if (bitIndex === null) { - throw new AttestationError(GossipAction.REJECT, { - code: AttestationErrorCode.NOT_EXACTLY_ONE_AGGREGATION_BIT_SET, - }); + const bitIndex = aggregationBits.getSingleTrueBit(); + if (bitIndex === null) { + throw new AttestationError(GossipAction.REJECT, { + code: AttestationErrorCode.NOT_EXACTLY_ONE_AGGREGATION_BIT_SET, + }); + } + } else { + // Populate aggregationBits if cached post-electra, else we populate later + if (attestationOrCache.cache && attestationOrCache.cache.aggregationBits !== null) { + aggregationBits = attestationOrCache.cache.aggregationBits; + } } let committeeValidatorIndices: Uint32Array; @@ -391,15 +402,39 @@ async function validateAttestationNoSignatureCheck( expectedSubnet = computeSubnetForSlot(shuffling, attSlot, committeeIndex); } - const validatorIndex = committeeValidatorIndices[bitIndex]; + let validatorIndex: number; - // [REJECT] The number of aggregation bits matches the committee size - // -- i.e. len(attestation.aggregation_bits) == len(get_beacon_committee(state, data.slot, data.index)). - // > TODO: Is this necessary? Lighthouse does not do this check. - if (aggregationBits.bitLen !== committeeValidatorIndices.length) { - throw new AttestationError(GossipAction.REJECT, { - code: AttestationErrorCode.WRONG_NUMBER_OF_AGGREGATION_BITS, - }); + if (!isForkPostElectra(fork)) { + // The validity of aggregation bits are already checked above + assert.notNull(aggregationBits); + const bitIndex = aggregationBits.getSingleTrueBit(); + assert.notNull(bitIndex); + + validatorIndex = committeeValidatorIndices[bitIndex]; + // [REJECT] The number of aggregation bits matches the committee size + // -- i.e. len(attestation.aggregation_bits) == len(get_beacon_committee(state, data.slot, data.index)). + // > TODO: Is this necessary? Lighthouse does not do this check. + if (aggregationBits.bitLen !== committeeValidatorIndices.length) { + throw new AttestationError(GossipAction.REJECT, { + code: AttestationErrorCode.WRONG_NUMBER_OF_AGGREGATION_BITS, + }); + } + } else { + validatorIndex = (attestationOrCache.attestation as SingleAttestation).attesterIndex; + // [REJECT] The attester is a member of the committee -- i.e. + // `attestation.attester_index in get_beacon_committee(state, attestation.data.slot, index)`. + // If `aggregationBitsElectra` exists, that means we have already cached it. No need to check again + if (aggregationBits === null) { + // Position of the validator in its committee + const committeeValidatorIndex = committeeValidatorIndices.indexOf(validatorIndex); + if (committeeValidatorIndex === -1) { + throw new AttestationError(GossipAction.REJECT, { + code: AttestationErrorCode.ATTESTER_NOT_IN_COMMITTEE, + }); + } + + aggregationBits = BitArray.fromSingleBit(committeeValidatorIndices.length, committeeValidatorIndex); + } } // LH > verify_middle_checks @@ -442,7 +477,6 @@ async function validateAttestationNoSignatureCheck( }); } - let committeeBits: BitArray | undefined = undefined; if (attestationOrCache.cache) { // there could be up to 6% of cpu time to compute signing root if we don't clone the signature set signatureSet = createSingleSignatureSetFromComponents( @@ -451,7 +485,6 @@ async function validateAttestationNoSignatureCheck( signature ); attDataRootHex = attestationOrCache.cache.attDataRootHex; - committeeBits = attestationOrCache.cache.committeeBits; } else { signatureSet = createSingleSignatureSetFromComponents( chain.index2pubkey[validatorIndex], @@ -461,14 +494,9 @@ async function validateAttestationNoSignatureCheck( // add cached attestation data before verifying signature attDataRootHex = toRootHex(ssz.phase0.AttestationData.hashTreeRoot(attData)); - // if attestation is phase0 the committeeBits is undefined anyway - committeeBits = isElectraAttestation(attestationOrCache.attestation) - ? attestationOrCache.attestation.committeeBits.clone() - : undefined; if (attDataKey) { - chain.seenAttestationDatas.add(attSlot, attDataKey, { + chain.seenAttestationDatas.add(attSlot, committeeIndex, attDataKey, { committeeValidatorIndices, - committeeBits, committeeIndex, signingRoot: signatureSet.signingRoot, subnet: expectedSubnet, @@ -476,6 +504,7 @@ async function validateAttestationNoSignatureCheck( // root of AttestationData was already cached during getIndexedAttestationSignatureSet attDataRootHex, attestationData: attData, + aggregationBits: isForkPostElectra(fork) ? aggregationBits : null, }); } } @@ -491,12 +520,18 @@ async function validateAttestationNoSignatureCheck( ? (indexedAttestationContent as electra.IndexedAttestation) : (indexedAttestationContent as phase0.IndexedAttestation); - const attestation: Attestation = attestationOrCache.attestation ?? { + const attestationContent = attestationOrCache.attestation ?? { aggregationBits, data: attData, - committeeBits, + committeeIndex, signature, }; + + const attestation = + ForkSeq[fork] >= ForkSeq.electra + ? (attestationContent as SingleAttestation) + : (attestationContent as SingleAttestation); + return { attestation, indexedAttestation, @@ -505,6 +540,7 @@ async function validateAttestationNoSignatureCheck( signatureSet, validatorIndex, committeeIndex, + aggregationBits: isForkPostElectra(fork) ? aggregationBits : null, }; } @@ -770,38 +806,58 @@ export function computeSubnetForSlot(shuffling: EpochShuffling, slot: number, co /** * Return fork-dependent seen attestation key - * - for pre-electra, it's the AttestationData base64 - * - for electra and later, it's the AttestationData base64 + committeeBits base64 + * - for pre-electra, it's the AttestationData base64 from Attestation + * - for electra and later, it's the AttestationData base64 from SingleAttestation + * - consumers need to also pass slot + committeeIndex to get the correct SeenAttestationData */ -export function getSeenAttDataKeyFromGossipAttestation( - fork: ForkName, - attestation: GossipAttestation -): SeenAttDataKey | null { - const {attDataBase64, serializedData} = attestation; - if (isForkPostElectra(fork)) { - const committeeBits = getCommitteeBitsFromAttestationSerialized(serializedData); - return attDataBase64 && committeeBits ? attDataBase64 + committeeBits : null; - } - - // pre-electra - return attDataBase64; +export function getSeenAttDataKeyFromGossipAttestation(attestation: GossipAttestation): SeenAttDataKey | null { + // SeenAttDataKey is the same as gossip index + return attestation.attDataBase64; } /** * Extract attestation data key from SignedAggregateAndProof Uint8Array to use cached data from SeenAttestationDatas - * - for pre-electra, it's the AttestationData base64 - * - for electra and later, it's the AttestationData base64 + committeeBits base64 + * - for both electra + pre-electra, it's the AttestationData base64 + * - consumers need to also pass slot + committeeIndex to get the correct SeenAttestationData */ export function getSeenAttDataKeyFromSignedAggregateAndProof( fork: ForkName, aggregateAndProof: Uint8Array ): SeenAttDataKey | null { + return isForkPostElectra(fork) + ? getAttDataFromSignedAggregateAndProofElectra(aggregateAndProof) + : getAttDataFromSignedAggregateAndProofPhase0(aggregateAndProof); +} + +export function getCommitteeIndexFromAttestationOrBytes( + fork: ForkName, + attestationOrBytes: AttestationOrBytes +): CommitteeIndex | null { + const isGossipAttestation = attestationOrBytes.serializedData !== null; + if (isForkPostElectra(fork)) { - const attData = getAttDataFromSignedAggregateAndProofElectra(aggregateAndProof); - const committeeBits = getCommitteeBitsFromSignedAggregateAndProofElectra(aggregateAndProof); - return attData && committeeBits ? attData + committeeBits : null; + if (isGossipAttestation) { + return getCommitteeIndexFromSingleAttestationSerialized(ForkName.electra, attestationOrBytes.serializedData); + } + return (attestationOrBytes.attestation as SingleAttestation).committeeIndex; } + if (isGossipAttestation) { + return getCommitteeIndexFromSingleAttestationSerialized(ForkName.phase0, attestationOrBytes.serializedData); + } + return (attestationOrBytes.attestation as SingleAttestation).data.index; +} - // pre-electra - return getAttDataFromSignedAggregateAndProofPhase0(aggregateAndProof); +/** + * Convert pre-electra single attestation (`phase0.Attestation`) to post-electra `SingleAttestation` + */ +export function toElectraSingleAttestation( + attestation: SingleAttestation, + attesterIndex: ValidatorIndex +): SingleAttestation { + return { + committeeIndex: attestation.data.index, + attesterIndex, + data: attestation.data, + signature: attestation.signature, + }; } diff --git a/packages/beacon-node/src/network/gossip/interface.ts b/packages/beacon-node/src/network/gossip/interface.ts index be7293524ce9..a5a0aea21a67 100644 --- a/packages/beacon-node/src/network/gossip/interface.ts +++ b/packages/beacon-node/src/network/gossip/interface.ts @@ -3,11 +3,11 @@ import {Message, TopicValidatorResult} from "@libp2p/interface"; import {BeaconConfig} from "@lodestar/config"; import {ForkName} from "@lodestar/params"; import { - Attestation, LightClientFinalityUpdate, LightClientOptimisticUpdate, SignedAggregateAndProof, SignedBeaconBlock, + SingleAttestation, Slot, altair, capella, @@ -86,7 +86,7 @@ export type GossipTypeMap = { [GossipType.beacon_block]: SignedBeaconBlock; [GossipType.blob_sidecar]: deneb.BlobSidecar; [GossipType.beacon_aggregate_and_proof]: SignedAggregateAndProof; - [GossipType.beacon_attestation]: Attestation; + [GossipType.beacon_attestation]: SingleAttestation; [GossipType.voluntary_exit]: phase0.SignedVoluntaryExit; [GossipType.proposer_slashing]: phase0.ProposerSlashing; [GossipType.attester_slashing]: phase0.AttesterSlashing; @@ -101,7 +101,7 @@ export type GossipFnByType = { [GossipType.beacon_block]: (signedBlock: SignedBeaconBlock) => Promise | void; [GossipType.blob_sidecar]: (blobSidecar: deneb.BlobSidecar) => Promise | void; [GossipType.beacon_aggregate_and_proof]: (aggregateAndProof: SignedAggregateAndProof) => Promise | void; - [GossipType.beacon_attestation]: (attestation: Attestation) => Promise | void; + [GossipType.beacon_attestation]: (attestation: SingleAttestation) => Promise | void; [GossipType.voluntary_exit]: (voluntaryExit: phase0.SignedVoluntaryExit) => Promise | void; [GossipType.proposer_slashing]: (proposerSlashing: phase0.ProposerSlashing) => Promise | void; [GossipType.attester_slashing]: (attesterSlashing: phase0.AttesterSlashing) => Promise | void; diff --git a/packages/beacon-node/src/network/gossip/topic.ts b/packages/beacon-node/src/network/gossip/topic.ts index 88ef4143f8ff..87ae2a30843d 100644 --- a/packages/beacon-node/src/network/gossip/topic.ts +++ b/packages/beacon-node/src/network/gossip/topic.ts @@ -5,8 +5,9 @@ import { ForkSeq, SYNC_COMMITTEE_SUBNET_COUNT, isForkLightClient, + isForkPostElectra, } from "@lodestar/params"; -import {Attestation, ssz, sszTypesFor} from "@lodestar/types"; +import {Attestation, SingleAttestation, ssz, sszTypesFor} from "@lodestar/types"; import {GossipAction, GossipActionError, GossipErrorCode} from "../../chain/errors/gossipValidation.js"; import {DEFAULT_ENCODING} from "./constants.js"; @@ -124,7 +125,9 @@ export function sszDeserialize(topic: T, serializedData: } /** + * @deprecated * Deserialize a gossip serialized data into an Attestation object. + * No longer used post-electra. Use `sszDeserializeSingleAttestation` instead */ export function sszDeserializeAttestation(fork: ForkName, serializedData: Uint8Array): Attestation { try { @@ -134,6 +137,20 @@ export function sszDeserializeAttestation(fork: ForkName, serializedData: Uint8A } } +/** + * Deserialize a gossip seralized data into an SingleAttestation object. + */ +export function sszDeserializeSingleAttestation(fork: ForkName, serializedData: Uint8Array): SingleAttestation { + try { + if (isForkPostElectra(fork)) { + return sszTypesFor(fork).SingleAttestation.deserialize(serializedData); + } + return sszTypesFor(fork).Attestation.deserialize(serializedData) as SingleAttestation; + } catch (_e) { + throw new GossipActionError(GossipAction.REJECT, {code: GossipErrorCode.INVALID_SERIALIZED_BYTES_ERROR_CODE}); + } +} + // Parsing const gossipTopicRegex = /^\/eth2\/(\w+)\/(\w+)\/(\w+)/; diff --git a/packages/beacon-node/src/network/interface.ts b/packages/beacon-node/src/network/interface.ts index edcf35878420..c105c0b80f7a 100644 --- a/packages/beacon-node/src/network/interface.ts +++ b/packages/beacon-node/src/network/interface.ts @@ -19,6 +19,7 @@ import { LightClientOptimisticUpdate, SignedAggregateAndProof, SignedBeaconBlock, + SingleAttestation, Slot, SlotRootHex, WithBytes, @@ -73,7 +74,7 @@ export interface INetwork extends INetworkCorePublic { publishBeaconBlock(signedBlock: SignedBeaconBlock): Promise; publishBlobSidecar(blobSidecar: deneb.BlobSidecar): Promise; publishBeaconAggregateAndProof(aggregateAndProof: SignedAggregateAndProof): Promise; - publishBeaconAttestation(attestation: phase0.Attestation, subnet: number): Promise; + publishBeaconAttestation(attestation: SingleAttestation, subnet: number): Promise; publishVoluntaryExit(voluntaryExit: phase0.SignedVoluntaryExit): Promise; publishBlsToExecutionChange(blsToExecutionChange: capella.SignedBLSToExecutionChange): Promise; publishProposerSlashing(proposerSlashing: phase0.ProposerSlashing): Promise; diff --git a/packages/beacon-node/src/network/network.ts b/packages/beacon-node/src/network/network.ts index d8370fc22b5f..2faf2371e5c7 100644 --- a/packages/beacon-node/src/network/network.ts +++ b/packages/beacon-node/src/network/network.ts @@ -15,6 +15,7 @@ import { Root, SignedAggregateAndProof, SignedBeaconBlock, + SingleAttestation, SlotRootHex, WithBytes, altair, @@ -328,7 +329,7 @@ export class Network implements INetwork { ); } - async publishBeaconAttestation(attestation: phase0.Attestation, subnet: number): Promise { + async publishBeaconAttestation(attestation: SingleAttestation, subnet: number): Promise { const fork = this.config.getForkName(attestation.data.slot); return this.publishGossip( {type: GossipType.beacon_attestation, fork, subnet}, diff --git a/packages/beacon-node/src/network/processor/extractSlotRootFns.ts b/packages/beacon-node/src/network/processor/extractSlotRootFns.ts index 57a4861b4cb8..d478078d5df5 100644 --- a/packages/beacon-node/src/network/processor/extractSlotRootFns.ts +++ b/packages/beacon-node/src/network/processor/extractSlotRootFns.ts @@ -1,8 +1,9 @@ +import {ForkName} from "@lodestar/params"; import {SlotOptionalRoot, SlotRootHex} from "@lodestar/types"; import { - getBlockRootFromAttestationSerialized, + getBlockRootFromBeaconAttestationSerialized, getBlockRootFromSignedAggregateAndProofSerialized, - getSlotFromAttestationSerialized, + getSlotFromBeaconAttestationSerialized, getSlotFromBlobSidecarSerialized, getSlotFromSignedAggregateAndProofSerialized, getSlotFromSignedBeaconBlockSerialized, @@ -16,9 +17,9 @@ import {ExtractSlotRootFns} from "./types.js"; */ export function createExtractBlockSlotRootFns(): ExtractSlotRootFns { return { - [GossipType.beacon_attestation]: (data: Uint8Array): SlotRootHex | null => { - const slot = getSlotFromAttestationSerialized(data); - const root = getBlockRootFromAttestationSerialized(data); + [GossipType.beacon_attestation]: (data: Uint8Array, fork: ForkName): SlotRootHex | null => { + const slot = getSlotFromBeaconAttestationSerialized(fork, data); + const root = getBlockRootFromBeaconAttestationSerialized(fork, data); if (slot === null || root === null) { return null; diff --git a/packages/beacon-node/src/network/processor/gossipHandlers.ts b/packages/beacon-node/src/network/processor/gossipHandlers.ts index 3fb7b06e700a..63f67631ea90 100644 --- a/packages/beacon-node/src/network/processor/gossipHandlers.ts +++ b/packages/beacon-node/src/network/processor/gossipHandlers.ts @@ -1,8 +1,8 @@ import {routes} from "@lodestar/api"; import {BeaconConfig, ChainForkConfig} from "@lodestar/config"; -import {ForkName, ForkSeq} from "@lodestar/params"; +import {ForkName, ForkPostElectra, ForkPreElectra, ForkSeq, isForkPostElectra} from "@lodestar/params"; import {computeTimeAtSlot} from "@lodestar/state-transition"; -import {Root, SignedBeaconBlock, Slot, UintNum64, deneb, ssz, sszTypesFor} from "@lodestar/types"; +import {Root, SignedBeaconBlock, SingleAttestation, Slot, UintNum64, deneb, ssz, sszTypesFor} from "@lodestar/types"; import {LogLevel, Logger, prettyBytes, toRootHex} from "@lodestar/utils"; import { BlobSidecarValidation, @@ -28,6 +28,7 @@ import {validateGossipBlobSidecar} from "../../chain/validation/blobSidecar.js"; import { AggregateAndProofValidationResult, GossipAttestation, + toElectraSingleAttestation, validateGossipAggregateAndProof, validateGossipAttestationsSameAttData, validateGossipAttesterSlashing, @@ -636,14 +637,21 @@ function getBatchHandlers(modules: ValidatorFnsModules, options: GossipHandlerOp results.push(null); // Handler - const {indexedAttestation, attDataRootHex, attestation, committeeIndex} = validationResult.result; + const {indexedAttestation, attDataRootHex, attestation, committeeIndex, aggregationBits} = + validationResult.result; metrics?.registerGossipUnaggregatedAttestation(gossipHandlerParams[i].seenTimestampSec, indexedAttestation); try { // Node may be subscribe to extra subnets (long-lived random subnets). For those, validate the messages // but don't add to attestation pool, to save CPU and RAM if (aggregatorTracker.shouldAggregate(subnet, indexedAttestation.data.slot)) { - const insertOutcome = chain.attestationPool.add(committeeIndex, attestation, attDataRootHex); + // TODO: modify after we change attestationPool due to SingleAttestation + const insertOutcome = chain.attestationPool.add( + committeeIndex, + attestation, + attDataRootHex, + aggregationBits + ); metrics?.opPool.attestationPoolInsertOutcome.inc({insertOutcome}); } } catch (e) { @@ -658,7 +666,21 @@ function getBatchHandlers(modules: ValidatorFnsModules, options: GossipHandlerOp } } - chain.emitter.emit(routes.events.EventType.attestation, attestation); + if (isForkPostElectra(fork)) { + chain.emitter.emit( + routes.events.EventType.singleAttestation, + attestation as SingleAttestation + ); + } else { + chain.emitter.emit(routes.events.EventType.attestation, attestation as SingleAttestation); + chain.emitter.emit( + routes.events.EventType.singleAttestation, + toElectraSingleAttestation( + attestation as SingleAttestation, + indexedAttestation.attestingIndices[0] + ) + ); + } } if (batchableBls) { diff --git a/packages/beacon-node/src/network/processor/gossipQueues/index.ts b/packages/beacon-node/src/network/processor/gossipQueues/index.ts index b76ebe2d875d..4958fd8a50e6 100644 --- a/packages/beacon-node/src/network/processor/gossipQueues/index.ts +++ b/packages/beacon-node/src/network/processor/gossipQueues/index.ts @@ -1,5 +1,5 @@ import {mapValues} from "@lodestar/utils"; -import {getGossipAttestationIndex} from "../../../util/sszBytes.js"; +import {getBeaconAttestationGossipIndex} from "../../../util/sszBytes.js"; import {BatchGossipType, GossipType, SequentialGossipType} from "../../gossip/interface.js"; import {PendingGossipsubMessage} from "../types.js"; import {IndexedGossipQueueMinSize} from "./indexed.js"; @@ -72,8 +72,7 @@ const indexedGossipQueueOpts: { // this topic may cause node to be overload and drop 100% of lower priority queues maxLength: 24576, indexFn: (item: PendingGossipsubMessage) => { - // Note indexFn is fork agnostic despite changes introduced in Electra - return getGossipAttestationIndex(item.msg.data); + return getBeaconAttestationGossipIndex(item.topic.fork, item.msg.data); }, minChunkSize: MIN_SIGNATURE_SETS_TO_BATCH_VERIFY, maxChunkSize: MAX_GOSSIP_ATTESTATION_BATCH_SIZE, diff --git a/packages/beacon-node/src/network/processor/index.ts b/packages/beacon-node/src/network/processor/index.ts index 4bfc4263adc8..2b471034aee5 100644 --- a/packages/beacon-node/src/network/processor/index.ts +++ b/packages/beacon-node/src/network/processor/index.ts @@ -247,7 +247,7 @@ export class NetworkProcessor { const extractBlockSlotRootFn = this.extractBlockSlotRootFns[topicType]; // check block root of Attestation and SignedAggregateAndProof messages if (extractBlockSlotRootFn) { - const slotRoot = extractBlockSlotRootFn(message.msg.data); + const slotRoot = extractBlockSlotRootFn(message.msg.data, message.topic.fork); // if slotRoot is null, it means the msg.data is invalid // in that case message will be rejected when deserializing data in later phase (gossipValidatorFn) if (slotRoot) { diff --git a/packages/beacon-node/src/network/processor/types.ts b/packages/beacon-node/src/network/processor/types.ts index ec78116bc766..c059f77eb727 100644 --- a/packages/beacon-node/src/network/processor/types.ts +++ b/packages/beacon-node/src/network/processor/types.ts @@ -1,4 +1,5 @@ import {Message} from "@libp2p/interface"; +import {ForkName} from "@lodestar/params"; import {Slot, SlotOptionalRoot} from "@lodestar/types"; import {PeerIdStr} from "../../util/peerId.js"; import {GossipTopic, GossipType} from "../gossip/index.js"; @@ -22,5 +23,5 @@ export type PendingGossipsubMessage = { }; export type ExtractSlotRootFns = { - [K in GossipType]?: (data: Uint8Array) => SlotOptionalRoot | null; + [K in GossipType]?: (data: Uint8Array, forkName: ForkName) => SlotOptionalRoot | null; }; diff --git a/packages/beacon-node/src/util/sszBytes.ts b/packages/beacon-node/src/util/sszBytes.ts index cb80d5d1bb4b..8dca248b5830 100644 --- a/packages/beacon-node/src/util/sszBytes.ts +++ b/packages/beacon-node/src/util/sszBytes.ts @@ -5,8 +5,9 @@ import { ForkName, ForkSeq, MAX_COMMITTEES_PER_SLOT, + isForkPostElectra, } from "@lodestar/params"; -import {BLSSignature, RootHex, Slot} from "@lodestar/types"; +import {BLSSignature, CommitteeIndex, RootHex, Slot} from "@lodestar/types"; export type BlockRootHex = RootHex; // pre-electra, AttestationData is used to cache attestations @@ -26,6 +27,12 @@ export type CommitteeBitsBase64 = string; // data: AttestationData - target data - 128 // signature: BLSSignature - 96 // committee_bits: BitVector[MAX_COMMITTEES_PER_SLOT] +// electra +// class SingleAttestation(Container): +// committeeIndex: CommitteeIndex - data 8 +// attesterIndex: ValidatorIndex - data 8 +// data: AttestationData - data 128 +// signature: BLSSignature - data 96 // // for all forks // class AttestationData(Container): 128 bytes fixed size @@ -39,10 +46,17 @@ const VARIABLE_FIELD_OFFSET = 4; const ATTESTATION_BEACON_BLOCK_ROOT_OFFSET = VARIABLE_FIELD_OFFSET + 8 + 8; const ROOT_SIZE = 32; const SLOT_SIZE = 8; +const COMMITTEE_INDEX_SIZE = 8; const ATTESTATION_DATA_SIZE = 128; // MAX_COMMITTEES_PER_SLOT is in bit, need to convert to byte const COMMITTEE_BITS_SIZE = Math.max(Math.ceil(MAX_COMMITTEES_PER_SLOT / 8), 1); const SIGNATURE_SIZE = 96; +const SINGLE_ATTESTATION_ATTDATA_OFFSET = 8 + 8; +const SINGLE_ATTESTATION_SLOT_OFFSET = SINGLE_ATTESTATION_ATTDATA_OFFSET; +const SINGLE_ATTESTATION_COMMITTEE_INDEX_OFFSET = 0; +const SINGLE_ATTESTATION_BEACON_BLOCK_ROOT_OFFSET = SINGLE_ATTESTATION_ATTDATA_OFFSET + 8 + 8; +const SINGLE_ATTESTATION_SIGNATURE_OFFSET = SINGLE_ATTESTATION_ATTDATA_OFFSET + ATTESTATION_DATA_SIZE; +const SINGLE_ATTESTATION_SIZE = SINGLE_ATTESTATION_SIGNATURE_OFFSET + SIGNATURE_SIZE; // shared Buffers to convert bytes to hex/base64 const blockRootBuf = Buffer.alloc(ROOT_SIZE); @@ -91,21 +105,43 @@ export function getAttDataFromAttestationSerialized(data: Uint8Array): AttDataBa } /** - * Alias of `getAttDataFromAttestationSerialized` specifically for batch handling indexing in gossip queue + * Extract AttDataBase64 from `beacon_attestation` gossip message serialized bytes. + * This is used for GossipQueue. */ -export function getGossipAttestationIndex(data: Uint8Array): AttDataBase64 | null { - return getAttDataFromAttestationSerialized(data); +export function getBeaconAttestationGossipIndex(fork: ForkName, data: Uint8Array): AttDataBase64 | null { + const forkSeq = ForkSeq[fork]; + return forkSeq >= ForkSeq.electra + ? getAttDataFromSingleAttestationSerialized(data) + : getAttDataFromAttestationSerialized(data); +} + +/** + * Extract slot from `beacon_attestation` gossip message serialized bytes. + */ +export function getSlotFromBeaconAttestationSerialized(fork: ForkName, data: Uint8Array): Slot | null { + const forkSeq = ForkSeq[fork]; + return forkSeq >= ForkSeq.electra + ? getSlotFromSingleAttestationSerialized(data) + : getSlotFromAttestationSerialized(data); +} + +/** + * Extract block root from `beacon_attestation` gossip message serialized bytes. + */ +export function getBlockRootFromBeaconAttestationSerialized(fork: ForkName, data: Uint8Array): BlockRootHex | null { + const forkSeq = ForkSeq[fork]; + return forkSeq >= ForkSeq.electra + ? getBlockRootFromSingleAttestationSerialized(data) + : getBlockRootFromAttestationSerialized(data); } /** * Extract aggregation bits from attestation serialized bytes. * Return null if data is not long enough to extract aggregation bits. + * Pre-electra attestation only */ -export function getAggregationBitsFromAttestationSerialized(fork: ForkName, data: Uint8Array): BitArray | null { - const aggregationBitsStartIndex = - ForkSeq[fork] >= ForkSeq.electra - ? VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE + SIGNATURE_SIZE + COMMITTEE_BITS_SIZE - : VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE + SIGNATURE_SIZE; +export function getAggregationBitsFromAttestationSerialized(data: Uint8Array): BitArray | null { + const aggregationBitsStartIndex = VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE + SIGNATURE_SIZE; if (data.length < aggregationBitsStartIndex) { return null; @@ -130,18 +166,82 @@ export function getSignatureFromAttestationSerialized(data: Uint8Array): BLSSign } /** - * Extract committee bits from Electra attestation serialized bytes. - * Return null if data is not long enough to extract committee bits. + * Extract slot from SingleAttestation serialized bytes. + * Return null if data is not long enough to extract slot. */ -export function getCommitteeBitsFromAttestationSerialized(data: Uint8Array): CommitteeBitsBase64 | null { - const committeeBitsStartIndex = VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE + SIGNATURE_SIZE; +export function getSlotFromSingleAttestationSerialized(data: Uint8Array): Slot | null { + if (data.length !== SINGLE_ATTESTATION_SIZE) { + return null; + } - if (data.length < committeeBitsStartIndex + COMMITTEE_BITS_SIZE) { + return getSlotFromOffset(data, SINGLE_ATTESTATION_SLOT_OFFSET); +} + +/** + * Extract committee index from SingleAttestation serialized bytes. + * Return null if data is not long enough to extract slot. + * TODO Electra: Rename getSlotFromOffset to reflect generic usage + */ +export function getCommitteeIndexFromSingleAttestationSerialized( + fork: ForkName, + data: Uint8Array +): CommitteeIndex | null { + if (isForkPostElectra(fork)) { + if (data.length !== SINGLE_ATTESTATION_SIZE) { + return null; + } + + return getSlotFromOffset(data, SINGLE_ATTESTATION_COMMITTEE_INDEX_OFFSET); + } + + if (data.length < VARIABLE_FIELD_OFFSET + SLOT_SIZE + COMMITTEE_INDEX_SIZE) { return null; } - committeeBitsDataBuf.set(data.subarray(committeeBitsStartIndex, committeeBitsStartIndex + COMMITTEE_BITS_SIZE)); - return committeeBitsDataBuf.toString("base64"); + return getSlotFromOffset(data, VARIABLE_FIELD_OFFSET + SLOT_SIZE); +} + +/** + * Extract block root from SingleAttestation serialized bytes. + * Return null if data is not long enough to extract block root. + */ +export function getBlockRootFromSingleAttestationSerialized(data: Uint8Array): BlockRootHex | null { + if (data.length !== SINGLE_ATTESTATION_SIZE) { + return null; + } + + blockRootBuf.set( + data.subarray(SINGLE_ATTESTATION_BEACON_BLOCK_ROOT_OFFSET, SINGLE_ATTESTATION_BEACON_BLOCK_ROOT_OFFSET + ROOT_SIZE) + ); + return `0x${blockRootBuf.toString("hex")}`; +} + +/** + * Extract attestation data base64 from SingleAttestation serialized bytes. + * Return null if data is not long enough to extract attestation data. + */ +export function getAttDataFromSingleAttestationSerialized(data: Uint8Array): AttDataBase64 | null { + if (data.length !== SINGLE_ATTESTATION_SIZE) { + return null; + } + + // base64 is a bit efficient than hex + attDataBuf.set( + data.subarray(SINGLE_ATTESTATION_ATTDATA_OFFSET, SINGLE_ATTESTATION_ATTDATA_OFFSET + ATTESTATION_DATA_SIZE) + ); + return attDataBuf.toString("base64"); +} + +/** + * Extract signature from SingleAttestation serialized bytes. + * Return null if data is not long enough to extract signature. + */ +export function getSignatureFromSingleAttestationSerialized(data: Uint8Array): BLSSignature | null { + if (data.length !== SINGLE_ATTESTATION_SIZE) { + return null; + } + + return data.subarray(SINGLE_ATTESTATION_SIGNATURE_OFFSET, SINGLE_ATTESTATION_SIGNATURE_OFFSET + SIGNATURE_SIZE); } // diff --git a/packages/beacon-node/test/memory/seenAttestationData.ts b/packages/beacon-node/test/memory/seenAttestationData.ts index 44a82dcd5841..53aa519f40f7 100644 --- a/packages/beacon-node/test/memory/seenAttestationData.ts +++ b/packages/beacon-node/test/memory/seenAttestationData.ts @@ -35,7 +35,7 @@ function getRandomSeenAttestationDatas(n: number): SeenAttestationDatas { attDataRootHex: toHexString(crypto.randomBytes(32)), subnet: i, } as unknown as AttestationDataCacheEntry; - seenAttestationDatas.add(slot, key, attDataCacheEntry); + seenAttestationDatas.add(slot, i, key, attDataCacheEntry); } return seenAttestationDatas; } diff --git a/packages/beacon-node/test/unit/chain/opPools/attestationPool.test.ts b/packages/beacon-node/test/unit/chain/opPools/attestationPool.test.ts index fd22f9a7c6a5..2d2411b7ca73 100644 --- a/packages/beacon-node/test/unit/chain/opPools/attestationPool.test.ts +++ b/packages/beacon-node/test/unit/chain/opPools/attestationPool.test.ts @@ -1,7 +1,7 @@ -import {fromHexString, toHexString} from "@chainsafe/ssz"; +import {BitArray, fromHexString, toHexString} from "@chainsafe/ssz"; import {createChainForkConfig, defaultChainConfig} from "@lodestar/config"; -import {GENESIS_SLOT, SLOTS_PER_EPOCH} from "@lodestar/params"; -import {ssz} from "@lodestar/types"; +import {GENESIS_SLOT, MAX_COMMITTEES_PER_SLOT, SLOTS_PER_EPOCH} from "@lodestar/params"; +import {electra, phase0, ssz} from "@lodestar/types"; import {beforeEach, describe, expect, it, vi} from "vitest"; import {AttestationPool} from "../../../../src/chain/opPools/attestationPool.js"; import {InsertOutcome} from "../../../../src/chain/opPools/types.js"; @@ -27,17 +27,26 @@ describe("AttestationPool", () => { const cutOffSecFromSlot = (2 / 3) * config.SECONDS_PER_SLOT; // Mock attestations - const electraAttestationData = { + const electraAttestationData: phase0.AttestationData = { ...ssz.phase0.AttestationData.defaultValue(), slot: config.ELECTRA_FORK_EPOCH * SLOTS_PER_EPOCH, }; - const electraAttestation = { + const electraSingleAttestation: electra.SingleAttestation = { + ...ssz.electra.SingleAttestation.defaultValue(), + data: electraAttestationData, + signature: validSignature, + }; + const electraAttestation: electra.Attestation = { ...ssz.electra.Attestation.defaultValue(), + committeeBits: BitArray.fromSingleBit(MAX_COMMITTEES_PER_SLOT, electraSingleAttestation.committeeIndex), data: electraAttestationData, signature: validSignature, }; - const phase0AttestationData = {...ssz.phase0.AttestationData.defaultValue(), slot: GENESIS_SLOT}; - const phase0Attestation = { + const phase0AttestationData: phase0.AttestationData = { + ...ssz.phase0.AttestationData.defaultValue(), + slot: GENESIS_SLOT, + }; + const phase0Attestation: phase0.Attestation = { ...ssz.phase0.Attestation.defaultValue(), data: phase0AttestationData, signature: validSignature, @@ -51,8 +60,9 @@ describe("AttestationPool", () => { it("add correct electra attestation", () => { const committeeIndex = 0; - const attDataRootHex = toHexString(ssz.phase0.AttestationData.hashTreeRoot(electraAttestation.data)); - const outcome = pool.add(committeeIndex, electraAttestation, attDataRootHex); + const aggregationBits = ssz.electra.Attestation.fields.aggregationBits.defaultValue(); + const attDataRootHex = toHexString(ssz.phase0.AttestationData.hashTreeRoot(electraSingleAttestation.data)); + const outcome = pool.add(committeeIndex, electraSingleAttestation, attDataRootHex, aggregationBits); expect(outcome).equal(InsertOutcome.NewData); expect(pool.getAggregate(electraAttestationData.slot, committeeIndex, attDataRootHex)).toEqual(electraAttestation); @@ -61,7 +71,7 @@ describe("AttestationPool", () => { it("add correct phase0 attestation", () => { const committeeIndex = null; const attDataRootHex = toHexString(ssz.phase0.AttestationData.hashTreeRoot(phase0Attestation.data)); - const outcome = pool.add(committeeIndex, phase0Attestation, attDataRootHex); + const outcome = pool.add(committeeIndex, phase0Attestation, attDataRootHex, null); expect(outcome).equal(InsertOutcome.NewData); expect(pool.getAggregate(phase0AttestationData.slot, committeeIndex, attDataRootHex)).toEqual(phase0Attestation); @@ -72,16 +82,18 @@ describe("AttestationPool", () => { it("add electra attestation without committee index", () => { const committeeIndex = null; - const attDataRootHex = toHexString(ssz.phase0.AttestationData.hashTreeRoot(electraAttestation.data)); + const aggregationBits = ssz.electra.Attestation.fields.aggregationBits.defaultValue(); + const attDataRootHex = toHexString(ssz.phase0.AttestationData.hashTreeRoot(electraSingleAttestation.data)); - expect(() => pool.add(committeeIndex, electraAttestation, attDataRootHex)).toThrow(); + expect(() => pool.add(committeeIndex, electraSingleAttestation, attDataRootHex, aggregationBits)).toThrow(); expect(pool.getAggregate(electraAttestationData.slot, committeeIndex, attDataRootHex)).toBeNull(); }); it("add phase0 attestation with committee index", () => { const committeeIndex = 0; + const aggregationBits = ssz.electra.Attestation.fields.aggregationBits.defaultValue(); const attDataRootHex = toHexString(ssz.phase0.AttestationData.hashTreeRoot(phase0Attestation.data)); - const outcome = pool.add(committeeIndex, phase0Attestation, attDataRootHex); + const outcome = pool.add(committeeIndex, phase0Attestation, attDataRootHex, aggregationBits); expect(outcome).equal(InsertOutcome.NewData); expect(pool.getAggregate(phase0AttestationData.slot, committeeIndex, attDataRootHex)).toEqual(phase0Attestation); @@ -92,14 +104,15 @@ describe("AttestationPool", () => { it("add electra attestation with phase0 slot", () => { const electraAttestationDataWithPhase0Slot = {...ssz.phase0.AttestationData.defaultValue(), slot: GENESIS_SLOT}; - const attestation = { - ...ssz.electra.Attestation.defaultValue(), + const aggregationBits = ssz.electra.Attestation.fields.aggregationBits.defaultValue(); + const singleAttestation = { + ...ssz.electra.SingleAttestation.defaultValue(), data: electraAttestationDataWithPhase0Slot, signature: validSignature, }; const attDataRootHex = toHexString(ssz.phase0.AttestationData.hashTreeRoot(electraAttestationDataWithPhase0Slot)); - expect(() => pool.add(0, attestation, attDataRootHex)).toThrow(); + expect(() => pool.add(0, singleAttestation, attDataRootHex, aggregationBits)).toThrow(); }); it("add phase0 attestation with electra slot", () => { @@ -114,6 +127,6 @@ describe("AttestationPool", () => { }; const attDataRootHex = toHexString(ssz.phase0.AttestationData.hashTreeRoot(phase0AttestationDataWithElectraSlot)); - expect(() => pool.add(0, attestation, attDataRootHex)).toThrow(); + expect(() => pool.add(0, attestation, attDataRootHex, null)).toThrow(); }); }); diff --git a/packages/beacon-node/test/unit/chain/seenCache/seenAttestationData.test.ts b/packages/beacon-node/test/unit/chain/seenCache/seenAttestationData.test.ts index ee5cb94ae959..aaac18b6f33a 100644 --- a/packages/beacon-node/test/unit/chain/seenCache/seenAttestationData.test.ts +++ b/packages/beacon-node/test/unit/chain/seenCache/seenAttestationData.test.ts @@ -1,6 +1,10 @@ import {beforeEach, describe, expect, it} from "vitest"; import {InsertOutcome} from "../../../../src/chain/opPools/types.js"; -import {AttestationDataCacheEntry, SeenAttestationDatas} from "../../../../src/chain/seenCache/seenAttestationData.js"; +import { + AttestationDataCacheEntry, + PRE_ELECTRA_SINGLE_ATTESTATION_COMMITTEE_INDEX, + SeenAttestationDatas, +} from "../../../../src/chain/seenCache/seenAttestationData.js"; // Compare this snippet from packages/beacon-node/src/chain/seenCache/seenAttestationData.ts: describe("SeenAttestationDatas", () => { @@ -11,9 +15,15 @@ describe("SeenAttestationDatas", () => { beforeEach(() => { cache = new SeenAttestationDatas(null, 1, 2); cache.onSlot(100); - cache.add(99, "99a", {attDataRootHex: "99a"} as AttestationDataCacheEntry); - cache.add(99, "99b", {attDataRootHex: "99b"} as AttestationDataCacheEntry); - cache.add(100, "100a", {attDataRootHex: "100a"} as AttestationDataCacheEntry); + cache.add(99, PRE_ELECTRA_SINGLE_ATTESTATION_COMMITTEE_INDEX, "99a", { + attDataRootHex: "99a", + } as AttestationDataCacheEntry); + cache.add(99, PRE_ELECTRA_SINGLE_ATTESTATION_COMMITTEE_INDEX, "99b", { + attDataRootHex: "99b", + } as AttestationDataCacheEntry); + cache.add(100, PRE_ELECTRA_SINGLE_ATTESTATION_COMMITTEE_INDEX, "100a", { + attDataRootHex: "100a", + } as AttestationDataCacheEntry); }); const addTestCases: {slot: number; attDataBase64: string; expected: InsertOutcome}[] = [ @@ -26,7 +36,7 @@ describe("SeenAttestationDatas", () => { for (const testCase of addTestCases) { it(`add slot ${testCase.slot} data ${testCase.attDataBase64} should return ${testCase.expected}`, () => { expect( - cache.add(testCase.slot, testCase.attDataBase64, { + cache.add(testCase.slot, PRE_ELECTRA_SINGLE_ATTESTATION_COMMITTEE_INDEX, testCase.attDataBase64, { attDataRootHex: testCase.attDataBase64, } as AttestationDataCacheEntry) ).toBe(testCase.expected); @@ -44,9 +54,13 @@ describe("SeenAttestationDatas", () => { testCase.expectedNull ? "null" : "not null" }`, () => { if (testCase.expectedNull) { - expect(cache.get(testCase.slot, testCase.attDataBase64)).toBeNull(); + expect( + cache.get(testCase.slot, PRE_ELECTRA_SINGLE_ATTESTATION_COMMITTEE_INDEX, testCase.attDataBase64) + ).toBeNull(); } else { - expect(cache.get(testCase.slot, testCase.attDataBase64)).not.toBeNull(); + expect( + cache.get(testCase.slot, PRE_ELECTRA_SINGLE_ATTESTATION_COMMITTEE_INDEX, testCase.attDataBase64) + ).not.toBeNull(); } }); } diff --git a/packages/beacon-node/test/unit/chain/validation/attestation/validateAttestation.test.ts b/packages/beacon-node/test/unit/chain/validation/attestation/validateAttestation.test.ts index 1ffbf7696111..8c6f789e581e 100644 --- a/packages/beacon-node/test/unit/chain/validation/attestation/validateAttestation.test.ts +++ b/packages/beacon-node/test/unit/chain/validation/attestation/validateAttestation.test.ts @@ -341,7 +341,7 @@ describe("getSeenAttDataKey", () => { signedAggregateAndProof.message.aggregate.data.beaconBlockRoot = blockRoot; const aggregateAndProofBytes = ssz.phase0.SignedAggregateAndProof.serialize(signedAggregateAndProof); - expect(getSeenAttDataKeyFromGossipAttestation(ForkName.phase0, gossipAttestation)).toEqual( + expect(getSeenAttDataKeyFromGossipAttestation(gossipAttestation)).toEqual( getSeenAttDataKeyFromSignedAggregateAndProof(ForkName.phase0, aggregateAndProofBytes) ); }); @@ -363,7 +363,7 @@ describe("getSeenAttDataKey", () => { signedAggregateAndProof.message.aggregate.data.beaconBlockRoot = blockRoot; const aggregateAndProofBytes = ssz.electra.SignedAggregateAndProof.serialize(signedAggregateAndProof); - expect(getSeenAttDataKeyFromGossipAttestation(ForkName.electra, gossipAttestation)).toEqual( + expect(getSeenAttDataKeyFromGossipAttestation(gossipAttestation)).toEqual( getSeenAttDataKeyFromSignedAggregateAndProof(ForkName.electra, aggregateAndProofBytes) ); }); diff --git a/packages/beacon-node/test/unit/util/sszBytes.test.ts b/packages/beacon-node/test/unit/util/sszBytes.test.ts index 8fd6011e6255..ef6b713dccdb 100644 --- a/packages/beacon-node/test/unit/util/sszBytes.test.ts +++ b/packages/beacon-node/test/unit/util/sszBytes.test.ts @@ -1,28 +1,44 @@ import {BitArray} from "@chainsafe/ssz"; import {ForkName, MAX_COMMITTEES_PER_SLOT} from "@lodestar/params"; -import {Epoch, RootHex, Slot, deneb, electra, isElectraAttestation, phase0, ssz} from "@lodestar/types"; -import {fromHex, toHex} from "@lodestar/utils"; +import { + CommitteeIndex, + Epoch, + RootHex, + SingleAttestation, + Slot, + deneb, + electra, + isElectraSingleAttestation, + phase0, + ssz, + sszTypesFor, +} from "@lodestar/types"; +import {fromHex, toHex, toRootHex} from "@lodestar/utils"; import {describe, expect, it} from "vitest"; import { getAggregationBitsFromAttestationSerialized, getAttDataFromAttestationSerialized, getAttDataFromSignedAggregateAndProofElectra, getAttDataFromSignedAggregateAndProofPhase0, + getAttDataFromSingleAttestationSerialized, getBlockRootFromAttestationSerialized, getBlockRootFromSignedAggregateAndProofSerialized, - getCommitteeBitsFromAttestationSerialized, + getBlockRootFromSingleAttestationSerialized, getCommitteeBitsFromSignedAggregateAndProofElectra, + getCommitteeIndexFromSingleAttestationSerialized, getSignatureFromAttestationSerialized, + getSignatureFromSingleAttestationSerialized, getSlotFromAttestationSerialized, getSlotFromBlobSidecarSerialized, getSlotFromSignedAggregateAndProofSerialized, getSlotFromSignedBeaconBlockSerialized, + getSlotFromSingleAttestationSerialized, } from "../../../src/util/sszBytes.js"; -describe("attestation SSZ serialized picking", () => { - const testCases: (phase0.Attestation | electra.Attestation)[] = [ +describe("SinlgeAttestation SSZ serialized picking", () => { + const testCases: SingleAttestation[] = [ ssz.phase0.Attestation.defaultValue(), - attestationFromValues( + phase0SingleAttestationFromValues( 4_000_000, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", 200_00, @@ -30,46 +46,51 @@ describe("attestation SSZ serialized picking", () => { ), ssz.electra.Attestation.defaultValue(), { - ...attestationFromValues( + ...electraSingleAttestationFromValues( 4_000_000, + 127, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", 200_00, "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeffffffffffffffffffffffffffffffff" ), - committeeBits: BitArray.fromSingleBit(MAX_COMMITTEES_PER_SLOT, 3), }, ]; for (const [i, attestation] of testCases.entries()) { it(`attestation ${i}`, () => { - const isElectra = isElectraAttestation(attestation); + const isElectra = isElectraSingleAttestation(attestation); const bytes = isElectra - ? ssz.electra.Attestation.serialize(attestation) + ? sszTypesFor(ForkName.electra, "SingleAttestation").serialize(attestation) : ssz.phase0.Attestation.serialize(attestation); - expect(getSlotFromAttestationSerialized(bytes)).toBe(attestation.data.slot); - expect(getBlockRootFromAttestationSerialized(bytes)).toBe(toHex(attestation.data.beaconBlockRoot)); - if (isElectra) { - expect(getAggregationBitsFromAttestationSerialized(ForkName.electra, bytes)?.toBoolArray()).toEqual( - attestation.aggregationBits.toBoolArray() + expect(getSlotFromSingleAttestationSerialized(bytes)).toEqual(attestation.data.slot); + expect(getCommitteeIndexFromSingleAttestationSerialized(ForkName.electra, bytes)).toEqual( + attestation.committeeIndex ); - expect(getCommitteeBitsFromAttestationSerialized(bytes)).toEqual( - Buffer.from(attestation.committeeBits.uint8Array).toString("base64") + expect(getBlockRootFromSingleAttestationSerialized(bytes)).toEqual(toRootHex(attestation.data.beaconBlockRoot)); + // base64, not hex + expect(getAttDataFromSingleAttestationSerialized(bytes)).toEqual( + Buffer.from(ssz.phase0.AttestationData.serialize(attestation.data)).toString("base64") ); - expect(getSignatureFromAttestationSerialized(bytes)).toEqual(attestation.signature); + expect(getSignatureFromSingleAttestationSerialized(bytes)).toEqual(attestation.signature); } else { - expect(getAggregationBitsFromAttestationSerialized(ForkName.phase0, bytes)?.toBoolArray()).toEqual( + expect(getSlotFromAttestationSerialized(bytes)).toBe(attestation.data.slot); + expect(getCommitteeIndexFromSingleAttestationSerialized(ForkName.phase0, bytes)).toEqual( + attestation.data.index + ); + expect(getBlockRootFromAttestationSerialized(bytes)).toBe(toRootHex(attestation.data.beaconBlockRoot)); + expect(getAggregationBitsFromAttestationSerialized(bytes)?.toBoolArray()).toEqual( attestation.aggregationBits.toBoolArray() ); + const attDataBase64 = ssz.phase0.AttestationData.serialize(attestation.data); + expect(getAttDataFromAttestationSerialized(bytes)).toBe(Buffer.from(attDataBase64).toString("base64")); expect(getSignatureFromAttestationSerialized(bytes)).toEqual(attestation.signature); } - - const attDataBase64 = ssz.phase0.AttestationData.serialize(attestation.data); - expect(getAttDataFromAttestationSerialized(bytes)).toBe(Buffer.from(attDataBase64).toString("base64")); }); } + // negative tests for phase0 it("getSlotFromAttestationSerialized - invalid data", () => { const invalidSlotDataSizes = [0, 4, 11]; for (const size of invalidSlotDataSizes) { @@ -94,8 +115,8 @@ describe("attestation SSZ serialized picking", () => { it("getAggregationBitsFromAttestationSerialized - invalid data", () => { const invalidAggregationBitsDataSizes = [0, 4, 100, 128, 227]; for (const size of invalidAggregationBitsDataSizes) { - expect(getAggregationBitsFromAttestationSerialized(ForkName.phase0, Buffer.alloc(size))).toBeNull(); - expect(getAggregationBitsFromAttestationSerialized(ForkName.electra, Buffer.alloc(size))).toBeNull(); + expect(getAggregationBitsFromAttestationSerialized(Buffer.alloc(size))).toBeNull(); + expect(getAggregationBitsFromAttestationSerialized(Buffer.alloc(size))).toBeNull(); } }); @@ -106,6 +127,42 @@ describe("attestation SSZ serialized picking", () => { expect(getSignatureFromAttestationSerialized(Buffer.alloc(size))).toBeNull(); } }); + + // negative tests for electra + it("getSlotFromSingleAttestationSerialized - invalid data", () => { + const invalidSlotDataSizes = [0, 4, 11]; + for (const size of invalidSlotDataSizes) { + expect(getSlotFromSingleAttestationSerialized(Buffer.alloc(size))).toBeNull(); + } + }); + + it("getCommitteeIndexFromSingleAttestationSerialized - invalid data", () => { + const invalidCommitteeIndexDataSizes = [0, 4, 11]; + for (const size of invalidCommitteeIndexDataSizes) { + expect(getCommitteeIndexFromSingleAttestationSerialized(ForkName.electra, Buffer.alloc(size))).toBeNull(); + } + }); + + it("getBlockRootFromSingleAttestationSerialized - invalid data", () => { + const invalidBlockRootDataSizes = [0, 4, 20, 49]; + for (const size of invalidBlockRootDataSizes) { + expect(getBlockRootFromSingleAttestationSerialized(Buffer.alloc(size))).toBeNull(); + } + }); + + it("getAttDataFromSingleAttestationSerialized - invalid data", () => { + const invalidAttDataBase64DataSizes = [0, 4, 100, 128, 131]; + for (const size of invalidAttDataBase64DataSizes) { + expect(getAttDataFromSingleAttestationSerialized(Buffer.alloc(size))).toBeNull(); + } + }); + + it("getSignatureFromSingleAttestationSerialized - invalid data", () => { + const invalidSignatureDataSizes = [0, 4, 100, 128, 227]; + for (const size of invalidSignatureDataSizes) { + expect(getSignatureFromSingleAttestationSerialized(Buffer.alloc(size))).toBeNull(); + } + }); }); describe("phase0 SignedAggregateAndProof SSZ serialized picking", () => { @@ -258,7 +315,7 @@ describe("BlobSidecar SSZ serialized picking", () => { }); }); -function attestationFromValues( +function phase0SingleAttestationFromValues( slot: Slot, blockRoot: RootHex, targetEpoch: Epoch, @@ -272,6 +329,22 @@ function attestationFromValues( return attestation; } +function electraSingleAttestationFromValues( + slot: Slot, + committeeIndex: CommitteeIndex, + blockRoot: RootHex, + targetEpoch: Epoch, + targetRoot: RootHex +): electra.SingleAttestation { + const attestation = ssz.electra.SingleAttestation.defaultValue(); + attestation.data.slot = slot; + attestation.data.beaconBlockRoot = fromHex(blockRoot); + attestation.data.target.epoch = targetEpoch; + attestation.data.target.root = fromHex(targetRoot); + attestation.committeeIndex = committeeIndex; + return attestation; +} + function phase0SignedAggregateAndProofFromValues( slot: Slot, blockRoot: RootHex, diff --git a/packages/types/src/electra/sszTypes.ts b/packages/types/src/electra/sszTypes.ts index f6b6c745803e..238e6cc29c76 100644 --- a/packages/types/src/electra/sszTypes.ts +++ b/packages/types/src/electra/sszTypes.ts @@ -44,6 +44,7 @@ const { UintBn64, ExecutionAddress, ValidatorIndex, + CommitteeIndex, } = primitiveSsz; export const AggregationBits = new BitListType(MAX_VALIDATORS_PER_COMMITTEE * MAX_COMMITTEES_PER_SLOT); @@ -67,6 +68,17 @@ export const Attestation = new ContainerType( {typeName: "Attestation", jsonCase: "eth2"} ); +// New type in ELECTRA +export const SingleAttestation = new ContainerType( + { + committeeIndex: CommitteeIndex, + attesterIndex: ValidatorIndex, + data: phase0Ssz.AttestationData, + signature: BLSSignature, + }, + {typeName: "SingleAttestation", jsonCase: "eth2"} +); + export const IndexedAttestation = new ContainerType( { attestingIndices: AttestingIndices, // Modified in ELECTRA diff --git a/packages/types/src/electra/types.ts b/packages/types/src/electra/types.ts index 691de409ed91..ee7585692d47 100644 --- a/packages/types/src/electra/types.ts +++ b/packages/types/src/electra/types.ts @@ -2,6 +2,7 @@ import {ValueOf} from "@chainsafe/ssz"; import * as ssz from "./sszTypes.js"; export type Attestation = ValueOf; +export type SingleAttestation = ValueOf; export type IndexedAttestation = ValueOf; export type IndexedAttestationBigint = ValueOf; export type AttesterSlashing = ValueOf; diff --git a/packages/types/src/types.ts b/packages/types/src/types.ts index badb1f1f2333..ce54c2f7ae63 100644 --- a/packages/types/src/types.ts +++ b/packages/types/src/types.ts @@ -56,6 +56,7 @@ type TypesByFork = { BeaconState: phase0.BeaconState; SignedBeaconBlock: phase0.SignedBeaconBlock; Metadata: phase0.Metadata; + SingleAttestation: phase0.Attestation; Attestation: phase0.Attestation; IndexedAttestation: phase0.IndexedAttestation; IndexedAttestationBigint: phase0.IndexedAttestationBigint; @@ -79,6 +80,7 @@ type TypesByFork = { LightClientStore: altair.LightClientStore; SyncCommittee: altair.SyncCommittee; SyncAggregate: altair.SyncAggregate; + SingleAttestation: phase0.Attestation; Attestation: phase0.Attestation; IndexedAttestation: phase0.IndexedAttestation; IndexedAttestationBigint: phase0.IndexedAttestationBigint; @@ -110,6 +112,7 @@ type TypesByFork = { SSEPayloadAttributes: bellatrix.SSEPayloadAttributes; SyncCommittee: altair.SyncCommittee; SyncAggregate: altair.SyncAggregate; + SingleAttestation: phase0.Attestation; Attestation: phase0.Attestation; IndexedAttestation: phase0.IndexedAttestation; IndexedAttestationBigint: phase0.IndexedAttestationBigint; @@ -141,6 +144,7 @@ type TypesByFork = { SSEPayloadAttributes: capella.SSEPayloadAttributes; SyncCommittee: altair.SyncCommittee; SyncAggregate: altair.SyncAggregate; + SingleAttestation: phase0.Attestation; Attestation: phase0.Attestation; IndexedAttestation: phase0.IndexedAttestation; IndexedAttestationBigint: phase0.IndexedAttestationBigint; @@ -177,6 +181,7 @@ type TypesByFork = { Contents: deneb.Contents; SyncCommittee: altair.SyncCommittee; SyncAggregate: altair.SyncAggregate; + SingleAttestation: phase0.Attestation; Attestation: phase0.Attestation; IndexedAttestation: phase0.IndexedAttestation; IndexedAttestationBigint: phase0.IndexedAttestationBigint; @@ -213,6 +218,7 @@ type TypesByFork = { Contents: deneb.Contents; SyncCommittee: altair.SyncCommittee; SyncAggregate: altair.SyncAggregate; + SingleAttestation: electra.SingleAttestation; Attestation: electra.Attestation; IndexedAttestation: electra.IndexedAttestation; IndexedAttestationBigint: electra.IndexedAttestationBigint; @@ -281,6 +287,7 @@ export type SignedBuilderBid = TypesByF export type SSEPayloadAttributes = TypesByFork[F]["SSEPayloadAttributes"]; export type Attestation = TypesByFork[F]["Attestation"]; +export type SingleAttestation = TypesByFork[F]["SingleAttestation"]; export type IndexedAttestation = TypesByFork[F]["IndexedAttestation"]; export type IndexedAttestationBigint = TypesByFork[F]["IndexedAttestationBigint"]; export type AttesterSlashing = TypesByFork[F]["AttesterSlashing"]; diff --git a/packages/types/src/utils/typeguards.ts b/packages/types/src/utils/typeguards.ts index c212e4726e78..8622bc805982 100644 --- a/packages/types/src/utils/typeguards.ts +++ b/packages/types/src/utils/typeguards.ts @@ -16,6 +16,7 @@ import { SignedBeaconBlockOrContents, SignedBlindedBeaconBlock, SignedBlockContents, + SingleAttestation, } from "../types.js"; export function isExecutionPayload( @@ -74,6 +75,12 @@ export function isElectraAttestation(attestation: Attestation): attestation is A return (attestation as Attestation).committeeBits !== undefined; } +export function isElectraSingleAttestation( + singleAttestation: SingleAttestation +): singleAttestation is SingleAttestation { + return (singleAttestation as SingleAttestation).committeeIndex !== undefined; +} + export function isElectraLightClientUpdate(update: LightClientUpdate): update is LightClientUpdate { const updatePostElectra = update as LightClientUpdate; return ( diff --git a/packages/validator/src/services/attestation.ts b/packages/validator/src/services/attestation.ts index 514ecbbd613d..56be02131af6 100644 --- a/packages/validator/src/services/attestation.ts +++ b/packages/validator/src/services/attestation.ts @@ -1,8 +1,8 @@ import {ApiClient, routes} from "@lodestar/api"; import {ChainForkConfig} from "@lodestar/config"; -import {ForkSeq} from "@lodestar/params"; +import {ForkPreElectra, ForkSeq} from "@lodestar/params"; import {computeEpochAtSlot, isAggregatorFromCommitteeLength} from "@lodestar/state-transition"; -import {Attestation, BLSSignature, SignedAggregateAndProof, Slot, phase0, ssz} from "@lodestar/types"; +import {BLSSignature, SignedAggregateAndProof, SingleAttestation, Slot, phase0, ssz} from "@lodestar/types"; import {prettyBytes, sleep, toRootHex} from "@lodestar/utils"; import {Metrics} from "../metrics.js"; import {PubkeyHex} from "../types.js"; @@ -193,7 +193,7 @@ export class AttestationService { attestationNoCommittee: phase0.AttestationData, duties: AttDutyAndProof[] ): Promise { - const signedAttestations: Attestation[] = []; + const signedAttestations: SingleAttestation[] = []; const headRootHex = toRootHex(attestationNoCommittee.beaconBlockRoot); const currentEpoch = computeEpochAtSlot(slot); const isPostElectra = currentEpoch >= this.config.ELECTRA_FORK_EPOCH; @@ -239,7 +239,11 @@ export class AttestationService { if (isPostElectra) { (await this.api.beacon.submitPoolAttestationsV2({signedAttestations})).assertOk(); } else { - (await this.api.beacon.submitPoolAttestations({signedAttestations})).assertOk(); + ( + await this.api.beacon.submitPoolAttestations({ + signedAttestations: signedAttestations as SingleAttestation[], + }) + ).assertOk(); } this.logger.info("Published attestations", { ...logCtx, diff --git a/packages/validator/src/services/validatorStore.ts b/packages/validator/src/services/validatorStore.ts index e80c0b291ef5..7755c5139694 100644 --- a/packages/validator/src/services/validatorStore.ts +++ b/packages/validator/src/services/validatorStore.ts @@ -13,7 +13,6 @@ import { DOMAIN_SYNC_COMMITTEE, DOMAIN_SYNC_COMMITTEE_SELECTION_PROOF, ForkSeq, - MAX_COMMITTEES_PER_SLOT, } from "@lodestar/params"; import { ZERO_HASH, @@ -35,6 +34,7 @@ import { SignedAggregateAndProof, SignedBeaconBlock, SignedBlindedBeaconBlock, + SingleAttestation, Slot, ValidatorIndex, altair, @@ -505,7 +505,7 @@ export class ValidatorStore { duty: routes.validator.AttesterDuty, attestationData: phase0.AttestationData, currentEpoch: Epoch - ): Promise { + ): Promise { // Make sure the target epoch is not higher than the current epoch to avoid potential attacks. if (attestationData.target.epoch > currentEpoch) { throw Error( @@ -539,10 +539,10 @@ export class ValidatorStore { if (this.config.getForkSeq(signingSlot) >= ForkSeq.electra) { return { - aggregationBits: BitArray.fromSingleBit(duty.committeeLength, duty.validatorCommitteeIndex), + committeeIndex: duty.committeeIndex, + attesterIndex: duty.validatorIndex, data: attestationData, signature: await this.getSignature(duty.pubkey, signingRoot, signingSlot, signableMessage), - committeeBits: BitArray.fromSingleBit(MAX_COMMITTEES_PER_SLOT, duty.committeeIndex), }; } diff --git a/packages/validator/test/unit/services/attestation.test.ts b/packages/validator/test/unit/services/attestation.test.ts index e3a6f0771d62..f8aee27d0dc1 100644 --- a/packages/validator/test/unit/services/attestation.test.ts +++ b/packages/validator/test/unit/services/attestation.test.ts @@ -81,17 +81,20 @@ describe("AttestationService", () => { opts ); - const attestation = isPostElectra + const singleAttestation = isPostElectra + ? ssz.electra.SingleAttestation.defaultValue() + : ssz.phase0.Attestation.defaultValue(); + const aggregatedAttestation = isPostElectra ? ssz.electra.Attestation.defaultValue() : ssz.phase0.Attestation.defaultValue(); - const aggregate = isPostElectra + const aggregateAndProof = isPostElectra ? ssz.electra.SignedAggregateAndProof.defaultValue() : ssz.phase0.SignedAggregateAndProof.defaultValue(); const duties: AttDutyAndProof[] = [ { duty: { slot: 0, - committeeIndex: attestation.data.index, + committeeIndex: singleAttestation.data.index, committeeLength: 120, committeesAtSlot: 120, validatorCommitteeIndex: 1, @@ -115,15 +118,15 @@ describe("AttestationService", () => { vi.spyOn(attestationService["dutiesService"], "getDutiesAtSlot").mockImplementation(() => duties); // Mock beacon's attestation and aggregates endpoints - api.validator.produceAttestationData.mockResolvedValue(mockApiResponse({data: attestation.data})); + api.validator.produceAttestationData.mockResolvedValue(mockApiResponse({data: singleAttestation.data})); if (isPostElectra) { api.validator.getAggregatedAttestationV2.mockResolvedValue( - mockApiResponse({data: attestation, meta: {version: ForkName.electra}}) + mockApiResponse({data: aggregatedAttestation, meta: {version: ForkName.electra}}) ); api.beacon.submitPoolAttestationsV2.mockResolvedValue(mockApiResponse({})); api.validator.publishAggregateAndProofsV2.mockResolvedValue(mockApiResponse({})); } else { - api.validator.getAggregatedAttestation.mockResolvedValue(mockApiResponse({data: attestation})); + api.validator.getAggregatedAttestation.mockResolvedValue(mockApiResponse({data: aggregatedAttestation})); api.beacon.submitPoolAttestations.mockResolvedValue(mockApiResponse({})); api.validator.publishAggregateAndProofs.mockResolvedValue(mockApiResponse({})); } @@ -139,8 +142,8 @@ describe("AttestationService", () => { } // Mock signing service - validatorStore.signAttestation.mockResolvedValue(attestation); - validatorStore.signAggregateAndProof.mockResolvedValue(aggregate); + validatorStore.signAttestation.mockResolvedValue(singleAttestation); + validatorStore.signAggregateAndProof.mockResolvedValue(aggregateAndProof); // Trigger clock onSlot for slot 0 await clock.tickSlotFns(0, controller.signal); @@ -170,21 +173,23 @@ describe("AttestationService", () => { if (isPostElectra) { // Must submit the attestation received through produceAttestationData() expect(api.beacon.submitPoolAttestationsV2).toHaveBeenCalledOnce(); - expect(api.beacon.submitPoolAttestationsV2).toHaveBeenCalledWith({signedAttestations: [attestation]}); + expect(api.beacon.submitPoolAttestationsV2).toHaveBeenCalledWith({signedAttestations: [singleAttestation]}); // Must submit the aggregate received through getAggregatedAttestationV2() then createAndSignAggregateAndProof() expect(api.validator.publishAggregateAndProofsV2).toHaveBeenCalledOnce(); expect(api.validator.publishAggregateAndProofsV2).toHaveBeenCalledWith({ - signedAggregateAndProofs: [aggregate], + signedAggregateAndProofs: [aggregateAndProof], }); } else { // Must submit the attestation received through produceAttestationData() expect(api.beacon.submitPoolAttestations).toHaveBeenCalledOnce(); - expect(api.beacon.submitPoolAttestations).toHaveBeenCalledWith({signedAttestations: [attestation]}); + expect(api.beacon.submitPoolAttestations).toHaveBeenCalledWith({signedAttestations: [singleAttestation]}); // Must submit the aggregate received through getAggregatedAttestation() then createAndSignAggregateAndProof() expect(api.validator.publishAggregateAndProofs).toHaveBeenCalledOnce(); - expect(api.validator.publishAggregateAndProofs).toHaveBeenCalledWith({signedAggregateAndProofs: [aggregate]}); + expect(api.validator.publishAggregateAndProofs).toHaveBeenCalledWith({ + signedAggregateAndProofs: [aggregateAndProof], + }); } }); }); From 3cd80bb6e79fc65c26699912a1c29bee9c31d75f Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Thu, 28 Nov 2024 11:54:33 +0100 Subject: [PATCH 02/34] fix: select correct gossip type when publishing single attestation (#7256) * fix: select correct gossip type when publishing single attestation * Add SingleAttestation as alias to phase0 ssz types --- packages/beacon-node/src/network/gossip/topic.ts | 2 +- packages/types/src/phase0/sszTypes.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/beacon-node/src/network/gossip/topic.ts b/packages/beacon-node/src/network/gossip/topic.ts index 87ae2a30843d..83603226a94c 100644 --- a/packages/beacon-node/src/network/gossip/topic.ts +++ b/packages/beacon-node/src/network/gossip/topic.ts @@ -88,7 +88,7 @@ export function getGossipSSZType(topic: GossipTopic) { case GossipType.beacon_aggregate_and_proof: return sszTypesFor(topic.fork).SignedAggregateAndProof; case GossipType.beacon_attestation: - return sszTypesFor(topic.fork).Attestation; + return sszTypesFor(topic.fork).SingleAttestation; case GossipType.proposer_slashing: return ssz.phase0.ProposerSlashing; case GossipType.attester_slashing: diff --git a/packages/types/src/phase0/sszTypes.ts b/packages/types/src/phase0/sszTypes.ts index 2f9eead77608..f64415439b3e 100644 --- a/packages/types/src/phase0/sszTypes.ts +++ b/packages/types/src/phase0/sszTypes.ts @@ -316,6 +316,8 @@ export const Attestation = new ContainerType( {typeName: "Attestation", jsonCase: "eth2"} ); +export const SingleAttestation = Attestation; + export const AttesterSlashing = new ContainerType( { // In state transition, AttesterSlashing attestations are only partially validated. Their slot and epoch could From 56d083a9f275bbf1937d2ec560e8c7dfa3fcd5a6 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Fri, 29 Nov 2024 12:02:36 +0100 Subject: [PATCH 03/34] fix: get attester index from single attestation bytes if cache is used (#7264) --- .../src/chain/validation/attestation.ts | 16 +++++++++++++--- packages/beacon-node/src/util/sszBytes.ts | 16 +++++++++++++++- .../beacon-node/test/unit/util/sszBytes.test.ts | 6 ++++++ 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/packages/beacon-node/src/chain/validation/attestation.ts b/packages/beacon-node/src/chain/validation/attestation.ts index 305cbba3fbde..d181ea4f26b5 100644 --- a/packages/beacon-node/src/chain/validation/attestation.ts +++ b/packages/beacon-node/src/chain/validation/attestation.ts @@ -43,8 +43,7 @@ import { getAggregationBitsFromAttestationSerialized, getAttDataFromSignedAggregateAndProofElectra, getAttDataFromSignedAggregateAndProofPhase0, - getBeaconAttestationGossipIndex, - getCommitteeBitsFromSignedAggregateAndProofElectra, + getAttesterIndexFromSingleAttestationSerialized, getCommitteeIndexFromSingleAttestationSerialized, getSignatureFromAttestationSerialized, } from "../../util/sszBytes.js"; @@ -420,7 +419,18 @@ async function validateAttestationNoSignatureCheck( }); } } else { - validatorIndex = (attestationOrCache.attestation as SingleAttestation).attesterIndex; + if (attestationOrCache.attestation) { + validatorIndex = (attestationOrCache.attestation as SingleAttestation).attesterIndex; + } else { + const attesterIndex = getAttesterIndexFromSingleAttestationSerialized(attestationOrCache.serializedData); + if (attesterIndex === null) { + throw new AttestationError(GossipAction.REJECT, { + code: AttestationErrorCode.INVALID_SERIALIZED_BYTES, + }); + } + validatorIndex = attesterIndex; + } + // [REJECT] The attester is a member of the committee -- i.e. // `attestation.attester_index in get_beacon_committee(state, attestation.data.slot, index)`. // If `aggregationBitsElectra` exists, that means we have already cached it. No need to check again diff --git a/packages/beacon-node/src/util/sszBytes.ts b/packages/beacon-node/src/util/sszBytes.ts index 8dca248b5830..ab2b59c68080 100644 --- a/packages/beacon-node/src/util/sszBytes.ts +++ b/packages/beacon-node/src/util/sszBytes.ts @@ -7,7 +7,7 @@ import { MAX_COMMITTEES_PER_SLOT, isForkPostElectra, } from "@lodestar/params"; -import {BLSSignature, CommitteeIndex, RootHex, Slot} from "@lodestar/types"; +import {BLSSignature, CommitteeIndex, RootHex, Slot, ValidatorIndex} from "@lodestar/types"; export type BlockRootHex = RootHex; // pre-electra, AttestationData is used to cache attestations @@ -54,6 +54,7 @@ const SIGNATURE_SIZE = 96; const SINGLE_ATTESTATION_ATTDATA_OFFSET = 8 + 8; const SINGLE_ATTESTATION_SLOT_OFFSET = SINGLE_ATTESTATION_ATTDATA_OFFSET; const SINGLE_ATTESTATION_COMMITTEE_INDEX_OFFSET = 0; +const SINGLE_ATTESTATION_ATTESTER_INDEX_OFFSET = 8; const SINGLE_ATTESTATION_BEACON_BLOCK_ROOT_OFFSET = SINGLE_ATTESTATION_ATTDATA_OFFSET + 8 + 8; const SINGLE_ATTESTATION_SIGNATURE_OFFSET = SINGLE_ATTESTATION_ATTDATA_OFFSET + ATTESTATION_DATA_SIZE; const SINGLE_ATTESTATION_SIZE = SINGLE_ATTESTATION_SIGNATURE_OFFSET + SIGNATURE_SIZE; @@ -201,6 +202,19 @@ export function getCommitteeIndexFromSingleAttestationSerialized( return getSlotFromOffset(data, VARIABLE_FIELD_OFFSET + SLOT_SIZE); } +/** + * Extract attester index from SingleAttestation serialized bytes. + * Return null if data is not long enough to extract index. + * TODO Electra: Rename getSlotFromOffset to reflect generic usage + */ +export function getAttesterIndexFromSingleAttestationSerialized(data: Uint8Array): ValidatorIndex | null { + if (data.length !== SINGLE_ATTESTATION_SIZE) { + return null; + } + + return getSlotFromOffset(data, SINGLE_ATTESTATION_ATTESTER_INDEX_OFFSET); +} + /** * Extract block root from SingleAttestation serialized bytes. * Return null if data is not long enough to extract block root. diff --git a/packages/beacon-node/test/unit/util/sszBytes.test.ts b/packages/beacon-node/test/unit/util/sszBytes.test.ts index ef6b713dccdb..6496c9e80619 100644 --- a/packages/beacon-node/test/unit/util/sszBytes.test.ts +++ b/packages/beacon-node/test/unit/util/sszBytes.test.ts @@ -6,6 +6,7 @@ import { RootHex, SingleAttestation, Slot, + ValidatorIndex, deneb, electra, isElectraSingleAttestation, @@ -21,6 +22,7 @@ import { getAttDataFromSignedAggregateAndProofElectra, getAttDataFromSignedAggregateAndProofPhase0, getAttDataFromSingleAttestationSerialized, + getAttesterIndexFromSingleAttestationSerialized, getBlockRootFromAttestationSerialized, getBlockRootFromSignedAggregateAndProofSerialized, getBlockRootFromSingleAttestationSerialized, @@ -49,6 +51,7 @@ describe("SinlgeAttestation SSZ serialized picking", () => { ...electraSingleAttestationFromValues( 4_000_000, 127, + 1, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", 200_00, "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeffffffffffffffffffffffffffffffff" @@ -68,6 +71,7 @@ describe("SinlgeAttestation SSZ serialized picking", () => { expect(getCommitteeIndexFromSingleAttestationSerialized(ForkName.electra, bytes)).toEqual( attestation.committeeIndex ); + expect(getAttesterIndexFromSingleAttestationSerialized(bytes)).toEqual(attestation.attesterIndex); expect(getBlockRootFromSingleAttestationSerialized(bytes)).toEqual(toRootHex(attestation.data.beaconBlockRoot)); // base64, not hex expect(getAttDataFromSingleAttestationSerialized(bytes)).toEqual( @@ -332,6 +336,7 @@ function phase0SingleAttestationFromValues( function electraSingleAttestationFromValues( slot: Slot, committeeIndex: CommitteeIndex, + attesterIndex: ValidatorIndex, blockRoot: RootHex, targetEpoch: Epoch, targetRoot: RootHex @@ -342,6 +347,7 @@ function electraSingleAttestationFromValues( attestation.data.target.epoch = targetEpoch; attestation.data.target.root = fromHex(targetRoot); attestation.committeeIndex = committeeIndex; + attestation.attesterIndex = attesterIndex; return attestation; } From b12b20532e47747f490bad18d55bd777480eb1b0 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Fri, 29 Nov 2024 14:15:53 +0100 Subject: [PATCH 04/34] fix: correctly get signature from single attestation bytes (#7266) --- packages/beacon-node/src/chain/validation/attestation.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/beacon-node/src/chain/validation/attestation.ts b/packages/beacon-node/src/chain/validation/attestation.ts index d181ea4f26b5..b54ae10e8f64 100644 --- a/packages/beacon-node/src/chain/validation/attestation.ts +++ b/packages/beacon-node/src/chain/validation/attestation.ts @@ -46,6 +46,7 @@ import { getAttesterIndexFromSingleAttestationSerialized, getCommitteeIndexFromSingleAttestationSerialized, getSignatureFromAttestationSerialized, + getSignatureFromSingleAttestationSerialized, } from "../../util/sszBytes.js"; import {Result, wrapError} from "../../util/wrapError.js"; import {AttestationError, AttestationErrorCode, GossipAction} from "../errors/index.js"; @@ -480,7 +481,9 @@ async function validateAttestationNoSignatureCheck( let attDataRootHex: RootHex; const signature = attestationOrCache.attestation ? attestationOrCache.attestation.signature - : getSignatureFromAttestationSerialized(attestationOrCache.serializedData); + : !isForkPostElectra(fork) + ? getSignatureFromAttestationSerialized(attestationOrCache.serializedData) + : getSignatureFromSingleAttestationSerialized(attestationOrCache.serializedData); if (signature === null) { throw new AttestationError(GossipAction.REJECT, { code: AttestationErrorCode.INVALID_SERIALIZED_BYTES, From e82a3f6af12ca74b6930d6a0a0abe7209fea986d Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Fri, 29 Nov 2024 14:16:54 +0100 Subject: [PATCH 05/34] fix: remove aggregation bits from seen attestation cache (#7265) * fix: remove aggregation bits from seen attestation cache * Allow passing null as aggregationBits to test pre-electra case * Only create aggregationBits once for the first attestation * Avoid second getSingleTrueBit call --- .../src/api/impl/beacon/pool/index.ts | 5 +-- .../src/chain/opPools/attestationPool.ts | 21 ++++++----- .../chain/seenCache/seenAttestationData.ts | 3 -- .../src/chain/validation/attestation.ts | 36 ++++++++----------- .../src/network/processor/gossipHandlers.ts | 13 +++++-- .../chain/opPools/attestationPool.test.ts | 31 +++++++++------- 6 files changed, 58 insertions(+), 51 deletions(-) diff --git a/packages/beacon-node/src/api/impl/beacon/pool/index.ts b/packages/beacon-node/src/api/impl/beacon/pool/index.ts index ee80310df49b..d0c35b1c0cd8 100644 --- a/packages/beacon-node/src/api/impl/beacon/pool/index.ts +++ b/packages/beacon-node/src/api/impl/beacon/pool/index.ts @@ -105,7 +105,7 @@ export function getBeaconPoolApi({ // when a validator is configured with multiple beacon node urls, this attestation data may come from another beacon node // and the block hasn't been in our forkchoice since we haven't seen / processing that block // see https://github.com/ChainSafe/lodestar/issues/5098 - const {indexedAttestation, subnet, attDataRootHex, committeeIndex, aggregationBits} = + const {indexedAttestation, subnet, attDataRootHex, committeeIndex, committeeValidatorIndex, committeeSize} = await validateGossipFnRetryUnknownRoot(validateFn, network, chain, slot, beaconBlockRoot); if (network.shouldAggregate(subnet, slot)) { @@ -113,7 +113,8 @@ export function getBeaconPoolApi({ committeeIndex, attestation, attDataRootHex, - aggregationBits + committeeValidatorIndex, + committeeSize ); metrics?.opPool.attestationPoolInsertOutcome.inc({insertOutcome}); } diff --git a/packages/beacon-node/src/chain/opPools/attestationPool.ts b/packages/beacon-node/src/chain/opPools/attestationPool.ts index 30f3738bb273..e7f302e42a50 100644 --- a/packages/beacon-node/src/chain/opPools/attestationPool.ts +++ b/packages/beacon-node/src/chain/opPools/attestationPool.ts @@ -109,7 +109,8 @@ export class AttestationPool { committeeIndex: CommitteeIndex, attestation: SingleAttestation, attDataRootHex: RootHex, - aggregationBits: BitArray | null + committeeValidatorIndex: number, + committeeSize: number ): InsertOutcome { const slot = attestation.data.slot; const fork = this.config.getForkName(slot); @@ -149,10 +150,10 @@ export class AttestationPool { const aggregate = aggregateByIndex.get(committeeIndex); if (aggregate) { // Aggregate mutating - return aggregateAttestationInto(aggregate, attestation, aggregationBits); + return aggregateAttestationInto(aggregate, attestation, committeeValidatorIndex); } // Create new aggregate - aggregateByIndex.set(committeeIndex, attestationToAggregate(attestation, aggregationBits)); + aggregateByIndex.set(committeeIndex, attestationToAggregate(attestation, committeeValidatorIndex, committeeSize)); return InsertOutcome.NewData; } @@ -224,13 +225,12 @@ export class AttestationPool { function aggregateAttestationInto( aggregate: AggregateFast, attestation: SingleAttestation, - aggregationBits: BitArray | null + committeeValidatorIndex: number ): InsertOutcome { let bitIndex: number | null; if (isElectraSingleAttestation(attestation)) { - assert.notNull(aggregationBits, "aggregationBits missing post-electra"); - bitIndex = aggregationBits.getSingleTrueBit(); + bitIndex = committeeValidatorIndex; } else { bitIndex = attestation.aggregationBits.getSingleTrueBit(); } @@ -250,12 +250,15 @@ function aggregateAttestationInto( /** * Format `contribution` into an efficient `aggregate` to add more contributions in with aggregateContributionInto() */ -function attestationToAggregate(attestation: SingleAttestation, aggregationBits: BitArray | null): AggregateFast { +function attestationToAggregate( + attestation: SingleAttestation, + committeeValidatorIndex: number, + committeeSize: number +): AggregateFast { if (isElectraSingleAttestation(attestation)) { - assert.notNull(aggregationBits, "aggregationBits missing post-electra to generate aggregate"); return { data: attestation.data, - aggregationBits, + aggregationBits: BitArray.fromSingleBit(committeeSize, committeeValidatorIndex), committeeBits: BitArray.fromSingleBit(MAX_COMMITTEES_PER_SLOT, attestation.committeeIndex), signature: signatureFromBytesNoCheck(attestation.signature), }; diff --git a/packages/beacon-node/src/chain/seenCache/seenAttestationData.ts b/packages/beacon-node/src/chain/seenCache/seenAttestationData.ts index 498c8f36ef8e..667b4aa98e9c 100644 --- a/packages/beacon-node/src/chain/seenCache/seenAttestationData.ts +++ b/packages/beacon-node/src/chain/seenCache/seenAttestationData.ts @@ -11,7 +11,6 @@ type AttDataBase64 = string; export type AttestationDataCacheEntry = { // part of shuffling data, so this does not take memory committeeValidatorIndices: Uint32Array; - // TODO: remove this? this is available in SingleAttestation committeeIndex: CommitteeIndex; // IndexedAttestationData signing root, 32 bytes signingRoot: Uint8Array; @@ -21,8 +20,6 @@ export type AttestationDataCacheEntry = { // for example in a mainnet node subscribing to all subnets, attestations are processed up to 20k per slot attestationData: phase0.AttestationData; subnet: number; - // aggregationBits only populates post-electra. Pre-electra can use get it directly from attestationOrBytes - aggregationBits: BitArray | null; }; export enum RejectReason { diff --git a/packages/beacon-node/src/chain/validation/attestation.ts b/packages/beacon-node/src/chain/validation/attestation.ts index b54ae10e8f64..395348a3ac34 100644 --- a/packages/beacon-node/src/chain/validation/attestation.ts +++ b/packages/beacon-node/src/chain/validation/attestation.ts @@ -69,7 +69,8 @@ export type AttestationValidationResult = { subnet: number; attDataRootHex: RootHex; committeeIndex: CommitteeIndex; - aggregationBits: BitArray | null; // Field populated post-electra only + committeeValidatorIndex: number; + committeeSize: number; }; export type AttestationOrBytes = ApiAttestation | GossipAttestation; @@ -325,6 +326,7 @@ async function validateAttestationNoSignatureCheck( } let aggregationBits: BitArray | null = null; + let committeeValidatorIndex: number | null = null; if (!isForkPostElectra(fork)) { // [REJECT] The attestation is unaggregated -- that is, it has exactly one participating validator // (len([bit for bit in attestation.aggregation_bits if bit]) == 1, i.e. exactly 1 bit is set). @@ -344,11 +346,7 @@ async function validateAttestationNoSignatureCheck( code: AttestationErrorCode.NOT_EXACTLY_ONE_AGGREGATION_BIT_SET, }); } - } else { - // Populate aggregationBits if cached post-electra, else we populate later - if (attestationOrCache.cache && attestationOrCache.cache.aggregationBits !== null) { - aggregationBits = attestationOrCache.cache.aggregationBits; - } + committeeValidatorIndex = bitIndex; } let committeeValidatorIndices: Uint32Array; @@ -407,10 +405,9 @@ async function validateAttestationNoSignatureCheck( if (!isForkPostElectra(fork)) { // The validity of aggregation bits are already checked above assert.notNull(aggregationBits); - const bitIndex = aggregationBits.getSingleTrueBit(); - assert.notNull(bitIndex); + assert.notNull(committeeValidatorIndex); - validatorIndex = committeeValidatorIndices[bitIndex]; + validatorIndex = committeeValidatorIndices[committeeValidatorIndex]; // [REJECT] The number of aggregation bits matches the committee size // -- i.e. len(attestation.aggregation_bits) == len(get_beacon_committee(state, data.slot, data.index)). // > TODO: Is this necessary? Lighthouse does not do this check. @@ -434,17 +431,12 @@ async function validateAttestationNoSignatureCheck( // [REJECT] The attester is a member of the committee -- i.e. // `attestation.attester_index in get_beacon_committee(state, attestation.data.slot, index)`. - // If `aggregationBitsElectra` exists, that means we have already cached it. No need to check again - if (aggregationBits === null) { - // Position of the validator in its committee - const committeeValidatorIndex = committeeValidatorIndices.indexOf(validatorIndex); - if (committeeValidatorIndex === -1) { - throw new AttestationError(GossipAction.REJECT, { - code: AttestationErrorCode.ATTESTER_NOT_IN_COMMITTEE, - }); - } - - aggregationBits = BitArray.fromSingleBit(committeeValidatorIndices.length, committeeValidatorIndex); + // Position of the validator in its committee + committeeValidatorIndex = committeeValidatorIndices.indexOf(validatorIndex); + if (committeeValidatorIndex === -1) { + throw new AttestationError(GossipAction.REJECT, { + code: AttestationErrorCode.ATTESTER_NOT_IN_COMMITTEE, + }); } } @@ -517,7 +509,6 @@ async function validateAttestationNoSignatureCheck( // root of AttestationData was already cached during getIndexedAttestationSignatureSet attDataRootHex, attestationData: attData, - aggregationBits: isForkPostElectra(fork) ? aggregationBits : null, }); } } @@ -553,7 +544,8 @@ async function validateAttestationNoSignatureCheck( signatureSet, validatorIndex, committeeIndex, - aggregationBits: isForkPostElectra(fork) ? aggregationBits : null, + committeeValidatorIndex, + committeeSize: committeeValidatorIndices.length, }; } diff --git a/packages/beacon-node/src/network/processor/gossipHandlers.ts b/packages/beacon-node/src/network/processor/gossipHandlers.ts index 63f67631ea90..906d16b33d71 100644 --- a/packages/beacon-node/src/network/processor/gossipHandlers.ts +++ b/packages/beacon-node/src/network/processor/gossipHandlers.ts @@ -637,8 +637,14 @@ function getBatchHandlers(modules: ValidatorFnsModules, options: GossipHandlerOp results.push(null); // Handler - const {indexedAttestation, attDataRootHex, attestation, committeeIndex, aggregationBits} = - validationResult.result; + const { + indexedAttestation, + attDataRootHex, + attestation, + committeeIndex, + committeeValidatorIndex, + committeeSize, + } = validationResult.result; metrics?.registerGossipUnaggregatedAttestation(gossipHandlerParams[i].seenTimestampSec, indexedAttestation); try { @@ -650,7 +656,8 @@ function getBatchHandlers(modules: ValidatorFnsModules, options: GossipHandlerOp committeeIndex, attestation, attDataRootHex, - aggregationBits + committeeValidatorIndex, + committeeSize ); metrics?.opPool.attestationPoolInsertOutcome.inc({insertOutcome}); } diff --git a/packages/beacon-node/test/unit/chain/opPools/attestationPool.test.ts b/packages/beacon-node/test/unit/chain/opPools/attestationPool.test.ts index 2d2411b7ca73..5e897bb6380a 100644 --- a/packages/beacon-node/test/unit/chain/opPools/attestationPool.test.ts +++ b/packages/beacon-node/test/unit/chain/opPools/attestationPool.test.ts @@ -24,6 +24,9 @@ describe("AttestationPool", () => { const clockStub = getMockedClock(); vi.spyOn(clockStub, "secFromSlot").mockReturnValue(0); + const committeeValidatorIndex = 0; + const committeeSize = 128; + const cutOffSecFromSlot = (2 / 3) * config.SECONDS_PER_SLOT; // Mock attestations @@ -37,10 +40,10 @@ describe("AttestationPool", () => { signature: validSignature, }; const electraAttestation: electra.Attestation = { - ...ssz.electra.Attestation.defaultValue(), - committeeBits: BitArray.fromSingleBit(MAX_COMMITTEES_PER_SLOT, electraSingleAttestation.committeeIndex), + aggregationBits: BitArray.fromSingleBit(committeeSize, committeeValidatorIndex), data: electraAttestationData, signature: validSignature, + committeeBits: BitArray.fromSingleBit(MAX_COMMITTEES_PER_SLOT, electraSingleAttestation.committeeIndex), }; const phase0AttestationData: phase0.AttestationData = { ...ssz.phase0.AttestationData.defaultValue(), @@ -60,9 +63,14 @@ describe("AttestationPool", () => { it("add correct electra attestation", () => { const committeeIndex = 0; - const aggregationBits = ssz.electra.Attestation.fields.aggregationBits.defaultValue(); const attDataRootHex = toHexString(ssz.phase0.AttestationData.hashTreeRoot(electraSingleAttestation.data)); - const outcome = pool.add(committeeIndex, electraSingleAttestation, attDataRootHex, aggregationBits); + const outcome = pool.add( + committeeIndex, + electraSingleAttestation, + attDataRootHex, + committeeValidatorIndex, + committeeSize + ); expect(outcome).equal(InsertOutcome.NewData); expect(pool.getAggregate(electraAttestationData.slot, committeeIndex, attDataRootHex)).toEqual(electraAttestation); @@ -71,7 +79,7 @@ describe("AttestationPool", () => { it("add correct phase0 attestation", () => { const committeeIndex = null; const attDataRootHex = toHexString(ssz.phase0.AttestationData.hashTreeRoot(phase0Attestation.data)); - const outcome = pool.add(committeeIndex, phase0Attestation, attDataRootHex, null); + const outcome = pool.add(committeeIndex, phase0Attestation, attDataRootHex, committeeValidatorIndex, committeeSize); expect(outcome).equal(InsertOutcome.NewData); expect(pool.getAggregate(phase0AttestationData.slot, committeeIndex, attDataRootHex)).toEqual(phase0Attestation); @@ -82,18 +90,18 @@ describe("AttestationPool", () => { it("add electra attestation without committee index", () => { const committeeIndex = null; - const aggregationBits = ssz.electra.Attestation.fields.aggregationBits.defaultValue(); const attDataRootHex = toHexString(ssz.phase0.AttestationData.hashTreeRoot(electraSingleAttestation.data)); - expect(() => pool.add(committeeIndex, electraSingleAttestation, attDataRootHex, aggregationBits)).toThrow(); + expect(() => + pool.add(committeeIndex, electraSingleAttestation, attDataRootHex, committeeValidatorIndex, committeeSize) + ).toThrow(); expect(pool.getAggregate(electraAttestationData.slot, committeeIndex, attDataRootHex)).toBeNull(); }); it("add phase0 attestation with committee index", () => { const committeeIndex = 0; - const aggregationBits = ssz.electra.Attestation.fields.aggregationBits.defaultValue(); const attDataRootHex = toHexString(ssz.phase0.AttestationData.hashTreeRoot(phase0Attestation.data)); - const outcome = pool.add(committeeIndex, phase0Attestation, attDataRootHex, aggregationBits); + const outcome = pool.add(committeeIndex, phase0Attestation, attDataRootHex, committeeValidatorIndex, committeeSize); expect(outcome).equal(InsertOutcome.NewData); expect(pool.getAggregate(phase0AttestationData.slot, committeeIndex, attDataRootHex)).toEqual(phase0Attestation); @@ -104,7 +112,6 @@ describe("AttestationPool", () => { it("add electra attestation with phase0 slot", () => { const electraAttestationDataWithPhase0Slot = {...ssz.phase0.AttestationData.defaultValue(), slot: GENESIS_SLOT}; - const aggregationBits = ssz.electra.Attestation.fields.aggregationBits.defaultValue(); const singleAttestation = { ...ssz.electra.SingleAttestation.defaultValue(), data: electraAttestationDataWithPhase0Slot, @@ -112,7 +119,7 @@ describe("AttestationPool", () => { }; const attDataRootHex = toHexString(ssz.phase0.AttestationData.hashTreeRoot(electraAttestationDataWithPhase0Slot)); - expect(() => pool.add(0, singleAttestation, attDataRootHex, aggregationBits)).toThrow(); + expect(() => pool.add(0, singleAttestation, attDataRootHex, committeeValidatorIndex, committeeSize)).toThrow(); }); it("add phase0 attestation with electra slot", () => { @@ -127,6 +134,6 @@ describe("AttestationPool", () => { }; const attDataRootHex = toHexString(ssz.phase0.AttestationData.hashTreeRoot(phase0AttestationDataWithElectraSlot)); - expect(() => pool.add(0, attestation, attDataRootHex, null)).toThrow(); + expect(() => pool.add(0, attestation, attDataRootHex, committeeValidatorIndex, committeeSize)).toThrow(); }); }); From 70cb9d0b90b0776ed077929c509edcdb78158cdd Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Sat, 30 Nov 2024 18:50:55 +0100 Subject: [PATCH 06/34] fix: return correct type from attestation validation when using cache (#7261) * fix: return correct type from attestation validation when using cache * Remove type casts * Remove unused import * Use ternary operator instead of if-else * Fix aggregationBits type issue * Add comment --- .../src/chain/validation/attestation.ts | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/packages/beacon-node/src/chain/validation/attestation.ts b/packages/beacon-node/src/chain/validation/attestation.ts index 395348a3ac34..df57a6855019 100644 --- a/packages/beacon-node/src/chain/validation/attestation.ts +++ b/packages/beacon-node/src/chain/validation/attestation.ts @@ -30,7 +30,6 @@ import { SingleAttestation, Slot, ValidatorIndex, - electra, isElectraSingleAttestation, phase0, ssz, @@ -514,27 +513,27 @@ async function validateAttestationNoSignatureCheck( } // no signature check, leave that for step1 - const indexedAttestationContent = { + const indexedAttestation: IndexedAttestation = { attestingIndices, data: attData, signature, }; - const indexedAttestation = - ForkSeq[fork] >= ForkSeq.electra - ? (indexedAttestationContent as electra.IndexedAttestation) - : (indexedAttestationContent as phase0.IndexedAttestation); - const attestationContent = attestationOrCache.attestation ?? { - aggregationBits, - data: attData, - committeeIndex, - signature, - }; - - const attestation = - ForkSeq[fork] >= ForkSeq.electra - ? (attestationContent as SingleAttestation) - : (attestationContent as SingleAttestation); + const attestation: SingleAttestation = attestationOrCache.attestation + ? attestationOrCache.attestation + : !isForkPostElectra(fork) + ? { + // Aggregation bits are already asserted above to not be null + aggregationBits: aggregationBits as BitArray, + data: attData, + signature, + } + : { + committeeIndex, + attesterIndex: validatorIndex, + data: attData, + signature, + }; return { attestation, From e7c00a81084afd750483ecc6d6e50f3851a05fb1 Mon Sep 17 00:00:00 2001 From: NC <17676176+ensi321@users.noreply.github.com> Date: Mon, 2 Dec 2024 16:22:28 -0800 Subject: [PATCH 07/34] Bump spec test version --- packages/beacon-node/test/spec/specTestVersioning.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/beacon-node/test/spec/specTestVersioning.ts b/packages/beacon-node/test/spec/specTestVersioning.ts index 89c701a83514..daaa21128fcf 100644 --- a/packages/beacon-node/test/spec/specTestVersioning.ts +++ b/packages/beacon-node/test/spec/specTestVersioning.ts @@ -14,7 +14,7 @@ import {DownloadTestsOptions} from "@lodestar/spec-test-util/downloadTests"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); export const ethereumConsensusSpecsTests: DownloadTestsOptions = { - specVersion: "v1.5.0-alpha.8", + specVersion: "v1.5.0-alpha.9", // Target directory is the host package root: 'packages/*/spec-tests' outputDir: path.join(__dirname, "../../spec-tests"), specTestsRepoUrl: "https://github.com/ethereum/consensus-spec-tests", From f5330892b6a944fe00ee1a8ee6baacf23b89f7a3 Mon Sep 17 00:00:00 2001 From: NC <17676176+ensi321@users.noreply.github.com> Date: Tue, 3 Dec 2024 18:59:49 -0800 Subject: [PATCH 08/34] feat: implement partial spec changes for devnet-5 (#7229) --- .../src/block/processAttestationPhase0.ts | 27 +++++++++++--- .../src/block/processConsolidationRequest.ts | 12 +++++- .../src/block/processWithdrawals.ts | 24 +++++++----- .../state-transition/src/cache/epochCache.ts | 37 ++++++++----------- .../src/slot/upgradeStateToElectra.ts | 14 +++---- 5 files changed, 68 insertions(+), 46 deletions(-) diff --git a/packages/state-transition/src/block/processAttestationPhase0.ts b/packages/state-transition/src/block/processAttestationPhase0.ts index ab57bd27c80d..3a44d11e7afc 100644 --- a/packages/state-transition/src/block/processAttestationPhase0.ts +++ b/packages/state-transition/src/block/processAttestationPhase0.ts @@ -100,15 +100,30 @@ export function validateAttestation(fork: ForkSeq, state: CachedBeaconStateAllFo ); } - // Get total number of attestation participant of every committee specified - const participantCount = committeeIndices - .map((committeeIndex) => epochCtx.getBeaconCommittee(data.slot, committeeIndex).length) - .reduce((acc, committeeSize) => acc + committeeSize, 0); + const validatorsByCommittee = epochCtx.getBeaconCommittees(data.slot, committeeIndices); + const aggregationBitsArray = attestationElectra.aggregationBits.toBoolArray(); + + // Total number of attestation participants of every committee specified + let committeeOffset = 0; + for (const committeeValidators of validatorsByCommittee) { + const committeeAggregationBits = aggregationBitsArray.slice( + committeeOffset, + committeeOffset + committeeValidators.length + ); + + // Assert aggregation bits in this committee have at least one true bit + if (committeeAggregationBits.every((bit) => !bit)) { + throw new Error("Every committee in aggregation bits must have at least one attester"); + } + + committeeOffset += committeeValidators.length; + } + // Bitfield length matches total number of participants assert.equal( attestationElectra.aggregationBits.bitLen, - participantCount, - `Attestation aggregation bits length does not match total number of committee participant aggregationBitsLength=${attestation.aggregationBits.bitLen} participantCount=${participantCount}` + committeeOffset, + `Attestation aggregation bits length does not match total number of committee participants aggregationBitsLength=${attestation.aggregationBits.bitLen} participantCount=${committeeOffset}` ); } else { if (!(data.index < committeeCount)) { diff --git a/packages/state-transition/src/block/processConsolidationRequest.ts b/packages/state-transition/src/block/processConsolidationRequest.ts index 1a1d83eee0be..ade22c7454db 100644 --- a/packages/state-transition/src/block/processConsolidationRequest.ts +++ b/packages/state-transition/src/block/processConsolidationRequest.ts @@ -5,7 +5,7 @@ import {CachedBeaconStateElectra} from "../types.js"; import {hasEth1WithdrawalCredential} from "../util/capella.js"; import {hasExecutionWithdrawalCredential, isPubkeyKnown, switchToCompoundingValidator} from "../util/electra.js"; import {computeConsolidationEpochAndUpdateChurn} from "../util/epoch.js"; -import {getConsolidationChurnLimit, isActiveValidator} from "../util/validator.js"; +import {getConsolidationChurnLimit, getPendingBalanceToWithdraw, isActiveValidator} from "../util/validator.js"; // TODO Electra: Clean up necessary as there is a lot of overlap with isValidSwitchToCompoundRequest export function processConsolidationRequest( @@ -71,6 +71,16 @@ export function processConsolidationRequest( return; } + // Verify the source has been active long enough + if (currentEpoch < sourceValidator.activationEpoch + state.config.SHARD_COMMITTEE_PERIOD) { + return; + } + + // Verify the source has no pending withdrawals in the queue + if (getPendingBalanceToWithdraw(state, sourceIndex) > 0) { + return; + } + // TODO Electra: See if we can get rid of big int const exitEpoch = computeConsolidationEpochAndUpdateChurn(state, BigInt(sourceValidator.effectiveBalance)); sourceValidator.exitEpoch = exitEpoch; diff --git a/packages/state-transition/src/block/processWithdrawals.ts b/packages/state-transition/src/block/processWithdrawals.ts index ab1df570eb30..7f9ab6aa53f1 100644 --- a/packages/state-transition/src/block/processWithdrawals.ts +++ b/packages/state-transition/src/block/processWithdrawals.ts @@ -25,9 +25,8 @@ export function processWithdrawals( state: CachedBeaconStateCapella | CachedBeaconStateElectra, payload: capella.FullOrBlindedExecutionPayload ): void { - // partialWithdrawalsCount is withdrawals coming from EL since electra (EIP-7002) - // TODO - electra: may switch to executionWithdrawalsCount - const {withdrawals: expectedWithdrawals, partialWithdrawalsCount} = getExpectedWithdrawals(fork, state); + // processedPartialWithdrawalsCount is withdrawals coming from EL since electra (EIP-7002) + const {withdrawals: expectedWithdrawals, processedPartialWithdrawalsCount} = getExpectedWithdrawals(fork, state); const numWithdrawals = expectedWithdrawals.length; if (isCapellaPayloadHeader(payload)) { @@ -59,7 +58,9 @@ export function processWithdrawals( if (fork >= ForkSeq.electra) { const stateElectra = state as CachedBeaconStateElectra; - stateElectra.pendingPartialWithdrawals = stateElectra.pendingPartialWithdrawals.sliceFrom(partialWithdrawalsCount); + stateElectra.pendingPartialWithdrawals = stateElectra.pendingPartialWithdrawals.sliceFrom( + processedPartialWithdrawalsCount + ); } // Update the nextWithdrawalIndex @@ -87,7 +88,7 @@ export function getExpectedWithdrawals( ): { withdrawals: capella.Withdrawal[]; sampledValidators: number; - partialWithdrawalsCount: number; + processedPartialWithdrawalsCount: number; } { if (fork < ForkSeq.capella) { throw new Error(`getExpectedWithdrawals not supported at forkSeq=${fork} < ForkSeq.capella`); @@ -100,7 +101,7 @@ export function getExpectedWithdrawals( const withdrawals: capella.Withdrawal[] = []; const isPostElectra = fork >= ForkSeq.electra; // partialWithdrawalsCount is withdrawals coming from EL since electra (EIP-7002) - let partialWithdrawalsCount = 0; + let processedPartialWithdrawalsCount = 0; if (isPostElectra) { const stateElectra = state as CachedBeaconStateElectra; @@ -140,7 +141,7 @@ export function getExpectedWithdrawals( }); withdrawalIndex++; } - partialWithdrawalsCount++; + processedPartialWithdrawalsCount++; } } @@ -151,9 +152,14 @@ export function getExpectedWithdrawals( for (n = 0; n < bound; n++) { // Get next validator in turn const validatorIndex = (nextWithdrawalValidatorIndex + n) % validators.length; + const partiallyWithdrawnBalance = withdrawals + .filter((withdrawal) => withdrawal.validatorIndex === validatorIndex) + .reduce((acc, withdrawal) => acc + Number(withdrawal.amount), 0); const validator = validators.getReadonly(validatorIndex); - const balance = balances.get(validatorIndex); + const balance = isPostElectra + ? balances.get(validatorIndex) - partiallyWithdrawnBalance + : balances.get(validatorIndex); const {withdrawableEpoch, withdrawalCredentials, effectiveBalance} = validator; const hasWithdrawableCredentials = isPostElectra ? hasExecutionWithdrawalCredential(withdrawalCredentials) @@ -193,5 +199,5 @@ export function getExpectedWithdrawals( } } - return {withdrawals, sampledValidators: n, partialWithdrawalsCount}; + return {withdrawals, sampledValidators: n, processedPartialWithdrawalsCount}; } diff --git a/packages/state-transition/src/cache/epochCache.ts b/packages/state-transition/src/cache/epochCache.ts index af9e79bc831c..c975331c7dfb 100644 --- a/packages/state-transition/src/cache/epochCache.ts +++ b/packages/state-transition/src/cache/epochCache.ts @@ -748,13 +748,13 @@ export class EpochCache { * Return the beacon committee at slot for index. */ getBeaconCommittee(slot: Slot, index: CommitteeIndex): Uint32Array { - return this.getBeaconCommittees(slot, [index]); + return this.getBeaconCommittees(slot, [index])[0]; } /** - * Return a single Uint32Array representing concatted committees of indices + * Return a Uint32Array[] representing committees of indices */ - getBeaconCommittees(slot: Slot, indices: CommitteeIndex[]): Uint32Array { + getBeaconCommittees(slot: Slot, indices: CommitteeIndex[]): Uint32Array[] { if (indices.length === 0) { throw new Error("Attempt to get committees without providing CommitteeIndex"); } @@ -773,22 +773,7 @@ export class EpochCache { committees.push(slotCommittees[index]); } - // Early return if only one index - if (committees.length === 1) { - return committees[0]; - } - - // Create a new Uint32Array to flatten `committees` - const totalLength = committees.reduce((acc, curr) => acc + curr.length, 0); - const result = new Uint32Array(totalLength); - - let offset = 0; - for (const committee of committees) { - result.set(committee, offset); - offset += committee.length; - } - - return result; + return committees; } getCommitteeCountPerSlot(epoch: Epoch): number { @@ -911,9 +896,19 @@ export class EpochCache { // TODO Electra: resolve the naming conflicts const committeeIndices = committeeBits.getTrueBitIndexes(); - const validatorIndices = this.getBeaconCommittees(data.slot, committeeIndices); + const validatorsByCommittee = this.getBeaconCommittees(data.slot, committeeIndices); + + // Create a new Uint32Array to flatten `validatorsByCommittee` + const totalLength = validatorsByCommittee.reduce((acc, curr) => acc + curr.length, 0); + const committeeValidators = new Uint32Array(totalLength); + + let offset = 0; + for (const committee of validatorsByCommittee) { + committeeValidators.set(committee, offset); + offset += committee.length; + } - return aggregationBits.intersectValues(validatorIndices); + return aggregationBits.intersectValues(committeeValidators); } getCommitteeAssignments( diff --git a/packages/state-transition/src/slot/upgradeStateToElectra.ts b/packages/state-transition/src/slot/upgradeStateToElectra.ts index f030f9d572fe..3aae0f5b487e 100644 --- a/packages/state-transition/src/slot/upgradeStateToElectra.ts +++ b/packages/state-transition/src/slot/upgradeStateToElectra.ts @@ -56,7 +56,8 @@ export function upgradeStateToElectra(stateDeneb: CachedBeaconStateDeneb): Cache stateElectraView.exitBalanceToConsume = BigInt(0); const validatorsArr = stateElectraView.validators.getAllReadonly(); - const exitEpochs: Epoch[] = []; + const currentEpochPre = stateDeneb.epochCtx.epoch; + let earliestExitEpoch = computeActivationExitEpoch(currentEpochPre); // [EIP-7251]: add validators that are not yet active to pending balance deposits const preActivation: ValidatorIndex[] = []; @@ -65,17 +66,12 @@ export function upgradeStateToElectra(stateDeneb: CachedBeaconStateDeneb): Cache if (activationEpoch === FAR_FUTURE_EPOCH) { preActivation.push(validatorIndex); } - if (exitEpoch !== FAR_FUTURE_EPOCH) { - exitEpochs.push(exitEpoch); + if (exitEpoch !== FAR_FUTURE_EPOCH && exitEpoch > earliestExitEpoch) { + earliestExitEpoch = exitEpoch; } } - const currentEpochPre = stateDeneb.epochCtx.epoch; - - if (exitEpochs.length === 0) { - exitEpochs.push(currentEpochPre); - } - stateElectraView.earliestExitEpoch = Math.max(...exitEpochs) + 1; + stateElectraView.earliestExitEpoch = earliestExitEpoch + 1; stateElectraView.consolidationBalanceToConsume = BigInt(0); stateElectraView.earliestConsolidationEpoch = computeActivationExitEpoch(currentEpochPre); // TODO-electra: can we improve this? From 76ee6b33d21f6eb646d3f95d1efd22f033f1651d Mon Sep 17 00:00:00 2001 From: NC <17676176+ensi321@users.noreply.github.com> Date: Wed, 4 Dec 2024 12:16:12 -0800 Subject: [PATCH 09/34] feat: exclude empty requests in execution requests list (#7196) * initial commit * Address comment * Lint --- .../src/execution/engine/interface.ts | 7 ++ .../beacon-node/src/execution/engine/types.ts | 92 +++++++++++++++---- 2 files changed, 81 insertions(+), 18 deletions(-) diff --git a/packages/beacon-node/src/execution/engine/interface.ts b/packages/beacon-node/src/execution/engine/interface.ts index c32cc1bc7215..f62bf50d8074 100644 --- a/packages/beacon-node/src/execution/engine/interface.ts +++ b/packages/beacon-node/src/execution/engine/interface.ts @@ -58,6 +58,13 @@ export enum ClientCode { XX = "XX", // unknown } +// Represents request type in ExecutionRequests defined in EIP-7685 +export enum RequestType { + DEPOSIT_REQUEST_TYPE = 0, // 0x00 + WITHDRAWAL_REQUEST_TYPE = 1, // 0x01 + CONSOLIDATION_REQUEST_TYPE = 2, // 0x02 +} + export type ExecutePayloadResponse = | { status: ExecutionPayloadStatus.SYNCING | ExecutionPayloadStatus.ACCEPTED; diff --git a/packages/beacon-node/src/execution/engine/types.ts b/packages/beacon-node/src/execution/engine/types.ts index f35a63aa3d96..def5831b7609 100644 --- a/packages/beacon-node/src/execution/engine/types.ts +++ b/packages/beacon-node/src/execution/engine/types.ts @@ -17,7 +17,7 @@ import { quantityToBigint, quantityToNum, } from "../../eth1/provider/utils.js"; -import {BlobsBundle, ExecutionPayloadStatus, PayloadAttributes, VersionedHashes} from "./interface.js"; +import {BlobsBundle, ExecutionPayloadStatus, PayloadAttributes, RequestType, VersionedHashes} from "./interface.js"; import {WithdrawalV1} from "./payloadIdCache.js"; export type EngineApiRpcParamTypes = { @@ -165,12 +165,12 @@ export type WithdrawalRpc = { }; /** - * ExecutionRequestsRpc only holds 3 elements in the following order: + * ExecutionRequestsRpc only holds at most 3 elements and no repeated type: * - ssz'ed DepositRequests * - ssz'ed WithdrawalRequests * - ssz'ed ConsolidationRequests */ -export type ExecutionRequestsRpc = [DepositRequestsRpc, WithdrawalRequestsRpc, ConsolidationRequestsRpc]; +export type ExecutionRequestsRpc = (DepositRequestsRpc | WithdrawalRequestsRpc | ConsolidationRequestsRpc)[]; export type DepositRequestsRpc = DATA; export type WithdrawalRequestsRpc = DATA; @@ -404,8 +404,20 @@ export function deserializeWithdrawal(serialized: WithdrawalRpc): capella.Withdr } as capella.Withdrawal; } +/** + * Prepend a single-byte requestType to requestsBytes + */ +function prefixRequests(requestsBytes: Uint8Array, requestType: RequestType): Uint8Array { + const prefixedRequests = new Uint8Array(1 + requestsBytes.length); + prefixedRequests[0] = requestType; + prefixedRequests.set(requestsBytes, 1); + + return prefixedRequests; +} + function serializeDepositRequests(depositRequests: electra.DepositRequests): DepositRequestsRpc { - return bytesToData(ssz.electra.DepositRequests.serialize(depositRequests)); + const requestsBytes = ssz.electra.DepositRequests.serialize(depositRequests); + return bytesToData(prefixRequests(requestsBytes, RequestType.DEPOSIT_REQUEST_TYPE)); } function deserializeDepositRequests(serialized: DepositRequestsRpc): electra.DepositRequests { @@ -413,17 +425,19 @@ function deserializeDepositRequests(serialized: DepositRequestsRpc): electra.Dep } function serializeWithdrawalRequests(withdrawalRequests: electra.WithdrawalRequests): WithdrawalRequestsRpc { - return bytesToData(ssz.electra.WithdrawalRequests.serialize(withdrawalRequests)); + const requestsBytes = ssz.electra.WithdrawalRequests.serialize(withdrawalRequests); + return bytesToData(prefixRequests(requestsBytes, RequestType.WITHDRAWAL_REQUEST_TYPE)); } -function deserializeWithdrawalRequest(serialized: WithdrawalRequestsRpc): electra.WithdrawalRequests { +function deserializeWithdrawalRequests(serialized: WithdrawalRequestsRpc): electra.WithdrawalRequests { return ssz.electra.WithdrawalRequests.deserialize(dataToBytes(serialized, null)); } function serializeConsolidationRequests( consolidationRequests: electra.ConsolidationRequests ): ConsolidationRequestsRpc { - return bytesToData(ssz.electra.ConsolidationRequests.serialize(consolidationRequests)); + const requestsBytes = ssz.electra.ConsolidationRequests.serialize(consolidationRequests); + return bytesToData(prefixRequests(requestsBytes, RequestType.CONSOLIDATION_REQUEST_TYPE)); } function deserializeConsolidationRequests(serialized: ConsolidationRequestsRpc): electra.ConsolidationRequests { @@ -436,22 +450,64 @@ function deserializeConsolidationRequests(serialized: ConsolidationRequestsRpc): */ export function serializeExecutionRequests(executionRequests: ExecutionRequests): ExecutionRequestsRpc { const {deposits, withdrawals, consolidations} = executionRequests; + const result = []; - return [ - serializeDepositRequests(deposits), - serializeWithdrawalRequests(withdrawals), - serializeConsolidationRequests(consolidations), - ]; + if (deposits.length !== 0) { + result.push(serializeDepositRequests(deposits)); + } + + if (withdrawals.length !== 0) { + result.push(serializeWithdrawalRequests(withdrawals)); + } + + if (consolidations.length !== 0) { + result.push(serializeConsolidationRequests(consolidations)); + } + + return result; } export function deserializeExecutionRequests(serialized: ExecutionRequestsRpc): ExecutionRequests { - const [deposits, withdrawals, consolidations] = serialized; - - return { - deposits: deserializeDepositRequests(deposits), - withdrawals: deserializeWithdrawalRequest(withdrawals), - consolidations: deserializeConsolidationRequests(consolidations), + const result: ExecutionRequests = { + deposits: [], + withdrawals: [], + consolidations: [], }; + + if (serialized.length === 0) { + return result; + } + + let prevRequestType: RequestType | undefined; + + for (const prefixedRequests of serialized) { + const currentRequestType = RequestType[prefixedRequests[0] as keyof typeof RequestType]; + const requests = prefixedRequests.slice(1); + + if (prevRequestType !== undefined && prevRequestType >= currentRequestType) { + throw Error( + `Current request type must be larger than previous request type prevRequestType=${prevRequestType} currentRequestType=${currentRequestType}` + ); + } + + switch (currentRequestType) { + case RequestType.DEPOSIT_REQUEST_TYPE: { + result.deposits = deserializeDepositRequests(requests); + break; + } + case RequestType.WITHDRAWAL_REQUEST_TYPE: { + result.withdrawals = deserializeWithdrawalRequests(requests); + break; + } + case RequestType.CONSOLIDATION_REQUEST_TYPE: { + result.consolidations = deserializeConsolidationRequests(requests); + break; + } + } + prevRequestType = currentRequestType; + } + + return result; } export function deserializeExecutionPayloadBody(data: ExecutionPayloadBodyRpc | null): ExecutionPayloadBody | null { From 031214e4a84f5d9bf6f8067f2110d1cf03eaa776 Mon Sep 17 00:00:00 2001 From: NC <17676176+ensi321@users.noreply.github.com> Date: Fri, 13 Dec 2024 16:18:11 -0800 Subject: [PATCH 10/34] feat: implement partial spec changes for v1.5.0-alpha.10 (#7288) * Rename PartialPendingWithdrawal field * do not change creds type on consolidation * Use validator EB to process pending consolidation * lint --- packages/params/src/presets/mainnet.ts | 2 +- packages/params/src/presets/minimal.ts | 2 +- .../src/block/processConsolidationRequest.ts | 14 +++----------- .../src/block/processWithdrawalRequest.ts | 2 +- .../src/block/processWithdrawals.ts | 10 ++++++---- .../src/epoch/processPendingConsolidations.ts | 6 ++++-- packages/state-transition/src/util/validator.ts | 2 +- packages/types/src/electra/sszTypes.ts | 2 +- .../validator/test/unit/utils/interopConfigs.ts | 8 ++++---- 9 files changed, 22 insertions(+), 26 deletions(-) diff --git a/packages/params/src/presets/mainnet.ts b/packages/params/src/presets/mainnet.ts index afbfd78eba95..b4136f83da29 100644 --- a/packages/params/src/presets/mainnet.ts +++ b/packages/params/src/presets/mainnet.ts @@ -131,6 +131,6 @@ export const mainnetPreset: BeaconPreset = { PENDING_DEPOSITS_LIMIT: 134217728, PENDING_PARTIAL_WITHDRAWALS_LIMIT: 134217728, PENDING_CONSOLIDATIONS_LIMIT: 262144, - MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD: 1, + MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD: 2, WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA: 4096, }; diff --git a/packages/params/src/presets/minimal.ts b/packages/params/src/presets/minimal.ts index d9be1b1468ab..4c2a56a11d82 100644 --- a/packages/params/src/presets/minimal.ts +++ b/packages/params/src/presets/minimal.ts @@ -132,6 +132,6 @@ export const minimalPreset: BeaconPreset = { PENDING_DEPOSITS_LIMIT: 134217728, PENDING_PARTIAL_WITHDRAWALS_LIMIT: 64, PENDING_CONSOLIDATIONS_LIMIT: 64, - MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD: 1, + MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD: 2, WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA: 4096, }; diff --git a/packages/state-transition/src/block/processConsolidationRequest.ts b/packages/state-transition/src/block/processConsolidationRequest.ts index ade22c7454db..8d3cfdc062c9 100644 --- a/packages/state-transition/src/block/processConsolidationRequest.ts +++ b/packages/state-transition/src/block/processConsolidationRequest.ts @@ -3,7 +3,7 @@ import {electra, ssz} from "@lodestar/types"; import {CachedBeaconStateElectra} from "../types.js"; import {hasEth1WithdrawalCredential} from "../util/capella.js"; -import {hasExecutionWithdrawalCredential, isPubkeyKnown, switchToCompoundingValidator} from "../util/electra.js"; +import {hasCompoundingWithdrawalCredential, isPubkeyKnown, switchToCompoundingValidator} from "../util/electra.js"; import {computeConsolidationEpochAndUpdateChurn} from "../util/epoch.js"; import {getConsolidationChurnLimit, getPendingBalanceToWithdraw, isActiveValidator} from "../util/validator.js"; @@ -49,11 +49,8 @@ export function processConsolidationRequest( const sourceWithdrawalAddress = sourceValidator.withdrawalCredentials.subarray(12); const currentEpoch = state.epochCtx.epoch; - // Verify withdrawal credentials - if ( - !hasExecutionWithdrawalCredential(sourceValidator.withdrawalCredentials) || - !hasExecutionWithdrawalCredential(targetValidator.withdrawalCredentials) - ) { + // Verify that target has compounding withdrawal credentials + if (!hasCompoundingWithdrawalCredential(targetValidator.withdrawalCredentials)) { return; } @@ -91,11 +88,6 @@ export function processConsolidationRequest( targetIndex, }); state.pendingConsolidations.push(pendingConsolidation); - - // Churn any target excess active balance of target and raise its max - if (hasEth1WithdrawalCredential(targetValidator.withdrawalCredentials)) { - switchToCompoundingValidator(state, targetIndex); - } } /** diff --git a/packages/state-transition/src/block/processWithdrawalRequest.ts b/packages/state-transition/src/block/processWithdrawalRequest.ts index 573c0a49dfc8..7dab10dbb32b 100644 --- a/packages/state-transition/src/block/processWithdrawalRequest.ts +++ b/packages/state-transition/src/block/processWithdrawalRequest.ts @@ -71,7 +71,7 @@ export function processWithdrawalRequest( const withdrawableEpoch = exitQueueEpoch + config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY; const pendingPartialWithdrawal = ssz.electra.PendingPartialWithdrawal.toViewDU({ - index: validatorIndex, + validatorIndex, amount: amountToWithdraw, withdrawableEpoch, }); diff --git a/packages/state-transition/src/block/processWithdrawals.ts b/packages/state-transition/src/block/processWithdrawals.ts index 7f9ab6aa53f1..d3d3a5d58d9e 100644 --- a/packages/state-transition/src/block/processWithdrawals.ts +++ b/packages/state-transition/src/block/processWithdrawals.ts @@ -123,19 +123,21 @@ export function getExpectedWithdrawals( break; } - const validator = validators.getReadonly(withdrawal.index); + const validator = validators.getReadonly(withdrawal.validatorIndex); if ( validator.exitEpoch === FAR_FUTURE_EPOCH && validator.effectiveBalance >= MIN_ACTIVATION_BALANCE && - balances.get(withdrawal.index) > MIN_ACTIVATION_BALANCE + balances.get(withdrawal.validatorIndex) > MIN_ACTIVATION_BALANCE ) { - const balanceOverMinActivationBalance = BigInt(balances.get(withdrawal.index) - MIN_ACTIVATION_BALANCE); + const balanceOverMinActivationBalance = BigInt( + balances.get(withdrawal.validatorIndex) - MIN_ACTIVATION_BALANCE + ); const withdrawableBalance = balanceOverMinActivationBalance < withdrawal.amount ? balanceOverMinActivationBalance : withdrawal.amount; withdrawals.push({ index: withdrawalIndex, - validatorIndex: withdrawal.index, + validatorIndex: withdrawal.validatorIndex, address: validator.withdrawalCredentials.subarray(12), amount: withdrawableBalance, }); diff --git a/packages/state-transition/src/epoch/processPendingConsolidations.ts b/packages/state-transition/src/epoch/processPendingConsolidations.ts index 0ec39409f8a7..f9afc7fb122b 100644 --- a/packages/state-transition/src/epoch/processPendingConsolidations.ts +++ b/packages/state-transition/src/epoch/processPendingConsolidations.ts @@ -34,8 +34,10 @@ export function processPendingConsolidations(state: CachedBeaconStateElectra, ca break; } // Move active balance to target. Excess balance is withdrawable. - const maxEffectiveBalance = getMaxEffectiveBalance(state.validators.getReadonly(sourceIndex).withdrawalCredentials); - const sourceEffectiveBalance = Math.min(state.balances.get(sourceIndex), maxEffectiveBalance); + const sourceEffectiveBalance = Math.min( + state.balances.get(sourceIndex), + state.validators.getReadonly(sourceIndex).effectiveBalance + ); decreaseBalance(state, sourceIndex, sourceEffectiveBalance); increaseBalance(state, targetIndex, sourceEffectiveBalance); if (cachedBalances) { diff --git a/packages/state-transition/src/util/validator.ts b/packages/state-transition/src/util/validator.ts index 555b8a09b614..071d936ead23 100644 --- a/packages/state-transition/src/util/validator.ts +++ b/packages/state-transition/src/util/validator.ts @@ -85,6 +85,6 @@ export function getMaxEffectiveBalance(withdrawalCredentials: Uint8Array): numbe export function getPendingBalanceToWithdraw(state: CachedBeaconStateElectra, validatorIndex: ValidatorIndex): number { return state.pendingPartialWithdrawals .getAllReadonly() - .filter((item) => item.index === validatorIndex) + .filter((item) => item.validatorIndex === validatorIndex) .reduce((total, item) => total + Number(item.amount), 0); } diff --git a/packages/types/src/electra/sszTypes.ts b/packages/types/src/electra/sszTypes.ts index 238e6cc29c76..081853b26ade 100644 --- a/packages/types/src/electra/sszTypes.ts +++ b/packages/types/src/electra/sszTypes.ts @@ -280,7 +280,7 @@ export const PendingDeposits = new ListCompositeType(PendingDeposit, PENDING_DEP export const PendingPartialWithdrawal = new ContainerType( { - index: ValidatorIndex, + validatorIndex: ValidatorIndex, amount: Gwei, withdrawableEpoch: Epoch, }, diff --git a/packages/validator/test/unit/utils/interopConfigs.ts b/packages/validator/test/unit/utils/interopConfigs.ts index 4805154b4b64..a575b796e095 100644 --- a/packages/validator/test/unit/utils/interopConfigs.ts +++ b/packages/validator/test/unit/utils/interopConfigs.ts @@ -126,7 +126,7 @@ export const lighthouseHoleskyConfig = { PENDING_DEPOSITS_LIMIT: "134217728", PENDING_PARTIAL_WITHDRAWALS_LIMIT: "134217728", PENDING_CONSOLIDATIONS_LIMIT: "262144", - MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD: "1", + MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD: "2", }; export const prysmHoleskyConfig = { @@ -266,7 +266,7 @@ export const prysmHoleskyConfig = { PENDING_DEPOSITS_LIMIT: "134217728", PENDING_PARTIAL_WITHDRAWALS_LIMIT: "134217728", PENDING_CONSOLIDATIONS_LIMIT: "262144", - MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD: "1", + MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD: "2", }; export const tekuHoleskyConfig = { @@ -406,7 +406,7 @@ export const tekuHoleskyConfig = { PENDING_DEPOSITS_LIMIT: "134217728", PENDING_PARTIAL_WITHDRAWALS_LIMIT: "134217728", PENDING_CONSOLIDATIONS_LIMIT: "262144", - MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD: "1", + MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD: "2", }; export const nimbusHoleskyConfig = { @@ -549,5 +549,5 @@ export const nimbusHoleskyConfig = { PENDING_DEPOSITS_LIMIT: "134217728", PENDING_PARTIAL_WITHDRAWALS_LIMIT: "134217728", PENDING_CONSOLIDATIONS_LIMIT: "262144", - MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD: "1", + MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD: "2", }; From cd7b1b42482f6526866639a32d6a0c333b60b240 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Wed, 18 Dec 2024 21:32:53 +0000 Subject: [PATCH 11/34] feat: use 16-bit random value to compute validator indices (#7311) --- packages/state-transition/src/util/seed.ts | 130 +++++++++++++-------- 1 file changed, 79 insertions(+), 51 deletions(-) diff --git a/packages/state-transition/src/util/seed.ts b/packages/state-transition/src/util/seed.ts index 129cf6bfaf72..b96b0616e7b7 100644 --- a/packages/state-transition/src/util/seed.ts +++ b/packages/state-transition/src/util/seed.ts @@ -12,7 +12,7 @@ import { SYNC_COMMITTEE_SIZE, } from "@lodestar/params"; import {Bytes32, DomainType, Epoch, ValidatorIndex} from "@lodestar/types"; -import {assert, bytesToBigInt, intToBytes} from "@lodestar/utils"; +import {assert, bytesToBigInt, bytesToInt, intToBytes} from "@lodestar/utils"; import {EffectiveBalanceIncrements} from "../cache/effectiveBalanceIncrements.js"; import {BeaconStateAllForks} from "../types.js"; import {computeStartSlotAtEpoch} from "./epoch.js"; @@ -57,30 +57,40 @@ export function computeProposerIndex( throw Error("Validator indices must not be empty"); } - // TODO: Inline outside this function - const MAX_RANDOM_BYTE = 2 ** 8 - 1; - const MAX_EFFECTIVE_BALANCE_INCREMENT = - fork >= ForkSeq.electra - ? MAX_EFFECTIVE_BALANCE_ELECTRA / EFFECTIVE_BALANCE_INCREMENT - : MAX_EFFECTIVE_BALANCE / EFFECTIVE_BALANCE_INCREMENT; - - let i = 0; - /* eslint-disable-next-line no-constant-condition */ - while (true) { - const candidateIndex = indices[computeShuffledIndex(i % indices.length, indices.length, seed)]; - const randByte = digest( - Buffer.concat([ - seed, - // - intToBytes(Math.floor(i / 32), 8, "le"), - ]) - )[i % 32]; - - const effectiveBalanceIncrement = effectiveBalanceIncrements[candidateIndex]; - if (effectiveBalanceIncrement * MAX_RANDOM_BYTE >= MAX_EFFECTIVE_BALANCE_INCREMENT * randByte) { - return candidateIndex; + if (fork >= ForkSeq.electra) { + const MAX_RANDOM_VALUE = 2 ** 16 - 1; + const MAX_EFFECTIVE_BALANCE_INCREMENT = MAX_EFFECTIVE_BALANCE_ELECTRA / EFFECTIVE_BALANCE_INCREMENT; + + let i = 0; + while (true) { + const candidateIndex = indices[computeShuffledIndex(i % indices.length, indices.length, seed)]; + const randomBytes = digest(Buffer.concat([seed, intToBytes(Math.floor(i / 16), 8, "le")])); + const offset = (i % 16) * 2; + const randomValue = bytesToInt(randomBytes.subarray(offset, offset + 2)); + + const effectiveBalanceIncrement = effectiveBalanceIncrements[candidateIndex]; + if (effectiveBalanceIncrement * MAX_RANDOM_VALUE >= MAX_EFFECTIVE_BALANCE_INCREMENT * randomValue) { + return candidateIndex; + } + + i += 1; + } + } else { + const MAX_RANDOM_BYTE = 2 ** 8 - 1; + const MAX_EFFECTIVE_BALANCE_INCREMENT = MAX_EFFECTIVE_BALANCE / EFFECTIVE_BALANCE_INCREMENT; + + let i = 0; + while (true) { + const candidateIndex = indices[computeShuffledIndex(i % indices.length, indices.length, seed)]; + const randomByte = digest(Buffer.concat([seed, intToBytes(Math.floor(i / 32), 8, "le")]))[i % 32]; + + const effectiveBalanceIncrement = effectiveBalanceIncrements[candidateIndex]; + if (effectiveBalanceIncrement * MAX_RANDOM_BYTE >= MAX_EFFECTIVE_BALANCE_INCREMENT * randomByte) { + return candidateIndex; + } + + i += 1; } - i += 1; } } @@ -100,37 +110,55 @@ export function getNextSyncCommitteeIndices( activeValidatorIndices: ArrayLike, effectiveBalanceIncrements: EffectiveBalanceIncrements ): ValidatorIndex[] { - // TODO: Bechmark if it's necessary to inline outside of this function - const MAX_RANDOM_BYTE = 2 ** 8 - 1; - const MAX_EFFECTIVE_BALANCE_INCREMENT = - fork >= ForkSeq.electra - ? MAX_EFFECTIVE_BALANCE_ELECTRA / EFFECTIVE_BALANCE_INCREMENT - : MAX_EFFECTIVE_BALANCE / EFFECTIVE_BALANCE_INCREMENT; - - const epoch = computeEpochAtSlot(state.slot) + 1; - - const activeValidatorCount = activeValidatorIndices.length; - const seed = getSeed(state, epoch, DOMAIN_SYNC_COMMITTEE); - let i = 0; const syncCommitteeIndices = []; - while (syncCommitteeIndices.length < SYNC_COMMITTEE_SIZE) { - const shuffledIndex = computeShuffledIndex(i % activeValidatorCount, activeValidatorCount, seed); - const candidateIndex = activeValidatorIndices[shuffledIndex]; - const randByte = digest( - Buffer.concat([ - seed, - // - intToBytes(Math.floor(i / 32), 8, "le"), - ]) - )[i % 32]; - - const effectiveBalanceIncrement = effectiveBalanceIncrements[candidateIndex]; - if (effectiveBalanceIncrement * MAX_RANDOM_BYTE >= MAX_EFFECTIVE_BALANCE_INCREMENT * randByte) { - syncCommitteeIndices.push(candidateIndex); + + if (fork >= ForkSeq.electra) { + const MAX_RANDOM_VALUE = 2 ** 16 - 1; + const MAX_EFFECTIVE_BALANCE_INCREMENT = MAX_EFFECTIVE_BALANCE_ELECTRA / EFFECTIVE_BALANCE_INCREMENT; + + const epoch = computeEpochAtSlot(state.slot) + 1; + const activeValidatorCount = activeValidatorIndices.length; + const seed = getSeed(state, epoch, DOMAIN_SYNC_COMMITTEE); + + let i = 0; + while (syncCommitteeIndices.length < SYNC_COMMITTEE_SIZE) { + const shuffledIndex = computeShuffledIndex(i % activeValidatorCount, activeValidatorCount, seed); + const candidateIndex = activeValidatorIndices[shuffledIndex]; + const randomBytes = digest(Buffer.concat([seed, intToBytes(Math.floor(i / 16), 8, "le")])); + const offset = (i % 16) * 2; + const randomValue = bytesToInt(randomBytes.subarray(offset, offset + 2)); + + const effectiveBalanceIncrement = effectiveBalanceIncrements[candidateIndex]; + if (effectiveBalanceIncrement * MAX_RANDOM_VALUE >= MAX_EFFECTIVE_BALANCE_INCREMENT * randomValue) { + syncCommitteeIndices.push(candidateIndex); + } + + i += 1; } + } else { + const MAX_RANDOM_BYTE = 2 ** 8 - 1; + const MAX_EFFECTIVE_BALANCE_INCREMENT = MAX_EFFECTIVE_BALANCE / EFFECTIVE_BALANCE_INCREMENT; + + const epoch = computeEpochAtSlot(state.slot) + 1; + + const activeValidatorCount = activeValidatorIndices.length; + const seed = getSeed(state, epoch, DOMAIN_SYNC_COMMITTEE); - i++; + let i = 0; + while (syncCommitteeIndices.length < SYNC_COMMITTEE_SIZE) { + const shuffledIndex = computeShuffledIndex(i % activeValidatorCount, activeValidatorCount, seed); + const candidateIndex = activeValidatorIndices[shuffledIndex]; + const randomByte = digest(Buffer.concat([seed, intToBytes(Math.floor(i / 32), 8, "le")]))[i % 32]; + + const effectiveBalanceIncrement = effectiveBalanceIncrements[candidateIndex]; + if (effectiveBalanceIncrement * MAX_RANDOM_BYTE >= MAX_EFFECTIVE_BALANCE_INCREMENT * randomByte) { + syncCommitteeIndices.push(candidateIndex); + } + + i += 1; + } } + return syncCommitteeIndices; } From 71e73b2fb24e41205b4c9e7627400360047f7091 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Thu, 19 Dec 2024 11:56:33 +0000 Subject: [PATCH 12/34] Bump CL spec version to alpha.10 --- packages/beacon-node/test/spec/specTestVersioning.ts | 2 +- packages/state-transition/src/util/seed.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/beacon-node/test/spec/specTestVersioning.ts b/packages/beacon-node/test/spec/specTestVersioning.ts index daaa21128fcf..dd2a39903d82 100644 --- a/packages/beacon-node/test/spec/specTestVersioning.ts +++ b/packages/beacon-node/test/spec/specTestVersioning.ts @@ -14,7 +14,7 @@ import {DownloadTestsOptions} from "@lodestar/spec-test-util/downloadTests"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); export const ethereumConsensusSpecsTests: DownloadTestsOptions = { - specVersion: "v1.5.0-alpha.9", + specVersion: "v1.5.0-alpha.10", // Target directory is the host package root: 'packages/*/spec-tests' outputDir: path.join(__dirname, "../../spec-tests"), specTestsRepoUrl: "https://github.com/ethereum/consensus-spec-tests", diff --git a/packages/state-transition/src/util/seed.ts b/packages/state-transition/src/util/seed.ts index b96b0616e7b7..70604ac21fcc 100644 --- a/packages/state-transition/src/util/seed.ts +++ b/packages/state-transition/src/util/seed.ts @@ -140,7 +140,6 @@ export function getNextSyncCommitteeIndices( const MAX_EFFECTIVE_BALANCE_INCREMENT = MAX_EFFECTIVE_BALANCE / EFFECTIVE_BALANCE_INCREMENT; const epoch = computeEpochAtSlot(state.slot) + 1; - const activeValidatorCount = activeValidatorIndices.length; const seed = getSeed(state, epoch, DOMAIN_SYNC_COMMITTEE); From 4b8de08ba9056681b33b7ced342c0b6aa9a4e04f Mon Sep 17 00:00:00 2001 From: NC <17676176+ensi321@users.noreply.github.com> Date: Thu, 19 Dec 2024 04:13:40 -0800 Subject: [PATCH 13/34] fix: parsing ExecutionRequests from EL (#7314) * Fix execution request parsing * lint * Rename type --------- Co-authored-by: Nico Flaig --- .../src/api/impl/config/constants.ts | 6 +++ .../src/execution/engine/interface.ts | 14 +++--- .../beacon-node/src/execution/engine/types.ts | 44 ++++++++++++++----- packages/params/src/index.ts | 3 ++ 4 files changed, 49 insertions(+), 18 deletions(-) diff --git a/packages/beacon-node/src/api/impl/config/constants.ts b/packages/beacon-node/src/api/impl/config/constants.ts index 6b390727cec3..48ca02bf1174 100644 --- a/packages/beacon-node/src/api/impl/config/constants.ts +++ b/packages/beacon-node/src/api/impl/config/constants.ts @@ -4,7 +4,9 @@ import { BLOB_TX_TYPE, BLS_WITHDRAWAL_PREFIX, COMPOUNDING_WITHDRAWAL_PREFIX, + CONSOLIDATION_REQUEST_TYPE, DEPOSIT_CONTRACT_TREE_DEPTH, + DEPOSIT_REQUEST_TYPE, DOMAIN_AGGREGATE_AND_PROOF, DOMAIN_APPLICATION_BUILDER, DOMAIN_APPLICATION_MASK, @@ -40,6 +42,7 @@ import { UNSET_DEPOSIT_REQUESTS_START_INDEX, VERSIONED_HASH_VERSION_KZG, WEIGHT_DENOMINATOR, + WITHDRAWAL_REQUEST_TYPE, } from "@lodestar/params"; /** @@ -108,4 +111,7 @@ export const specConstants = { // electra UNSET_DEPOSIT_REQUESTS_START_INDEX, FULL_EXIT_REQUEST_AMOUNT, + DEPOSIT_REQUEST_TYPE, + WITHDRAWAL_REQUEST_TYPE, + CONSOLIDATION_REQUEST_TYPE, }; diff --git a/packages/beacon-node/src/execution/engine/interface.ts b/packages/beacon-node/src/execution/engine/interface.ts index f62bf50d8074..40ca06c4d2c2 100644 --- a/packages/beacon-node/src/execution/engine/interface.ts +++ b/packages/beacon-node/src/execution/engine/interface.ts @@ -1,4 +1,4 @@ -import {ForkName} from "@lodestar/params"; +import {CONSOLIDATION_REQUEST_TYPE, DEPOSIT_REQUEST_TYPE, ForkName, WITHDRAWAL_REQUEST_TYPE} from "@lodestar/params"; import {ExecutionPayload, ExecutionRequests, Root, RootHex, Wei, capella} from "@lodestar/types"; import {Blob, BlobAndProof, KZGCommitment, KZGProof} from "@lodestar/types/deneb"; @@ -58,11 +58,13 @@ export enum ClientCode { XX = "XX", // unknown } -// Represents request type in ExecutionRequests defined in EIP-7685 -export enum RequestType { - DEPOSIT_REQUEST_TYPE = 0, // 0x00 - WITHDRAWAL_REQUEST_TYPE = 1, // 0x01 - CONSOLIDATION_REQUEST_TYPE = 2, // 0x02 +export type ExecutionRequestType = + | typeof DEPOSIT_REQUEST_TYPE + | typeof WITHDRAWAL_REQUEST_TYPE + | typeof CONSOLIDATION_REQUEST_TYPE; + +export function isExecutionRequestType(type: number): type is ExecutionRequestType { + return type === DEPOSIT_REQUEST_TYPE || type === WITHDRAWAL_REQUEST_TYPE || type === CONSOLIDATION_REQUEST_TYPE; } export type ExecutePayloadResponse = diff --git a/packages/beacon-node/src/execution/engine/types.ts b/packages/beacon-node/src/execution/engine/types.ts index def5831b7609..76130dc3ec46 100644 --- a/packages/beacon-node/src/execution/engine/types.ts +++ b/packages/beacon-node/src/execution/engine/types.ts @@ -1,9 +1,12 @@ import { BYTES_PER_FIELD_ELEMENT, BYTES_PER_LOGS_BLOOM, + CONSOLIDATION_REQUEST_TYPE, + DEPOSIT_REQUEST_TYPE, FIELD_ELEMENTS_PER_BLOB, ForkName, ForkSeq, + WITHDRAWAL_REQUEST_TYPE, } from "@lodestar/params"; import {ExecutionPayload, ExecutionRequests, Root, Wei, bellatrix, capella, deneb, electra, ssz} from "@lodestar/types"; import {BlobAndProof} from "@lodestar/types/deneb"; @@ -17,7 +20,14 @@ import { quantityToBigint, quantityToNum, } from "../../eth1/provider/utils.js"; -import {BlobsBundle, ExecutionPayloadStatus, PayloadAttributes, RequestType, VersionedHashes} from "./interface.js"; +import { + BlobsBundle, + ExecutionPayloadStatus, + ExecutionRequestType, + PayloadAttributes, + VersionedHashes, + isExecutionRequestType, +} from "./interface.js"; import {WithdrawalV1} from "./payloadIdCache.js"; export type EngineApiRpcParamTypes = { @@ -407,7 +417,7 @@ export function deserializeWithdrawal(serialized: WithdrawalRpc): capella.Withdr /** * Prepend a single-byte requestType to requestsBytes */ -function prefixRequests(requestsBytes: Uint8Array, requestType: RequestType): Uint8Array { +function prefixRequests(requestsBytes: Uint8Array, requestType: ExecutionRequestType): Uint8Array { const prefixedRequests = new Uint8Array(1 + requestsBytes.length); prefixedRequests[0] = requestType; prefixedRequests.set(requestsBytes, 1); @@ -417,7 +427,7 @@ function prefixRequests(requestsBytes: Uint8Array, requestType: RequestType): Ui function serializeDepositRequests(depositRequests: electra.DepositRequests): DepositRequestsRpc { const requestsBytes = ssz.electra.DepositRequests.serialize(depositRequests); - return bytesToData(prefixRequests(requestsBytes, RequestType.DEPOSIT_REQUEST_TYPE)); + return bytesToData(prefixRequests(requestsBytes, DEPOSIT_REQUEST_TYPE)); } function deserializeDepositRequests(serialized: DepositRequestsRpc): electra.DepositRequests { @@ -426,7 +436,7 @@ function deserializeDepositRequests(serialized: DepositRequestsRpc): electra.Dep function serializeWithdrawalRequests(withdrawalRequests: electra.WithdrawalRequests): WithdrawalRequestsRpc { const requestsBytes = ssz.electra.WithdrawalRequests.serialize(withdrawalRequests); - return bytesToData(prefixRequests(requestsBytes, RequestType.WITHDRAWAL_REQUEST_TYPE)); + return bytesToData(prefixRequests(requestsBytes, WITHDRAWAL_REQUEST_TYPE)); } function deserializeWithdrawalRequests(serialized: WithdrawalRequestsRpc): electra.WithdrawalRequests { @@ -437,7 +447,7 @@ function serializeConsolidationRequests( consolidationRequests: electra.ConsolidationRequests ): ConsolidationRequestsRpc { const requestsBytes = ssz.electra.ConsolidationRequests.serialize(consolidationRequests); - return bytesToData(prefixRequests(requestsBytes, RequestType.CONSOLIDATION_REQUEST_TYPE)); + return bytesToData(prefixRequests(requestsBytes, CONSOLIDATION_REQUEST_TYPE)); } function deserializeConsolidationRequests(serialized: ConsolidationRequestsRpc): electra.ConsolidationRequests { @@ -478,11 +488,21 @@ export function deserializeExecutionRequests(serialized: ExecutionRequestsRpc): return result; } - let prevRequestType: RequestType | undefined; + let prevRequestType: ExecutionRequestType | undefined; + + for (let prefixedRequests of serialized) { + // Slice out 0x so it is easier to extract request type + if (prefixedRequests.startsWith("0x")) { + prefixedRequests = prefixedRequests.slice(2); + } + + const currentRequestType = Number(prefixedRequests.substring(0, 2)); + + if (!isExecutionRequestType(currentRequestType)) { + throw Error(`Invalid request type currentRequestType=${prefixedRequests.substring(0, 2)}`); + } - for (const prefixedRequests of serialized) { - const currentRequestType = RequestType[prefixedRequests[0] as keyof typeof RequestType]; - const requests = prefixedRequests.slice(1); + const requests = prefixedRequests.slice(2); if (prevRequestType !== undefined && prevRequestType >= currentRequestType) { throw Error( @@ -491,15 +511,15 @@ export function deserializeExecutionRequests(serialized: ExecutionRequestsRpc): } switch (currentRequestType) { - case RequestType.DEPOSIT_REQUEST_TYPE: { + case DEPOSIT_REQUEST_TYPE: { result.deposits = deserializeDepositRequests(requests); break; } - case RequestType.WITHDRAWAL_REQUEST_TYPE: { + case WITHDRAWAL_REQUEST_TYPE: { result.withdrawals = deserializeWithdrawalRequests(requests); break; } - case RequestType.CONSOLIDATION_REQUEST_TYPE: { + case CONSOLIDATION_REQUEST_TYPE: { result.consolidations = deserializeConsolidationRequests(requests); break; } diff --git a/packages/params/src/index.ts b/packages/params/src/index.ts index 64d3b64dbd60..1fb875801ba9 100644 --- a/packages/params/src/index.ts +++ b/packages/params/src/index.ts @@ -270,3 +270,6 @@ export const FINALIZED_ROOT_INDEX_ELECTRA = 41; export const NEXT_SYNC_COMMITTEE_GINDEX_ELECTRA = 87; export const NEXT_SYNC_COMMITTEE_DEPTH_ELECTRA = 6; export const NEXT_SYNC_COMMITTEE_INDEX_ELECTRA = 23; +export const DEPOSIT_REQUEST_TYPE = 0x00; +export const WITHDRAWAL_REQUEST_TYPE = 0x01; +export const CONSOLIDATION_REQUEST_TYPE = 0x02; From 265579f0e710428f663b84addd44e8159207eb5f Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Mon, 6 Jan 2025 22:27:39 +0000 Subject: [PATCH 14/34] test: ensure execution requests are de-/serialized according to EIP-7685 (#7330) * test: ensure execution requests are de-/serialized according to EIP-7685 * Fix format * Add test case where deposits, withdrawals and consolidations are all empty * Remove map --- .../test/unit/execution/engine/types.test.ts | 131 ++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 packages/beacon-node/test/unit/execution/engine/types.test.ts diff --git a/packages/beacon-node/test/unit/execution/engine/types.test.ts b/packages/beacon-node/test/unit/execution/engine/types.test.ts new file mode 100644 index 000000000000..6943c7c21a34 --- /dev/null +++ b/packages/beacon-node/test/unit/execution/engine/types.test.ts @@ -0,0 +1,131 @@ +import {CONSOLIDATION_REQUEST_TYPE, DEPOSIT_REQUEST_TYPE, WITHDRAWAL_REQUEST_TYPE} from "@lodestar/params"; +import {ExecutionRequests, ssz} from "@lodestar/types"; +import {fromHex, strip0xPrefix} from "@lodestar/utils"; +import {describe, expect, it} from "vitest"; +import {deserializeExecutionRequests, serializeExecutionRequests} from "../../../../src/execution/engine/types.js"; + +describe("execution / engine / types", () => { + describe("serializeExecutionRequests", () => { + it("should serialize execution requests according to EIP-7685", () => { + const executionRequests: ExecutionRequests = { + deposits: [ssz.electra.DepositRequest.defaultValue()], + withdrawals: [ssz.electra.WithdrawalRequest.defaultValue()], + consolidations: [ssz.electra.ConsolidationRequest.defaultValue()], + }; + + const serialized = serializeExecutionRequests(executionRequests).map(strip0xPrefix); + + // Assert 1-byte request_type prefix is set correctly + expect(serialized.length).toBe(3); + expect(Number(serialized[0].substring(0, 2))).toBe(DEPOSIT_REQUEST_TYPE); + expect(Number(serialized[1].substring(0, 2))).toBe(WITHDRAWAL_REQUEST_TYPE); + expect(Number(serialized[2].substring(0, 2))).toBe(CONSOLIDATION_REQUEST_TYPE); + + // Assert execution requests can be deserialized + expect(ssz.electra.DepositRequests.deserialize(fromHex(serialized[0].slice(2)))).toEqual( + executionRequests.deposits + ); + expect(ssz.electra.WithdrawalRequests.deserialize(fromHex(serialized[1].slice(2)))).toEqual( + executionRequests.withdrawals + ); + expect(ssz.electra.ConsolidationRequests.deserialize(fromHex(serialized[2].slice(2)))).toEqual( + executionRequests.consolidations + ); + }); + + it("should omit empty requests when serializing data", () => { + const executionRequests: ExecutionRequests = { + deposits: [ssz.electra.DepositRequest.defaultValue()], + withdrawals: [], + consolidations: [ssz.electra.ConsolidationRequest.defaultValue()], + }; + + const serialized = serializeExecutionRequests(executionRequests).map(strip0xPrefix); + + // Assert withdrawals are omitted + expect(serialized.length).toBe(2); + expect(Number(serialized[0].substring(0, 2))).toBe(DEPOSIT_REQUEST_TYPE); + expect(Number(serialized[1].substring(0, 2))).toBe(CONSOLIDATION_REQUEST_TYPE); + + // Assert execution requests can be deserialized + expect(ssz.electra.DepositRequests.deserialize(fromHex(serialized[0].slice(2)))).toEqual( + executionRequests.deposits + ); + expect(ssz.electra.ConsolidationRequests.deserialize(fromHex(serialized[1].slice(2)))).toEqual( + executionRequests.consolidations + ); + }); + + it("should return an empty array if all requests are empty", () => { + const executionRequests: ExecutionRequests = { + deposits: [], + withdrawals: [], + consolidations: [], + }; + + const serialized = serializeExecutionRequests(executionRequests); + + expect(serialized.length).toBe(0); + }); + }); + + describe("deserializeExecutionRequests", () => { + // From https://github.com/ethereum/execution-apis/blob/f6a6f52bccdb05f8b2f894a56fe1232432069d65/src/engine/openrpc/methods/payload.yaml#L553-L556 + const serializedRequests: string[] = [ + "0x0096a96086cff07df17668f35f7418ef8798079167e3f4f9b72ecde17b28226137cf454ab1dd20ef5d924786ab3483c2f9003f" + + "5102dabe0a27b1746098d1dc17a5d3fbd478759fea9287e4e419b3c3cef20100000000000000b1acdb2c4d3df3f1b8d3bfd334" + + "21660df358d84d78d16c4603551935f4b67643373e7eb63dcb16ec359be0ec41fee33b03a16e80745f2374ff1d3c352508ac5d" + + "857c6476d3c3bcf7e6ca37427c9209f17be3af5264c0e2132b3dd1156c28b4e9f000000000000000a5c85a60ba2905c215f6a1" + + "2872e62b1ee037051364244043a5f639aa81b04a204c55e7cc851f29c7c183be253ea1510b001db70c485b6264692f26b8aeaa" + + "b5b0c384180df8e2184a21a808a3ec8e86ca01000000000000009561731785b48cf1886412234531e4940064584463e96ac63a" + + "1a154320227e333fb51addc4a89b7e0d3f862d7c1fd4ea03bd8eb3d8806f1e7daf591cbbbb92b0beb74d13c01617f22c5026b4" + + "f9f9f294a8a7c32db895de3b01bee0132c9209e1f100000000000000", + "0x01a94f5374fce5edbc8e2a8697c15331677e6ebf0b85103a5617937691dfeeb89b86a80d5dc9e3c9d3a1a0e7ce311e26e0bb73" + + "2eabaa47ffa288f0d54de28209a62a7d29d0000000000000000000000000000000000000000000000000000010f698daeed734" + + "da114470da559bd4b4c7259e1f7952555241dcbc90cf194a2ef676fc6005f3672fada2a3645edb297a75530100000000000000", + "0x02a94f5374fce5edbc8e2a8697c15331677e6ebf0b85103a5617937691dfeeb89b86a80d5dc9e3c9d3a1a0e7ce311e26e0bb73" + + "2eabaa47ffa288f0d54de28209a62a7d29d098daeed734da114470da559bd4b4c7259e1f7952555241dcbc90cf194a2ef676fc" + + "6005f3672fada2a3645edb297a7553", + ]; + + it("should deserialize execution requests according to EIP-7685", () => { + const executionRequests = deserializeExecutionRequests(serializedRequests); + + expect(executionRequests.deposits.length).toBe(2); + expect(executionRequests.withdrawals.length).toBe(2); + expect(executionRequests.consolidations.length).toBe(1); + + expect(serializeExecutionRequests(executionRequests)).toEqual(serializedRequests); + }); + + it("should correctly deserialize if execution request is omitted", () => { + const serializedOmitted = [serializedRequests[0], serializedRequests[2]]; + + const executionRequests = deserializeExecutionRequests(serializedOmitted); + + expect(executionRequests.deposits.length).toBe(2); + expect(executionRequests.withdrawals.length).toBe(0); + expect(executionRequests.consolidations.length).toBe(1); + + expect(serializeExecutionRequests(executionRequests)).toEqual(serializedOmitted); + }); + + it("should throw an error if execution requests order is incorrect", () => { + const serializedUnordered = [serializedRequests[0], serializedRequests[2], serializedRequests[1]]; + + expect(() => deserializeExecutionRequests(serializedUnordered)).toThrow(); + }); + + it("should throw an error if execution request is missing type prefix", () => { + const serializedNoPrefix = [serializedRequests[0], `0x${serializedRequests[1].slice(4)}`]; + + expect(() => deserializeExecutionRequests(serializedNoPrefix)).toThrow(); + }); + + it("should throw an error if execution request has incorrect prefix", () => { + const serializedWrongPrefix = [serializedRequests[0], `0x05${serializedRequests[1].slice(4)}`]; + + expect(() => deserializeExecutionRequests(serializedWrongPrefix)).toThrow(); + }); + }); +}); From 42d69827c57ee6c5e948e306e7e21e177160e586 Mon Sep 17 00:00:00 2001 From: NC <17676176+ensi321@users.noreply.github.com> Date: Wed, 8 Jan 2025 22:29:52 -0800 Subject: [PATCH 15/34] feat: implement EIP-7691 increase blob throughput (#7309) * Init * Add reqresp v2 definition * Update validateGossipBlock * Partial commit * Reqresp. Add todos * polish * Fork-aware requestSszTypeByMethod * Fixed minimal constants * Bump config test version * Update blob sidecar subnet computation * Pass proper commitment limit to block gossip error * Update blob sidecar index check * Lint * Update kzg unit test * Subscribe to correct number of blob sidecar subnets * Refactor constants getter to constantsHelper * address comment * Pass fork as first arg * Update packages/state-transition/src/block/processExecutionPayload.ts * refactor: move helper to get max blobs per block to fork config (#7322) * Simplify type cast --------- Co-authored-by: Nico Flaig --- .../src/chain/validation/blobSidecar.ts | 21 ++++++++++++----- .../beacon-node/src/chain/validation/block.ts | 5 ++-- .../beacon-node/src/network/gossip/topic.ts | 6 ++++- packages/beacon-node/src/network/network.ts | 23 +++++++++++++++---- .../src/network/processor/gossipHandlers.ts | 3 ++- .../src/network/reqresp/ReqRespBeaconNode.ts | 11 ++++++++- .../reqresp/handlers/beaconBlocksByRange.ts | 1 + .../src/network/reqresp/handlers/index.ts | 6 +++-- .../src/network/reqresp/protocols.ts | 12 ++++++++++ .../src/network/reqresp/rateLimit.ts | 2 ++ .../beacon-node/src/network/reqresp/types.ts | 10 +++++--- packages/beacon-node/src/util/types.ts | 8 +++++-- .../beacon-node/test/unit/util/kzg.test.ts | 3 ++- .../config/src/chainConfig/configs/mainnet.ts | 6 ++++- .../config/src/chainConfig/configs/minimal.ts | 4 ++++ packages/config/src/chainConfig/types.ts | 6 +++++ packages/config/src/forkConfig/index.ts | 4 ++++ packages/config/src/forkConfig/types.ts | 2 ++ packages/params/src/index.ts | 4 ++-- packages/params/src/presets/minimal.ts | 4 ++-- .../test/e2e/ensure-config-is-synced.test.ts | 2 +- .../src/block/processExecutionPayload.ts | 10 ++++---- packages/validator/src/util/params.ts | 3 +++ 23 files changed, 122 insertions(+), 34 deletions(-) diff --git a/packages/beacon-node/src/chain/validation/blobSidecar.ts b/packages/beacon-node/src/chain/validation/blobSidecar.ts index b79db77e3931..78f40e407925 100644 --- a/packages/beacon-node/src/chain/validation/blobSidecar.ts +++ b/packages/beacon-node/src/chain/validation/blobSidecar.ts @@ -1,5 +1,10 @@ import {ChainConfig} from "@lodestar/config"; -import {KZG_COMMITMENT_INCLUSION_PROOF_DEPTH, KZG_COMMITMENT_SUBTREE_INDEX0} from "@lodestar/params"; +import { + ForkName, + KZG_COMMITMENT_INCLUSION_PROOF_DEPTH, + KZG_COMMITMENT_SUBTREE_INDEX0, + isForkPostElectra, +} from "@lodestar/params"; import {computeStartSlotAtEpoch, getBlockHeaderProposerSignatureSet} from "@lodestar/state-transition"; import {BlobIndex, Root, Slot, deneb, ssz} from "@lodestar/types"; import {toRootHex, verifyMerkleBranch} from "@lodestar/utils"; @@ -12,6 +17,7 @@ import {IBeaconChain} from "../interface.js"; import {RegenCaller} from "../regen/index.js"; export async function validateGossipBlobSidecar( + fork: ForkName, chain: IBeaconChain, blobSidecar: deneb.BlobSidecar, subnet: number @@ -19,16 +25,17 @@ export async function validateGossipBlobSidecar( const blobSlot = blobSidecar.signedBlockHeader.message.slot; // [REJECT] The sidecar's index is consistent with `MAX_BLOBS_PER_BLOCK` -- i.e. `blob_sidecar.index < MAX_BLOBS_PER_BLOCK`. - if (blobSidecar.index >= chain.config.MAX_BLOBS_PER_BLOCK) { + const maxBlobsPerBlock = chain.config.getMaxBlobsPerBlock(fork); + if (blobSidecar.index >= maxBlobsPerBlock) { throw new BlobSidecarGossipError(GossipAction.REJECT, { code: BlobSidecarErrorCode.INDEX_TOO_LARGE, blobIdx: blobSidecar.index, - maxBlobsPerBlock: chain.config.MAX_BLOBS_PER_BLOCK, + maxBlobsPerBlock, }); } // [REJECT] The sidecar is for the correct subnet -- i.e. `compute_subnet_for_blob_sidecar(sidecar.index) == subnet_id`. - if (computeSubnetForBlobSidecar(blobSidecar.index, chain.config) !== subnet) { + if (computeSubnetForBlobSidecar(fork, chain.config, blobSidecar.index) !== subnet) { throw new BlobSidecarGossipError(GossipAction.REJECT, { code: BlobSidecarErrorCode.INVALID_INDEX, blobIdx: blobSidecar.index, @@ -236,6 +243,8 @@ function validateInclusionProof(blobSidecar: deneb.BlobSidecar): boolean { ); } -function computeSubnetForBlobSidecar(blobIndex: BlobIndex, config: ChainConfig): number { - return blobIndex % config.BLOB_SIDECAR_SUBNET_COUNT; +function computeSubnetForBlobSidecar(fork: ForkName, config: ChainConfig, blobIndex: BlobIndex): number { + return ( + blobIndex % (isForkPostElectra(fork) ? config.BLOB_SIDECAR_SUBNET_COUNT_ELECTRA : config.BLOB_SIDECAR_SUBNET_COUNT) + ); } diff --git a/packages/beacon-node/src/chain/validation/block.ts b/packages/beacon-node/src/chain/validation/block.ts index b2623aa4f79d..2b18999db402 100644 --- a/packages/beacon-node/src/chain/validation/block.ts +++ b/packages/beacon-node/src/chain/validation/block.ts @@ -113,11 +113,12 @@ export async function validateGossipBlock( // [REJECT] The length of KZG commitments is less than or equal to the limitation defined in Consensus Layer -- i.e. validate that len(body.signed_beacon_block.message.blob_kzg_commitments) <= MAX_BLOBS_PER_BLOCK if (isForkBlobs(fork)) { const blobKzgCommitmentsLen = (block as deneb.BeaconBlock).body.blobKzgCommitments.length; - if (blobKzgCommitmentsLen > chain.config.MAX_BLOBS_PER_BLOCK) { + const maxBlobsPerBlock = chain.config.getMaxBlobsPerBlock(fork); + if (blobKzgCommitmentsLen > maxBlobsPerBlock) { throw new BlockGossipError(GossipAction.REJECT, { code: BlockErrorCode.TOO_MANY_KZG_COMMITMENTS, blobKzgCommitmentsLen, - commitmentLimit: chain.config.MAX_BLOBS_PER_BLOCK, + commitmentLimit: maxBlobsPerBlock, }); } } diff --git a/packages/beacon-node/src/network/gossip/topic.ts b/packages/beacon-node/src/network/gossip/topic.ts index 83603226a94c..bf44dd90b8af 100644 --- a/packages/beacon-node/src/network/gossip/topic.ts +++ b/packages/beacon-node/src/network/gossip/topic.ts @@ -230,7 +230,11 @@ export function getCoreTopicsAtFork( // After Deneb also track blob_sidecar_{subnet_id} if (ForkSeq[fork] >= ForkSeq.deneb) { - for (let subnet = 0; subnet < config.BLOB_SIDECAR_SUBNET_COUNT; subnet++) { + const subnetCount = isForkPostElectra(fork) + ? config.BLOB_SIDECAR_SUBNET_COUNT_ELECTRA + : config.BLOB_SIDECAR_SUBNET_COUNT; + + for (let subnet = 0; subnet < subnetCount; subnet++) { topics.push({type: GossipType.blob_sidecar, subnet}); } } diff --git a/packages/beacon-node/src/network/network.ts b/packages/beacon-node/src/network/network.ts index 2faf2371e5c7..ae06cbab715f 100644 --- a/packages/beacon-node/src/network/network.ts +++ b/packages/beacon-node/src/network/network.ts @@ -4,7 +4,7 @@ import {PeerId} from "@libp2p/interface"; import {routes} from "@lodestar/api"; import {BeaconConfig} from "@lodestar/config"; import {LoggerNode} from "@lodestar/logger/node"; -import {ForkSeq} from "@lodestar/params"; +import {ForkSeq, isForkPostElectra} from "@lodestar/params"; import {ResponseIncoming} from "@lodestar/reqresp"; import {computeStartSlotAtEpoch, computeTimeAtSlot} from "@lodestar/state-transition"; import { @@ -501,17 +501,29 @@ export class Network implements INetwork { peerId: PeerIdStr, request: deneb.BlobSidecarsByRangeRequest ): Promise { + const fork = this.config.getForkName(request.startSlot); return collectMaxResponseTyped( - this.sendReqRespRequest(peerId, ReqRespMethod.BlobSidecarsByRange, [Version.V1], request), + this.sendReqRespRequest( + peerId, + ReqRespMethod.BlobSidecarsByRange, + [isForkPostElectra(fork) ? Version.V2 : Version.V1], + request + ), // request's count represent the slots, so the actual max count received could be slots * blobs per slot - request.count * this.config.MAX_BLOBS_PER_BLOCK, + request.count * this.config.getMaxBlobsPerBlock(fork), responseSszTypeByMethod[ReqRespMethod.BlobSidecarsByRange] ); } async sendBlobSidecarsByRoot(peerId: PeerIdStr, request: BlobSidecarsByRootRequest): Promise { + const fork = this.config.getForkName(this.clock.currentSlot); return collectMaxResponseTyped( - this.sendReqRespRequest(peerId, ReqRespMethod.BlobSidecarsByRoot, [Version.V1], request), + this.sendReqRespRequest( + peerId, + ReqRespMethod.BlobSidecarsByRoot, + [isForkPostElectra(fork) ? Version.V2 : Version.V1], + request + ), request.length, responseSszTypeByMethod[ReqRespMethod.BlobSidecarsByRoot] ); @@ -523,7 +535,8 @@ export class Network implements INetwork { versions: number[], request: Req ): AsyncIterable { - const requestType = requestSszTypeByMethod(this.config)[method]; + const fork = this.config.getForkName(this.clock.currentSlot); + const requestType = requestSszTypeByMethod(this.config, fork)[method]; const requestData = requestType ? requestType.serialize(request as never) : new Uint8Array(); // ReqResp outgoing request, emit from main thread to worker diff --git a/packages/beacon-node/src/network/processor/gossipHandlers.ts b/packages/beacon-node/src/network/processor/gossipHandlers.ts index 906d16b33d71..4646797c2425 100644 --- a/packages/beacon-node/src/network/processor/gossipHandlers.ts +++ b/packages/beacon-node/src/network/processor/gossipHandlers.ts @@ -186,6 +186,7 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand ): Promise { const blobBlockHeader = blobSidecar.signedBlockHeader.message; const slot = blobBlockHeader.slot; + const fork = config.getForkName(slot); const blockRoot = ssz.phase0.BeaconBlockHeader.hashTreeRoot(blobBlockHeader); const blockHex = prettyBytes(blockRoot); @@ -203,7 +204,7 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand ); try { - await validateGossipBlobSidecar(chain, blobSidecar, subnet); + await validateGossipBlobSidecar(fork, chain, blobSidecar, subnet); const recvToValidation = Date.now() / 1000 - seenTimestampSec; const validationTime = recvToValidation - recvToValLatency; diff --git a/packages/beacon-node/src/network/reqresp/ReqRespBeaconNode.ts b/packages/beacon-node/src/network/reqresp/ReqRespBeaconNode.ts index 96b5c6c0a776..72d9892faec8 100644 --- a/packages/beacon-node/src/network/reqresp/ReqRespBeaconNode.ts +++ b/packages/beacon-node/src/network/reqresp/ReqRespBeaconNode.ts @@ -207,7 +207,8 @@ export class ReqRespBeaconNode extends ReqResp { versions: number[], request: Req ): AsyncIterable { - const requestType = requestSszTypeByMethod(this.config)[method]; + const fork = ForkName[ForkSeq[this.currentRegisteredFork] as ForkName]; + const requestType = requestSszTypeByMethod(this.config, fork)[method]; const requestData = requestType ? requestType.serialize(request as never) : new Uint8Array(); return this.sendRequestWithoutEncoding(peerId, method, versions, requestData); } @@ -251,12 +252,20 @@ export class ReqRespBeaconNode extends ReqResp { } if (ForkSeq[fork] >= ForkSeq.deneb) { + // TODO Electra: Consider deprecating BlobSidecarsByRootV1 and BlobSidecarsByRangeV1 at fork boundary or after Electra is stable protocolsAtFork.push( [protocols.BlobSidecarsByRoot(this.config), this.getHandler(ReqRespMethod.BlobSidecarsByRoot)], [protocols.BlobSidecarsByRange(this.config), this.getHandler(ReqRespMethod.BlobSidecarsByRange)] ); } + if (ForkSeq[fork] >= ForkSeq.electra) { + protocolsAtFork.push( + [protocols.BlobSidecarsByRootV2(this.config), this.getHandler(ReqRespMethod.BlobSidecarsByRoot)], + [protocols.BlobSidecarsByRangeV2(this.config), this.getHandler(ReqRespMethod.BlobSidecarsByRange)] + ); + } + return protocolsAtFork; } diff --git a/packages/beacon-node/src/network/reqresp/handlers/beaconBlocksByRange.ts b/packages/beacon-node/src/network/reqresp/handlers/beaconBlocksByRange.ts index e8c19fb49628..620b98be63d8 100644 --- a/packages/beacon-node/src/network/reqresp/handlers/beaconBlocksByRange.ts +++ b/packages/beacon-node/src/network/reqresp/handlers/beaconBlocksByRange.ts @@ -85,6 +85,7 @@ export function validateBeaconBlocksByRangeRequest( // step > 1 is deprecated, see https://github.com/ethereum/consensus-specs/pull/2856 if (count > MAX_REQUEST_BLOCKS) { + // TODO: This is probably not right as `BeaconBlocksByRangeV2` takes at most `MAX_REQUEST_BLOCKS_DENEB` count = MAX_REQUEST_BLOCKS; } diff --git a/packages/beacon-node/src/network/reqresp/handlers/index.ts b/packages/beacon-node/src/network/reqresp/handlers/index.ts index 83f6620dbbd4..49b552b58178 100644 --- a/packages/beacon-node/src/network/reqresp/handlers/index.ts +++ b/packages/beacon-node/src/network/reqresp/handlers/index.ts @@ -1,9 +1,10 @@ +import {ForkName} from "@lodestar/params"; import {ProtocolHandler} from "@lodestar/reqresp"; import {ssz} from "@lodestar/types"; import {IBeaconChain} from "../../../chain/index.js"; import {IBeaconDb} from "../../../db/index.js"; import {BlobSidecarsByRootRequestType} from "../../../util/types.js"; -import {GetReqRespHandlerFn, ReqRespMethod} from "../types.js"; +import {GetReqRespHandlerFn, ReqRespMethod, Version} from "../types.js"; import {onBeaconBlocksByRange} from "./beaconBlocksByRange.js"; import {onBeaconBlocksByRoot} from "./beaconBlocksByRoot.js"; import {onBlobSidecarsByRange} from "./blobSidecarsByRange.js"; @@ -38,7 +39,8 @@ export function getReqRespHandlers({db, chain}: {db: IBeaconDb; chain: IBeaconCh return onBeaconBlocksByRoot(body, chain, db); }, [ReqRespMethod.BlobSidecarsByRoot]: (req) => { - const body = BlobSidecarsByRootRequestType(chain.config).deserialize(req.data); + const fork = req.version === Version.V2 ? ForkName.electra : ForkName.deneb; + const body = BlobSidecarsByRootRequestType(fork, chain.config).deserialize(req.data); return onBlobSidecarsByRoot(body, chain, db); }, [ReqRespMethod.BlobSidecarsByRange]: (req) => { diff --git a/packages/beacon-node/src/network/reqresp/protocols.ts b/packages/beacon-node/src/network/reqresp/protocols.ts index b254db022101..c84b16695788 100644 --- a/packages/beacon-node/src/network/reqresp/protocols.ts +++ b/packages/beacon-node/src/network/reqresp/protocols.ts @@ -63,12 +63,24 @@ export const BlobSidecarsByRange = toProtocol({ contextBytesType: ContextBytesType.ForkDigest, }); +export const BlobSidecarsByRangeV2 = toProtocol({ + method: ReqRespMethod.BlobSidecarsByRange, + version: Version.V2, + contextBytesType: ContextBytesType.ForkDigest, +}); + export const BlobSidecarsByRoot = toProtocol({ method: ReqRespMethod.BlobSidecarsByRoot, version: Version.V1, contextBytesType: ContextBytesType.ForkDigest, }); +export const BlobSidecarsByRootV2 = toProtocol({ + method: ReqRespMethod.BlobSidecarsByRoot, + version: Version.V2, + contextBytesType: ContextBytesType.ForkDigest, +}); + export const LightClientBootstrap = toProtocol({ method: ReqRespMethod.LightClientBootstrap, version: Version.V1, diff --git a/packages/beacon-node/src/network/reqresp/rateLimit.ts b/packages/beacon-node/src/network/reqresp/rateLimit.ts index 771d01f6c339..ce8a996a49a8 100644 --- a/packages/beacon-node/src/network/reqresp/rateLimit.ts +++ b/packages/beacon-node/src/network/reqresp/rateLimit.ts @@ -34,11 +34,13 @@ export const rateLimitQuotas: (config: ChainConfig) => Record req.count), }, [ReqRespMethod.BlobSidecarsByRoot]: { // Rationale: quota of BeaconBlocksByRoot * MAX_BLOBS_PER_BLOCK + // TODO Electra: Stays as `MAX_REQUEST_BLOB_SIDECARS` until we have fork-aware `byPeer` and set it to `MAX_REQUEST_BLOB_SIDECARS_ELECTRA` byPeer: {quota: config.MAX_REQUEST_BLOB_SIDECARS, quotaTimeMs: 10_000}, getRequestCount: getRequestCountFn(config, ReqRespMethod.BlobSidecarsByRoot, (req) => req.length), }, diff --git a/packages/beacon-node/src/network/reqresp/types.ts b/packages/beacon-node/src/network/reqresp/types.ts index b7c18ebdfeb5..ac14b24eaf24 100644 --- a/packages/beacon-node/src/network/reqresp/types.ts +++ b/packages/beacon-node/src/network/reqresp/types.ts @@ -70,9 +70,13 @@ type ResponseBodyByMethod = { }; /** Request SSZ type for each method and ForkName */ -export const requestSszTypeByMethod: (config: ChainConfig) => { +// TODO Electra: Currently setting default fork to deneb because not every caller of requestSszTypeByMethod can provide fork info +export const requestSszTypeByMethod: ( + config: ChainConfig, + fork?: ForkName +) => { [K in ReqRespMethod]: RequestBodyByMethod[K] extends null ? null : Type; -} = (config) => ({ +} = (config, fork = ForkName.deneb) => ({ [ReqRespMethod.Status]: ssz.phase0.Status, [ReqRespMethod.Goodbye]: ssz.phase0.Goodbye, [ReqRespMethod.Ping]: ssz.phase0.Ping, @@ -80,7 +84,7 @@ export const requestSszTypeByMethod: (config: ChainConfig) => { [ReqRespMethod.BeaconBlocksByRange]: ssz.phase0.BeaconBlocksByRangeRequest, [ReqRespMethod.BeaconBlocksByRoot]: ssz.phase0.BeaconBlocksByRootRequest, [ReqRespMethod.BlobSidecarsByRange]: ssz.deneb.BlobSidecarsByRangeRequest, - [ReqRespMethod.BlobSidecarsByRoot]: BlobSidecarsByRootRequestType(config), + [ReqRespMethod.BlobSidecarsByRoot]: BlobSidecarsByRootRequestType(fork, config), [ReqRespMethod.LightClientBootstrap]: ssz.Root, [ReqRespMethod.LightClientUpdatesByRange]: ssz.altair.LightClientUpdatesByRange, [ReqRespMethod.LightClientFinalityUpdate]: null, diff --git a/packages/beacon-node/src/util/types.ts b/packages/beacon-node/src/util/types.ts index 5b9c7a784277..2a99fc34bcc2 100644 --- a/packages/beacon-node/src/util/types.ts +++ b/packages/beacon-node/src/util/types.ts @@ -1,5 +1,6 @@ import {ContainerType, ListCompositeType, ValueOf} from "@chainsafe/ssz"; import {ChainConfig} from "@lodestar/config"; +import {ForkName, isForkPostElectra} from "@lodestar/params"; import {ssz} from "@lodestar/types"; // Misc SSZ types used only in the beacon-node package, no need to upstream to types @@ -14,6 +15,9 @@ export const signedBLSToExecutionChangeVersionedType = new ContainerType( ); export type SignedBLSToExecutionChangeVersioned = ValueOf; -export const BlobSidecarsByRootRequestType = (config: ChainConfig) => - new ListCompositeType(ssz.deneb.BlobIdentifier, config.MAX_REQUEST_BLOB_SIDECARS); +export const BlobSidecarsByRootRequestType = (fork: ForkName, config: ChainConfig) => + new ListCompositeType( + ssz.deneb.BlobIdentifier, + isForkPostElectra(fork) ? config.MAX_REQUEST_BLOB_SIDECARS_ELECTRA : config.MAX_REQUEST_BLOB_SIDECARS + ); export type BlobSidecarsByRootRequest = ValueOf>; diff --git a/packages/beacon-node/test/unit/util/kzg.test.ts b/packages/beacon-node/test/unit/util/kzg.test.ts index 616505268ae1..589e8d28d834 100644 --- a/packages/beacon-node/test/unit/util/kzg.test.ts +++ b/packages/beacon-node/test/unit/util/kzg.test.ts @@ -45,6 +45,7 @@ describe("C-KZG", () => { afterEachCallbacks.push(() => chain.close()); const slot = 0; + const fork = config.getForkName(slot); const blobs = [generateRandomBlob(), generateRandomBlob()]; const kzgCommitments = blobs.map((blob) => ckzg.blobToKzgCommitment(blob)); @@ -65,7 +66,7 @@ describe("C-KZG", () => { for (const blobSidecar of blobSidecars) { try { - await validateGossipBlobSidecar(chain, blobSidecar, blobSidecar.index); + await validateGossipBlobSidecar(fork, chain, blobSidecar, blobSidecar.index); } catch (_e) { // We expect some error from here // console.log(error); diff --git a/packages/config/src/chainConfig/configs/mainnet.ts b/packages/config/src/chainConfig/configs/mainnet.ts index a4adc04a1756..7b332f94b1a6 100644 --- a/packages/config/src/chainConfig/configs/mainnet.ts +++ b/packages/config/src/chainConfig/configs/mainnet.ts @@ -109,6 +109,10 @@ export const chainConfig: ChainConfig = { // Electra // 2**8 * 10**9 (= 256,000,000,000) MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: 256000000000, - // 2*7 * 10**9 (= 128,000,000,000) + // 2**7 * 10**9 (= 128,000,000,000) MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 128000000000, + BLOB_SIDECAR_SUBNET_COUNT_ELECTRA: 9, + MAX_BLOBS_PER_BLOCK_ELECTRA: 9, + // MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK_ELECTRA + MAX_REQUEST_BLOB_SIDECARS_ELECTRA: 1152, }; diff --git a/packages/config/src/chainConfig/configs/minimal.ts b/packages/config/src/chainConfig/configs/minimal.ts index d16b03e82c28..0902742277e0 100644 --- a/packages/config/src/chainConfig/configs/minimal.ts +++ b/packages/config/src/chainConfig/configs/minimal.ts @@ -108,4 +108,8 @@ export const chainConfig: ChainConfig = { MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: 128000000000, // 2**6 * 10**9 (= 64,000,000,000) MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 64000000000, + BLOB_SIDECAR_SUBNET_COUNT_ELECTRA: 9, + MAX_BLOBS_PER_BLOCK_ELECTRA: 9, + // MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK_ELECTRA + MAX_REQUEST_BLOB_SIDECARS_ELECTRA: 1152, }; diff --git a/packages/config/src/chainConfig/types.ts b/packages/config/src/chainConfig/types.ts index 291dcc8601a7..8ca338c89a24 100644 --- a/packages/config/src/chainConfig/types.ts +++ b/packages/config/src/chainConfig/types.ts @@ -75,6 +75,9 @@ export type ChainConfig = { BLOB_SIDECAR_SUBNET_COUNT: number; MAX_BLOBS_PER_BLOCK: number; MAX_REQUEST_BLOB_SIDECARS: number; + BLOB_SIDECAR_SUBNET_COUNT_ELECTRA: number; + MAX_BLOBS_PER_BLOCK_ELECTRA: number; + MAX_REQUEST_BLOB_SIDECARS_ELECTRA: number; }; export const chainConfigTypes: SpecTypes = { @@ -142,6 +145,9 @@ export const chainConfigTypes: SpecTypes = { BLOB_SIDECAR_SUBNET_COUNT: "number", MAX_BLOBS_PER_BLOCK: "number", MAX_REQUEST_BLOB_SIDECARS: "number", + BLOB_SIDECAR_SUBNET_COUNT_ELECTRA: "number", + MAX_BLOBS_PER_BLOCK_ELECTRA: "number", + MAX_REQUEST_BLOB_SIDECARS_ELECTRA: "number", }; /** Allows values in a Spec file */ diff --git a/packages/config/src/forkConfig/index.ts b/packages/config/src/forkConfig/index.ts index 725e0a3af572..6540106bb886 100644 --- a/packages/config/src/forkConfig/index.ts +++ b/packages/config/src/forkConfig/index.ts @@ -10,6 +10,7 @@ import { isForkBlobs, isForkExecution, isForkLightClient, + isForkPostElectra, } from "@lodestar/params"; import {Epoch, SSZTypesFor, Slot, Version, sszTypesFor} from "@lodestar/types"; import {ChainConfig} from "../chainConfig/index.js"; @@ -129,5 +130,8 @@ export function createForkConfig(config: ChainConfig): ForkConfig { } return sszTypesFor(forkName); }, + getMaxBlobsPerBlock(fork: ForkName): number { + return isForkPostElectra(fork) ? config.MAX_BLOBS_PER_BLOCK_ELECTRA : config.MAX_BLOBS_PER_BLOCK; + }, }; } diff --git a/packages/config/src/forkConfig/types.ts b/packages/config/src/forkConfig/types.ts index ebb2899a2a21..420cd6bcd244 100644 --- a/packages/config/src/forkConfig/types.ts +++ b/packages/config/src/forkConfig/types.ts @@ -39,4 +39,6 @@ export type ForkConfig = { getExecutionForkTypes(slot: Slot): SSZTypesFor; /** Get blobs SSZ types by hard-fork*/ getBlobsForkTypes(slot: Slot): SSZTypesFor; + /** Get max blobs per block by hard-fork */ + getMaxBlobsPerBlock(fork: ForkName): number; }; diff --git a/packages/params/src/index.ts b/packages/params/src/index.ts index 1fb875801ba9..838624e29f23 100644 --- a/packages/params/src/index.ts +++ b/packages/params/src/index.ts @@ -255,11 +255,11 @@ export const BLOB_TX_TYPE = 0x03; export const VERSIONED_HASH_VERSION_KZG = 0x01; // ssz.deneb.BeaconBlockBody.getPathInfo(['blobKzgCommitments',0]).gindex -export const KZG_COMMITMENT_GINDEX0 = ACTIVE_PRESET === PresetName.minimal ? 864 : 221184; +export const KZG_COMMITMENT_GINDEX0 = ACTIVE_PRESET === PresetName.minimal ? 1728 : 221184; export const KZG_COMMITMENT_SUBTREE_INDEX0 = KZG_COMMITMENT_GINDEX0 - 2 ** KZG_COMMITMENT_INCLUSION_PROOF_DEPTH; // ssz.deneb.BlobSidecars.elementType.fixedSize -export const BLOBSIDECAR_FIXED_SIZE = ACTIVE_PRESET === PresetName.minimal ? 131672 : 131928; +export const BLOBSIDECAR_FIXED_SIZE = ACTIVE_PRESET === PresetName.minimal ? 131704 : 131928; // Electra Misc export const UNSET_DEPOSIT_REQUESTS_START_INDEX = 2n ** 64n - 1n; diff --git a/packages/params/src/presets/minimal.ts b/packages/params/src/presets/minimal.ts index 4c2a56a11d82..4595ee9ee879 100644 --- a/packages/params/src/presets/minimal.ts +++ b/packages/params/src/presets/minimal.ts @@ -115,8 +115,8 @@ export const minimalPreset: BeaconPreset = { // DENEB /////////// FIELD_ELEMENTS_PER_BLOB: 4096, - MAX_BLOB_COMMITMENTS_PER_BLOCK: 16, - KZG_COMMITMENT_INCLUSION_PROOF_DEPTH: 9, + MAX_BLOB_COMMITMENTS_PER_BLOCK: 32, + KZG_COMMITMENT_INCLUSION_PROOF_DEPTH: 10, // ELECTRA MAX_DEPOSIT_REQUESTS_PER_PAYLOAD: 4, diff --git a/packages/params/test/e2e/ensure-config-is-synced.test.ts b/packages/params/test/e2e/ensure-config-is-synced.test.ts index b1d9c05a54b1..d09ea85e016e 100644 --- a/packages/params/test/e2e/ensure-config-is-synced.test.ts +++ b/packages/params/test/e2e/ensure-config-is-synced.test.ts @@ -8,7 +8,7 @@ import {loadConfigYaml} from "../yaml.js"; // Not e2e, but slow. Run with e2e tests /** https://github.com/ethereum/consensus-specs/releases */ -const specConfigCommit = "v1.5.0-alpha.8"; +const specConfigCommit = "v1.5.0-alpha.10"; /** * Fields that we filter from local config when doing comparison. * Ideally this should be empty as it is not spec compliant diff --git a/packages/state-transition/src/block/processExecutionPayload.ts b/packages/state-transition/src/block/processExecutionPayload.ts index 0ea2fc7a16f7..ddc24e884d98 100644 --- a/packages/state-transition/src/block/processExecutionPayload.ts +++ b/packages/state-transition/src/block/processExecutionPayload.ts @@ -1,5 +1,5 @@ import {byteArrayEquals} from "@chainsafe/ssz"; -import {ForkSeq} from "@lodestar/params"; +import {ForkName, ForkSeq, isForkBlobs} from "@lodestar/params"; import {BeaconBlockBody, BlindedBeaconBlockBody, deneb, isExecutionPayload} from "@lodestar/types"; import {toHex, toRootHex} from "@lodestar/utils"; import {CachedBeaconStateBellatrix, CachedBeaconStateCapella} from "../types.js"; @@ -18,6 +18,7 @@ export function processExecutionPayload( externalData: Omit ): void { const payload = getFullOrBlindedPayloadFromBody(body); + const forkName = ForkName[ForkSeq[fork] as ForkName]; // Verify consistency of the parent hash, block number, base fee per gas and gas limit // with respect to the previous execution payload header if (isMergeTransitionComplete(state)) { @@ -47,10 +48,11 @@ export function processExecutionPayload( throw Error(`Invalid timestamp ${payload.timestamp} genesisTime=${state.genesisTime} slot=${state.slot}`); } - if (fork >= ForkSeq.deneb) { + if (isForkBlobs(forkName)) { + const maxBlobsPerBlock = state.config.getMaxBlobsPerBlock(forkName); const blobKzgCommitmentsLen = (body as deneb.BeaconBlockBody).blobKzgCommitments?.length ?? 0; - if (blobKzgCommitmentsLen > state.config.MAX_BLOBS_PER_BLOCK) { - throw Error(`blobKzgCommitmentsLen exceeds limit=${state.config.MAX_BLOBS_PER_BLOCK}`); + if (blobKzgCommitmentsLen > maxBlobsPerBlock) { + throw Error(`blobKzgCommitmentsLen exceeds limit=${maxBlobsPerBlock}`); } } diff --git a/packages/validator/src/util/params.ts b/packages/validator/src/util/params.ts index 825f60e8c7fa..383275bfb47b 100644 --- a/packages/validator/src/util/params.ts +++ b/packages/validator/src/util/params.ts @@ -137,7 +137,9 @@ function getSpecCriticalParams(localConfig: ChainConfig): Record Date: Thu, 9 Jan 2025 23:25:39 +0000 Subject: [PATCH 16/34] fix: use Bigint for deposit index to pass spec tests (#7344) fix: use Bigint for deposit index --- packages/types/src/primitive/sszTypes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/types/src/primitive/sszTypes.ts b/packages/types/src/primitive/sszTypes.ts index 40806aa40f4a..d7c74857077c 100644 --- a/packages/types/src/primitive/sszTypes.ts +++ b/packages/types/src/primitive/sszTypes.ts @@ -51,7 +51,7 @@ export const SubcommitteeIndex = UintNum64; */ export const ValidatorIndex = UintNum64; export const WithdrawalIndex = UintNum64; -export const DepositIndex = UintNum64; +export const DepositIndex = UintBn64; export const Gwei = UintBn64; export const Wei = UintBn256; export const Root = new ByteVectorType(32); From 793ad4921827cbd3dec3cef72809dc1b3dca7cc3 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Thu, 9 Jan 2025 23:26:00 +0000 Subject: [PATCH 17/34] chore: skip fulu fork spec tests (#7343) chore: skip fulu spec tests --- packages/beacon-node/test/spec/utils/specTestIterator.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/beacon-node/test/spec/utils/specTestIterator.ts b/packages/beacon-node/test/spec/utils/specTestIterator.ts index 0868183295cf..7fbde723b00b 100644 --- a/packages/beacon-node/test/spec/utils/specTestIterator.ts +++ b/packages/beacon-node/test/spec/utils/specTestIterator.ts @@ -58,7 +58,7 @@ const coveredTestRunners = [ // ], // ``` export const defaultSkipOpts: SkipOpts = { - skippedForks: ["eip7594"], + skippedForks: ["eip7594", "fulu"], // TODO: capella // BeaconBlockBody proof in lightclient is the new addition in v1.3.0-rc.2-hotfix // Skip them for now to enable subsequently From cf64359dd5ab6273619c0abf6e4ca92dc4931b10 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Fri, 10 Jan 2025 08:56:18 +0000 Subject: [PATCH 18/34] Bump CL spec version to beta.0 --- packages/beacon-node/test/spec/specTestVersioning.ts | 2 +- packages/params/test/e2e/ensure-config-is-synced.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/beacon-node/test/spec/specTestVersioning.ts b/packages/beacon-node/test/spec/specTestVersioning.ts index dd2a39903d82..7925a9af80f1 100644 --- a/packages/beacon-node/test/spec/specTestVersioning.ts +++ b/packages/beacon-node/test/spec/specTestVersioning.ts @@ -14,7 +14,7 @@ import {DownloadTestsOptions} from "@lodestar/spec-test-util/downloadTests"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); export const ethereumConsensusSpecsTests: DownloadTestsOptions = { - specVersion: "v1.5.0-alpha.10", + specVersion: "v1.5.0-beta.0", // Target directory is the host package root: 'packages/*/spec-tests' outputDir: path.join(__dirname, "../../spec-tests"), specTestsRepoUrl: "https://github.com/ethereum/consensus-spec-tests", diff --git a/packages/params/test/e2e/ensure-config-is-synced.test.ts b/packages/params/test/e2e/ensure-config-is-synced.test.ts index d09ea85e016e..22224e6421c4 100644 --- a/packages/params/test/e2e/ensure-config-is-synced.test.ts +++ b/packages/params/test/e2e/ensure-config-is-synced.test.ts @@ -8,7 +8,7 @@ import {loadConfigYaml} from "../yaml.js"; // Not e2e, but slow. Run with e2e tests /** https://github.com/ethereum/consensus-specs/releases */ -const specConfigCommit = "v1.5.0-alpha.10"; +const specConfigCommit = "v1.5.0-beta.0"; /** * Fields that we filter from local config when doing comparison. * Ideally this should be empty as it is not spec compliant From e30fd40c053e2dd07a4f5b4b6e37118efd91209a Mon Sep 17 00:00:00 2001 From: Phil Ngo Date: Fri, 10 Jan 2025 12:07:35 -0500 Subject: [PATCH 19/34] chore: bump package versions to 1.25.0 --- lerna.json | 2 +- packages/api/package.json | 10 +++++----- packages/beacon-node/package.json | 26 +++++++++++++------------- packages/cli/package.json | 26 +++++++++++++------------- packages/config/package.json | 8 ++++---- packages/db/package.json | 8 ++++---- packages/flare/package.json | 14 +++++++------- packages/fork-choice/package.json | 12 ++++++------ packages/light-client/package.json | 12 ++++++------ packages/logger/package.json | 6 +++--- packages/params/package.json | 2 +- packages/prover/package.json | 18 +++++++++--------- packages/reqresp/package.json | 12 ++++++------ packages/spec-test-util/package.json | 4 ++-- packages/state-transition/package.json | 10 +++++----- packages/test-utils/package.json | 6 +++--- packages/types/package.json | 4 ++-- packages/utils/package.json | 2 +- packages/validator/package.json | 18 +++++++++--------- 19 files changed, 100 insertions(+), 100 deletions(-) diff --git a/lerna.json b/lerna.json index 99654f9e81de..97c908dc50ee 100644 --- a/lerna.json +++ b/lerna.json @@ -4,7 +4,7 @@ ], "npmClient": "yarn", "useNx": true, - "version": "1.24.0", + "version": "1.25.0", "stream": true, "command": { "version": { diff --git a/packages/api/package.json b/packages/api/package.json index f650a377f6a5..c2234e0db5ee 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -11,7 +11,7 @@ "bugs": { "url": "https://github.com/ChainSafe/lodestar/issues" }, - "version": "1.24.0", + "version": "1.25.0", "type": "module", "exports": { ".": { @@ -72,10 +72,10 @@ "dependencies": { "@chainsafe/persistent-merkle-tree": "^0.8.0", "@chainsafe/ssz": "^0.18.0", - "@lodestar/config": "^1.24.0", - "@lodestar/params": "^1.24.0", - "@lodestar/types": "^1.24.0", - "@lodestar/utils": "^1.24.0", + "@lodestar/config": "^1.25.0", + "@lodestar/params": "^1.25.0", + "@lodestar/types": "^1.25.0", + "@lodestar/utils": "^1.25.0", "eventsource": "^2.0.2", "qs": "^6.11.1" }, diff --git a/packages/beacon-node/package.json b/packages/beacon-node/package.json index f80dc93b1314..4be101ba92a6 100644 --- a/packages/beacon-node/package.json +++ b/packages/beacon-node/package.json @@ -11,7 +11,7 @@ "bugs": { "url": "https://github.com/ChainSafe/lodestar/issues" }, - "version": "1.24.0", + "version": "1.25.0", "type": "module", "exports": { ".": { @@ -120,18 +120,18 @@ "@libp2p/peer-id-factory": "^4.1.0", "@libp2p/prometheus-metrics": "^3.0.21", "@libp2p/tcp": "9.0.23", - "@lodestar/api": "^1.24.0", - "@lodestar/config": "^1.24.0", - "@lodestar/db": "^1.24.0", - "@lodestar/fork-choice": "^1.24.0", - "@lodestar/light-client": "^1.24.0", - "@lodestar/logger": "^1.24.0", - "@lodestar/params": "^1.24.0", - "@lodestar/reqresp": "^1.24.0", - "@lodestar/state-transition": "^1.24.0", - "@lodestar/types": "^1.24.0", - "@lodestar/utils": "^1.24.0", - "@lodestar/validator": "^1.24.0", + "@lodestar/api": "^1.25.0", + "@lodestar/config": "^1.25.0", + "@lodestar/db": "^1.25.0", + "@lodestar/fork-choice": "^1.25.0", + "@lodestar/light-client": "^1.25.0", + "@lodestar/logger": "^1.25.0", + "@lodestar/params": "^1.25.0", + "@lodestar/reqresp": "^1.25.0", + "@lodestar/state-transition": "^1.25.0", + "@lodestar/types": "^1.25.0", + "@lodestar/utils": "^1.25.0", + "@lodestar/validator": "^1.25.0", "@multiformats/multiaddr": "^12.1.3", "c-kzg": "^2.1.2", "datastore-core": "^9.1.1", diff --git a/packages/cli/package.json b/packages/cli/package.json index 8f3cc163cc57..b9d9ce7c1644 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@chainsafe/lodestar", - "version": "1.24.0", + "version": "1.25.0", "description": "Command line interface for lodestar", "author": "ChainSafe Systems", "license": "LGPL-3.0", @@ -62,17 +62,17 @@ "@libp2p/crypto": "^4.1.0", "@libp2p/peer-id": "^4.1.0", "@libp2p/peer-id-factory": "^4.1.0", - "@lodestar/api": "^1.24.0", - "@lodestar/beacon-node": "^1.24.0", - "@lodestar/config": "^1.24.0", - "@lodestar/db": "^1.24.0", - "@lodestar/light-client": "^1.24.0", - "@lodestar/logger": "^1.24.0", - "@lodestar/params": "^1.24.0", - "@lodestar/state-transition": "^1.24.0", - "@lodestar/types": "^1.24.0", - "@lodestar/utils": "^1.24.0", - "@lodestar/validator": "^1.24.0", + "@lodestar/api": "^1.25.0", + "@lodestar/beacon-node": "^1.25.0", + "@lodestar/config": "^1.25.0", + "@lodestar/db": "^1.25.0", + "@lodestar/light-client": "^1.25.0", + "@lodestar/logger": "^1.25.0", + "@lodestar/params": "^1.25.0", + "@lodestar/state-transition": "^1.25.0", + "@lodestar/types": "^1.25.0", + "@lodestar/utils": "^1.25.0", + "@lodestar/validator": "^1.25.0", "@multiformats/multiaddr": "^12.1.3", "deepmerge": "^4.3.1", "ethers": "^6.7.0", @@ -88,7 +88,7 @@ "yargs": "^17.7.1" }, "devDependencies": { - "@lodestar/test-utils": "^1.24.0", + "@lodestar/test-utils": "^1.25.0", "@types/debug": "^4.1.7", "@types/got": "^9.6.12", "@types/inquirer": "^9.0.3", diff --git a/packages/config/package.json b/packages/config/package.json index c421d8802f08..8e9047a12dba 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -1,6 +1,6 @@ { "name": "@lodestar/config", - "version": "1.24.0", + "version": "1.25.0", "description": "Chain configuration required for lodestar", "author": "ChainSafe Systems", "license": "Apache-2.0", @@ -65,8 +65,8 @@ ], "dependencies": { "@chainsafe/ssz": "^0.18.0", - "@lodestar/params": "^1.24.0", - "@lodestar/types": "^1.24.0", - "@lodestar/utils": "^1.24.0" + "@lodestar/params": "^1.25.0", + "@lodestar/types": "^1.25.0", + "@lodestar/utils": "^1.25.0" } } diff --git a/packages/db/package.json b/packages/db/package.json index c71fae529350..30cf7e6bbc25 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -1,6 +1,6 @@ { "name": "@lodestar/db", - "version": "1.24.0", + "version": "1.25.0", "description": "DB modules of Lodestar", "author": "ChainSafe Systems", "homepage": "https://github.com/ChainSafe/lodestar#readme", @@ -36,12 +36,12 @@ }, "dependencies": { "@chainsafe/ssz": "^0.18.0", - "@lodestar/config": "^1.24.0", - "@lodestar/utils": "^1.24.0", + "@lodestar/config": "^1.25.0", + "@lodestar/utils": "^1.25.0", "classic-level": "^1.4.1", "it-all": "^3.0.4" }, "devDependencies": { - "@lodestar/logger": "^1.24.0" + "@lodestar/logger": "^1.25.0" } } diff --git a/packages/flare/package.json b/packages/flare/package.json index a4fa6ca96399..d57a9de7c4b7 100644 --- a/packages/flare/package.json +++ b/packages/flare/package.json @@ -1,6 +1,6 @@ { "name": "@lodestar/flare", - "version": "1.24.0", + "version": "1.25.0", "description": "Beacon chain debugging tool", "author": "ChainSafe Systems", "license": "Apache-2.0", @@ -60,12 +60,12 @@ "dependencies": { "@chainsafe/bls-keygen": "^0.4.0", "@chainsafe/blst": "^2.1.0", - "@lodestar/api": "^1.24.0", - "@lodestar/config": "^1.24.0", - "@lodestar/params": "^1.24.0", - "@lodestar/state-transition": "^1.24.0", - "@lodestar/types": "^1.24.0", - "@lodestar/utils": "^1.24.0", + "@lodestar/api": "^1.25.0", + "@lodestar/config": "^1.25.0", + "@lodestar/params": "^1.25.0", + "@lodestar/state-transition": "^1.25.0", + "@lodestar/types": "^1.25.0", + "@lodestar/utils": "^1.25.0", "source-map-support": "^0.5.21", "yargs": "^17.7.1" }, diff --git a/packages/fork-choice/package.json b/packages/fork-choice/package.json index a3d0470b21d2..1102bca3c6b8 100644 --- a/packages/fork-choice/package.json +++ b/packages/fork-choice/package.json @@ -11,7 +11,7 @@ "bugs": { "url": "https://github.com/ChainSafe/lodestar/issues" }, - "version": "1.24.0", + "version": "1.25.0", "type": "module", "exports": "./lib/index.js", "types": "./lib/index.d.ts", @@ -37,11 +37,11 @@ }, "dependencies": { "@chainsafe/ssz": "^0.18.0", - "@lodestar/config": "^1.24.0", - "@lodestar/params": "^1.24.0", - "@lodestar/state-transition": "^1.24.0", - "@lodestar/types": "^1.24.0", - "@lodestar/utils": "^1.24.0" + "@lodestar/config": "^1.25.0", + "@lodestar/params": "^1.25.0", + "@lodestar/state-transition": "^1.25.0", + "@lodestar/types": "^1.25.0", + "@lodestar/utils": "^1.25.0" }, "keywords": [ "ethereum", diff --git a/packages/light-client/package.json b/packages/light-client/package.json index 2ceac4f703bb..9f408ac1cab5 100644 --- a/packages/light-client/package.json +++ b/packages/light-client/package.json @@ -11,7 +11,7 @@ "bugs": { "url": "https://github.com/ChainSafe/lodestar/issues" }, - "version": "1.24.0", + "version": "1.25.0", "type": "module", "exports": { ".": { @@ -77,11 +77,11 @@ "@chainsafe/blst": "^0.2.0", "@chainsafe/persistent-merkle-tree": "^0.8.0", "@chainsafe/ssz": "^0.18.0", - "@lodestar/api": "^1.24.0", - "@lodestar/config": "^1.24.0", - "@lodestar/params": "^1.24.0", - "@lodestar/types": "^1.24.0", - "@lodestar/utils": "^1.24.0", + "@lodestar/api": "^1.25.0", + "@lodestar/config": "^1.25.0", + "@lodestar/params": "^1.25.0", + "@lodestar/types": "^1.25.0", + "@lodestar/utils": "^1.25.0", "mitt": "^3.0.0" }, "devDependencies": { diff --git a/packages/logger/package.json b/packages/logger/package.json index badf74add4a6..a0c4ed936d3e 100644 --- a/packages/logger/package.json +++ b/packages/logger/package.json @@ -11,7 +11,7 @@ "bugs": { "url": "https://github.com/ChainSafe/lodestar/issues" }, - "version": "1.24.0", + "version": "1.25.0", "type": "module", "exports": { ".": { @@ -66,14 +66,14 @@ }, "types": "lib/index.d.ts", "dependencies": { - "@lodestar/utils": "^1.24.0", + "@lodestar/utils": "^1.25.0", "winston": "^3.8.2", "winston-daily-rotate-file": "^4.7.1", "winston-transport": "^4.5.0" }, "devDependencies": { "@chainsafe/threads": "^1.11.1", - "@lodestar/test-utils": "^1.24.0", + "@lodestar/test-utils": "^1.25.0", "@types/triple-beam": "^1.3.2", "triple-beam": "^1.3.0" }, diff --git a/packages/params/package.json b/packages/params/package.json index 7089fef3a13c..8ec18b62efc9 100644 --- a/packages/params/package.json +++ b/packages/params/package.json @@ -1,6 +1,6 @@ { "name": "@lodestar/params", - "version": "1.24.0", + "version": "1.25.0", "description": "Chain parameters required for lodestar", "author": "ChainSafe Systems", "license": "Apache-2.0", diff --git a/packages/prover/package.json b/packages/prover/package.json index 7df21de669d8..58563612ea9c 100644 --- a/packages/prover/package.json +++ b/packages/prover/package.json @@ -11,7 +11,7 @@ "bugs": { "url": "https://github.com/ChainSafe/lodestar/issues" }, - "version": "1.24.0", + "version": "1.25.0", "type": "module", "exports": { ".": { @@ -69,13 +69,13 @@ "@ethereumjs/tx": "^4.1.2", "@ethereumjs/util": "^8.0.6", "@ethereumjs/vm": "^6.4.2", - "@lodestar/api": "^1.24.0", - "@lodestar/config": "^1.24.0", - "@lodestar/light-client": "^1.24.0", - "@lodestar/logger": "^1.24.0", - "@lodestar/params": "^1.24.0", - "@lodestar/types": "^1.24.0", - "@lodestar/utils": "^1.24.0", + "@lodestar/api": "^1.25.0", + "@lodestar/config": "^1.25.0", + "@lodestar/light-client": "^1.25.0", + "@lodestar/logger": "^1.25.0", + "@lodestar/params": "^1.25.0", + "@lodestar/types": "^1.25.0", + "@lodestar/utils": "^1.25.0", "ethereum-cryptography": "^2.0.0", "find-up": "^6.3.0", "http-proxy": "^1.18.1", @@ -84,7 +84,7 @@ "yargs": "^17.7.1" }, "devDependencies": { - "@lodestar/test-utils": "^1.24.0", + "@lodestar/test-utils": "^1.25.0", "@types/http-proxy": "^1.17.10", "@types/yargs": "^17.0.24", "axios": "^1.3.4", diff --git a/packages/reqresp/package.json b/packages/reqresp/package.json index dc6082fe0b00..8814a1b7d131 100644 --- a/packages/reqresp/package.json +++ b/packages/reqresp/package.json @@ -11,7 +11,7 @@ "bugs": { "url": "https://github.com/ChainSafe/lodestar/issues" }, - "version": "1.24.0", + "version": "1.25.0", "type": "module", "exports": { ".": { @@ -54,9 +54,9 @@ "dependencies": { "@chainsafe/fast-crc32c": "^4.1.1", "@libp2p/interface": "^1.3.0", - "@lodestar/config": "^1.24.0", - "@lodestar/params": "^1.24.0", - "@lodestar/utils": "^1.24.0", + "@lodestar/config": "^1.25.0", + "@lodestar/params": "^1.25.0", + "@lodestar/utils": "^1.25.0", "it-all": "^3.0.4", "it-pipe": "^3.0.1", "snappy": "^7.2.2", @@ -65,8 +65,8 @@ "uint8arraylist": "^2.4.7" }, "devDependencies": { - "@lodestar/logger": "^1.24.0", - "@lodestar/types": "^1.24.0", + "@lodestar/logger": "^1.25.0", + "@lodestar/types": "^1.25.0", "libp2p": "1.4.3" }, "peerDependencies": { diff --git a/packages/spec-test-util/package.json b/packages/spec-test-util/package.json index 5ed1a6cdf833..d556e09707ce 100644 --- a/packages/spec-test-util/package.json +++ b/packages/spec-test-util/package.json @@ -1,6 +1,6 @@ { "name": "@lodestar/spec-test-util", - "version": "1.24.0", + "version": "1.25.0", "description": "Spec test suite generator from yaml test files", "author": "ChainSafe Systems", "license": "Apache-2.0", @@ -62,7 +62,7 @@ "blockchain" ], "dependencies": { - "@lodestar/utils": "^1.24.0", + "@lodestar/utils": "^1.25.0", "axios": "^1.3.4", "rimraf": "^4.4.1", "snappyjs": "^0.7.0", diff --git a/packages/state-transition/package.json b/packages/state-transition/package.json index 5caf4d4ff3db..38c22e84d568 100644 --- a/packages/state-transition/package.json +++ b/packages/state-transition/package.json @@ -11,7 +11,7 @@ "bugs": { "url": "https://github.com/ChainSafe/lodestar/issues" }, - "version": "1.24.0", + "version": "1.25.0", "type": "module", "exports": { ".": { @@ -65,10 +65,10 @@ "@chainsafe/pubkey-index-map": "2.0.0", "@chainsafe/ssz": "^0.18.0", "@chainsafe/swap-or-not-shuffle": "^0.0.2", - "@lodestar/config": "^1.24.0", - "@lodestar/params": "^1.24.0", - "@lodestar/types": "^1.24.0", - "@lodestar/utils": "^1.24.0", + "@lodestar/config": "^1.25.0", + "@lodestar/params": "^1.25.0", + "@lodestar/types": "^1.25.0", + "@lodestar/utils": "^1.25.0", "bigint-buffer": "^1.1.5" }, "keywords": [ diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index a5588dddfd93..bc8a8d1c282d 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -1,7 +1,7 @@ { "name": "@lodestar/test-utils", "private": true, - "version": "1.24.0", + "version": "1.25.0", "description": "Test utilities reused across other packages", "author": "ChainSafe Systems", "license": "Apache-2.0", @@ -59,8 +59,8 @@ "dependencies": { "@chainsafe/bls-keystore": "^3.1.0", "@chainsafe/blst": "^2.1.0", - "@lodestar/params": "^1.24.0", - "@lodestar/utils": "^1.24.0", + "@lodestar/params": "^1.25.0", + "@lodestar/utils": "^1.25.0", "axios": "^1.3.4", "testcontainers": "^10.2.1", "tmp": "^0.2.1", diff --git a/packages/types/package.json b/packages/types/package.json index 949755d62a97..6db16772d3f7 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -11,7 +11,7 @@ "bugs": { "url": "https://github.com/ChainSafe/lodestar/issues" }, - "version": "1.24.0", + "version": "1.25.0", "type": "module", "exports": { ".": { @@ -74,7 +74,7 @@ "types": "lib/index.d.ts", "dependencies": { "@chainsafe/ssz": "^0.18.0", - "@lodestar/params": "^1.24.0", + "@lodestar/params": "^1.25.0", "ethereum-cryptography": "^2.0.0" }, "keywords": [ diff --git a/packages/utils/package.json b/packages/utils/package.json index 3347b4815ee3..4180998f2caf 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -11,7 +11,7 @@ "bugs": { "url": "https://github.com/ChainSafe/lodestar/issues" }, - "version": "1.24.0", + "version": "1.25.0", "type": "module", "exports": "./lib/index.js", "files": [ diff --git a/packages/validator/package.json b/packages/validator/package.json index 255782e1b531..c71c6b68872a 100644 --- a/packages/validator/package.json +++ b/packages/validator/package.json @@ -1,6 +1,6 @@ { "name": "@lodestar/validator", - "version": "1.24.0", + "version": "1.25.0", "description": "A Typescript implementation of the validator client", "author": "ChainSafe Systems", "license": "LGPL-3.0", @@ -47,17 +47,17 @@ "dependencies": { "@chainsafe/blst": "^2.1.0", "@chainsafe/ssz": "^0.18.0", - "@lodestar/api": "^1.24.0", - "@lodestar/config": "^1.24.0", - "@lodestar/db": "^1.24.0", - "@lodestar/params": "^1.24.0", - "@lodestar/state-transition": "^1.24.0", - "@lodestar/types": "^1.24.0", - "@lodestar/utils": "^1.24.0", + "@lodestar/api": "^1.25.0", + "@lodestar/config": "^1.25.0", + "@lodestar/db": "^1.25.0", + "@lodestar/params": "^1.25.0", + "@lodestar/state-transition": "^1.25.0", + "@lodestar/types": "^1.25.0", + "@lodestar/utils": "^1.25.0", "strict-event-emitter-types": "^2.0.0" }, "devDependencies": { - "@lodestar/test-utils": "^1.24.0", + "@lodestar/test-utils": "^1.25.0", "bigint-buffer": "^1.1.5", "rimraf": "^4.4.1" } From 8e423e74373b2e57f2a69cc762fc690195a17397 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Fri, 10 Jan 2025 19:08:33 +0000 Subject: [PATCH 20/34] fix: revert BlobSidecarsByRoot/Range version bump (#7347) * fix: revert BlobSidecarsByRoot/Range version bump * Remove version check from handler * Remove unused imports --- packages/beacon-node/src/network/network.ts | 17 +++-------------- .../src/network/reqresp/ReqRespBeaconNode.ts | 8 -------- .../src/network/reqresp/handlers/index.ts | 5 ++--- .../src/network/reqresp/protocols.ts | 12 ------------ 4 files changed, 5 insertions(+), 37 deletions(-) diff --git a/packages/beacon-node/src/network/network.ts b/packages/beacon-node/src/network/network.ts index 6e37f64f2edc..505da6718c33 100644 --- a/packages/beacon-node/src/network/network.ts +++ b/packages/beacon-node/src/network/network.ts @@ -4,7 +4,7 @@ import {PeerId} from "@libp2p/interface"; import {routes} from "@lodestar/api"; import {BeaconConfig} from "@lodestar/config"; import {LoggerNode} from "@lodestar/logger/node"; -import {ForkSeq, isForkPostElectra} from "@lodestar/params"; +import {ForkSeq} from "@lodestar/params"; import {ResponseIncoming} from "@lodestar/reqresp"; import {computeStartSlotAtEpoch, computeTimeAtSlot} from "@lodestar/state-transition"; import { @@ -504,12 +504,7 @@ export class Network implements INetwork { ): Promise { const fork = this.config.getForkName(request.startSlot); return collectMaxResponseTyped( - this.sendReqRespRequest( - peerId, - ReqRespMethod.BlobSidecarsByRange, - [isForkPostElectra(fork) ? Version.V2 : Version.V1], - request - ), + this.sendReqRespRequest(peerId, ReqRespMethod.BlobSidecarsByRange, [Version.V1], request), // request's count represent the slots, so the actual max count received could be slots * blobs per slot request.count * this.config.getMaxBlobsPerBlock(fork), responseSszTypeByMethod[ReqRespMethod.BlobSidecarsByRange] @@ -517,14 +512,8 @@ export class Network implements INetwork { } async sendBlobSidecarsByRoot(peerId: PeerIdStr, request: BlobSidecarsByRootRequest): Promise { - const fork = this.config.getForkName(this.clock.currentSlot); return collectMaxResponseTyped( - this.sendReqRespRequest( - peerId, - ReqRespMethod.BlobSidecarsByRoot, - [isForkPostElectra(fork) ? Version.V2 : Version.V1], - request - ), + this.sendReqRespRequest(peerId, ReqRespMethod.BlobSidecarsByRoot, [Version.V1], request), request.length, responseSszTypeByMethod[ReqRespMethod.BlobSidecarsByRoot] ); diff --git a/packages/beacon-node/src/network/reqresp/ReqRespBeaconNode.ts b/packages/beacon-node/src/network/reqresp/ReqRespBeaconNode.ts index 72d9892faec8..b84886bd2974 100644 --- a/packages/beacon-node/src/network/reqresp/ReqRespBeaconNode.ts +++ b/packages/beacon-node/src/network/reqresp/ReqRespBeaconNode.ts @@ -252,20 +252,12 @@ export class ReqRespBeaconNode extends ReqResp { } if (ForkSeq[fork] >= ForkSeq.deneb) { - // TODO Electra: Consider deprecating BlobSidecarsByRootV1 and BlobSidecarsByRangeV1 at fork boundary or after Electra is stable protocolsAtFork.push( [protocols.BlobSidecarsByRoot(this.config), this.getHandler(ReqRespMethod.BlobSidecarsByRoot)], [protocols.BlobSidecarsByRange(this.config), this.getHandler(ReqRespMethod.BlobSidecarsByRange)] ); } - if (ForkSeq[fork] >= ForkSeq.electra) { - protocolsAtFork.push( - [protocols.BlobSidecarsByRootV2(this.config), this.getHandler(ReqRespMethod.BlobSidecarsByRoot)], - [protocols.BlobSidecarsByRangeV2(this.config), this.getHandler(ReqRespMethod.BlobSidecarsByRange)] - ); - } - return protocolsAtFork; } diff --git a/packages/beacon-node/src/network/reqresp/handlers/index.ts b/packages/beacon-node/src/network/reqresp/handlers/index.ts index 49b552b58178..85eb4a0136b2 100644 --- a/packages/beacon-node/src/network/reqresp/handlers/index.ts +++ b/packages/beacon-node/src/network/reqresp/handlers/index.ts @@ -1,10 +1,9 @@ -import {ForkName} from "@lodestar/params"; import {ProtocolHandler} from "@lodestar/reqresp"; import {ssz} from "@lodestar/types"; import {IBeaconChain} from "../../../chain/index.js"; import {IBeaconDb} from "../../../db/index.js"; import {BlobSidecarsByRootRequestType} from "../../../util/types.js"; -import {GetReqRespHandlerFn, ReqRespMethod, Version} from "../types.js"; +import {GetReqRespHandlerFn, ReqRespMethod} from "../types.js"; import {onBeaconBlocksByRange} from "./beaconBlocksByRange.js"; import {onBeaconBlocksByRoot} from "./beaconBlocksByRoot.js"; import {onBlobSidecarsByRange} from "./blobSidecarsByRange.js"; @@ -39,7 +38,7 @@ export function getReqRespHandlers({db, chain}: {db: IBeaconDb; chain: IBeaconCh return onBeaconBlocksByRoot(body, chain, db); }, [ReqRespMethod.BlobSidecarsByRoot]: (req) => { - const fork = req.version === Version.V2 ? ForkName.electra : ForkName.deneb; + const fork = chain.config.getForkName(chain.clock.currentSlot); const body = BlobSidecarsByRootRequestType(fork, chain.config).deserialize(req.data); return onBlobSidecarsByRoot(body, chain, db); }, diff --git a/packages/beacon-node/src/network/reqresp/protocols.ts b/packages/beacon-node/src/network/reqresp/protocols.ts index c84b16695788..b254db022101 100644 --- a/packages/beacon-node/src/network/reqresp/protocols.ts +++ b/packages/beacon-node/src/network/reqresp/protocols.ts @@ -63,24 +63,12 @@ export const BlobSidecarsByRange = toProtocol({ contextBytesType: ContextBytesType.ForkDigest, }); -export const BlobSidecarsByRangeV2 = toProtocol({ - method: ReqRespMethod.BlobSidecarsByRange, - version: Version.V2, - contextBytesType: ContextBytesType.ForkDigest, -}); - export const BlobSidecarsByRoot = toProtocol({ method: ReqRespMethod.BlobSidecarsByRoot, version: Version.V1, contextBytesType: ContextBytesType.ForkDigest, }); -export const BlobSidecarsByRootV2 = toProtocol({ - method: ReqRespMethod.BlobSidecarsByRoot, - version: Version.V2, - contextBytesType: ContextBytesType.ForkDigest, -}); - export const LightClientBootstrap = toProtocol({ method: ReqRespMethod.LightClientBootstrap, version: Version.V1, From 3ea46e2094c35830140276ee730db8753e0185b4 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Mon, 13 Jan 2025 11:18:02 +0000 Subject: [PATCH 21/34] chore: skip light client data_collection spec tests (#7349) --- packages/beacon-node/test/spec/utils/specTestIterator.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/beacon-node/test/spec/utils/specTestIterator.ts b/packages/beacon-node/test/spec/utils/specTestIterator.ts index 7fbde723b00b..deb2988cfdfe 100644 --- a/packages/beacon-node/test/spec/utils/specTestIterator.ts +++ b/packages/beacon-node/test/spec/utils/specTestIterator.ts @@ -66,6 +66,7 @@ export const defaultSkipOpts: SkipOpts = { /^capella\/light_client\/single_merkle_proof\/BeaconBlockBody.*/, /^deneb\/light_client\/single_merkle_proof\/BeaconBlockBody.*/, /^electra\/light_client\/single_merkle_proof\/BeaconBlockBody.*/, + /^.+\/light_client\/data_collection\/.*/, ], skippedTests: [], skippedRunners: ["merkle_proof", "networking"], From 0a93016ef41f2c4fb76530b06f9693056a9d9ce1 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Wed, 15 Jan 2025 14:02:08 +0000 Subject: [PATCH 22/34] chore: update lighthouse to latest unstable version in sim tests (#7364) * chore: update lighthouse to latest unstable version * Remove unsupported CLI flags * Update to deposit_contract_block.txt * Update to deposit_contract_block.txt for validator --- .env.test | 2 +- .../cli/test/utils/crucible/clients/beacon/lighthouse.ts | 6 +----- .../cli/test/utils/crucible/clients/validator/lighthouse.ts | 2 +- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/.env.test b/.env.test index 9d3b289a5151..6eb7ab17a4df 100644 --- a/.env.test +++ b/.env.test @@ -4,7 +4,7 @@ GETH_DOCKER_IMAGE=ethereum/client-go:v1.13.14 # Use either image or local binary for the testing GETH_BINARY_DIR= -LIGHTHOUSE_DOCKER_IMAGE=sigp/lighthouse:v5.1.1-amd64-modern-dev +LIGHTHOUSE_DOCKER_IMAGE=sigp/lighthouse:latest-amd64-unstable # We can't upgrade nethermind further due to genesis hash mismatch with the geth # https://github.com/NethermindEth/nethermind/issues/6683 diff --git a/packages/cli/test/utils/crucible/clients/beacon/lighthouse.ts b/packages/cli/test/utils/crucible/clients/beacon/lighthouse.ts index 725c08389105..4716e8538c3e 100644 --- a/packages/cli/test/utils/crucible/clients/beacon/lighthouse.ts +++ b/packages/cli/test/utils/crucible/clients/beacon/lighthouse.ts @@ -29,10 +29,6 @@ export const generateLighthouseBeaconNode: BeaconNodeGenerator { await writeFile(path.join(rootDir, "config.yaml"), yaml.dump(chainConfigToJson(forkConfig))); - await writeFile(path.join(rootDir, "deploy_block.txt"), "0"); + await writeFile(path.join(rootDir, "deposit_contract_block.txt"), "0"); }, cli: { command: isDocker ? "lighthouse" : (process.env.LIGHTHOUSE_BINARY_PATH as string), diff --git a/packages/cli/test/utils/crucible/clients/validator/lighthouse.ts b/packages/cli/test/utils/crucible/clients/validator/lighthouse.ts index a5f137a861d5..8f5ab64caffb 100644 --- a/packages/cli/test/utils/crucible/clients/validator/lighthouse.ts +++ b/packages/cli/test/utils/crucible/clients/validator/lighthouse.ts @@ -65,7 +65,7 @@ export const generateLighthouseValidatorNode: ValidatorNodeGenerator Date: Wed, 15 Jan 2025 21:04:01 +0000 Subject: [PATCH 23/34] chore: review devnet-5 branch (#7365) * Remove stale todo * some refactoring * Readd comment, seems somewhat useful * Add alias for getSlotFromOffset * Use parseInt instead of Number * Update return type --- .../beacon-node/src/execution/engine/types.ts | 2 +- .../src/network/processor/gossipHandlers.ts | 1 - packages/beacon-node/src/util/sszBytes.ts | 24 ++++++++++--------- .../crucible/clients/beacon/lighthouse.ts | 1 + 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/packages/beacon-node/src/execution/engine/types.ts b/packages/beacon-node/src/execution/engine/types.ts index 76130dc3ec46..24de1b9e6576 100644 --- a/packages/beacon-node/src/execution/engine/types.ts +++ b/packages/beacon-node/src/execution/engine/types.ts @@ -496,7 +496,7 @@ export function deserializeExecutionRequests(serialized: ExecutionRequestsRpc): prefixedRequests = prefixedRequests.slice(2); } - const currentRequestType = Number(prefixedRequests.substring(0, 2)); + const currentRequestType = parseInt(prefixedRequests.substring(0, 2), 16); if (!isExecutionRequestType(currentRequestType)) { throw Error(`Invalid request type currentRequestType=${prefixedRequests.substring(0, 2)}`); diff --git a/packages/beacon-node/src/network/processor/gossipHandlers.ts b/packages/beacon-node/src/network/processor/gossipHandlers.ts index 2a9a9129a983..ec61aa277d03 100644 --- a/packages/beacon-node/src/network/processor/gossipHandlers.ts +++ b/packages/beacon-node/src/network/processor/gossipHandlers.ts @@ -662,7 +662,6 @@ function getBatchHandlers(modules: ValidatorFnsModules, options: GossipHandlerOp // Node may be subscribe to extra subnets (long-lived random subnets). For those, validate the messages // but don't add to attestation pool, to save CPU and RAM if (aggregatorTracker.shouldAggregate(subnet, indexedAttestation.data.slot)) { - // TODO: modify after we change attestationPool due to SingleAttestation const insertOutcome = chain.attestationPool.add( committeeIndex, attestation, diff --git a/packages/beacon-node/src/util/sszBytes.ts b/packages/beacon-node/src/util/sszBytes.ts index ab2b59c68080..13ce4c417ee9 100644 --- a/packages/beacon-node/src/util/sszBytes.ts +++ b/packages/beacon-node/src/util/sszBytes.ts @@ -110,8 +110,7 @@ export function getAttDataFromAttestationSerialized(data: Uint8Array): AttDataBa * This is used for GossipQueue. */ export function getBeaconAttestationGossipIndex(fork: ForkName, data: Uint8Array): AttDataBase64 | null { - const forkSeq = ForkSeq[fork]; - return forkSeq >= ForkSeq.electra + return ForkSeq[fork] >= ForkSeq.electra ? getAttDataFromSingleAttestationSerialized(data) : getAttDataFromAttestationSerialized(data); } @@ -120,8 +119,7 @@ export function getBeaconAttestationGossipIndex(fork: ForkName, data: Uint8Array * Extract slot from `beacon_attestation` gossip message serialized bytes. */ export function getSlotFromBeaconAttestationSerialized(fork: ForkName, data: Uint8Array): Slot | null { - const forkSeq = ForkSeq[fork]; - return forkSeq >= ForkSeq.electra + return ForkSeq[fork] >= ForkSeq.electra ? getSlotFromSingleAttestationSerialized(data) : getSlotFromAttestationSerialized(data); } @@ -130,8 +128,7 @@ export function getSlotFromBeaconAttestationSerialized(fork: ForkName, data: Uin * Extract block root from `beacon_attestation` gossip message serialized bytes. */ export function getBlockRootFromBeaconAttestationSerialized(fork: ForkName, data: Uint8Array): BlockRootHex | null { - const forkSeq = ForkSeq[fork]; - return forkSeq >= ForkSeq.electra + return ForkSeq[fork] >= ForkSeq.electra ? getBlockRootFromSingleAttestationSerialized(data) : getBlockRootFromAttestationSerialized(data); } @@ -181,7 +178,6 @@ export function getSlotFromSingleAttestationSerialized(data: Uint8Array): Slot | /** * Extract committee index from SingleAttestation serialized bytes. * Return null if data is not long enough to extract slot. - * TODO Electra: Rename getSlotFromOffset to reflect generic usage */ export function getCommitteeIndexFromSingleAttestationSerialized( fork: ForkName, @@ -192,27 +188,26 @@ export function getCommitteeIndexFromSingleAttestationSerialized( return null; } - return getSlotFromOffset(data, SINGLE_ATTESTATION_COMMITTEE_INDEX_OFFSET); + return getIndexFromOffset(data, SINGLE_ATTESTATION_COMMITTEE_INDEX_OFFSET); } if (data.length < VARIABLE_FIELD_OFFSET + SLOT_SIZE + COMMITTEE_INDEX_SIZE) { return null; } - return getSlotFromOffset(data, VARIABLE_FIELD_OFFSET + SLOT_SIZE); + return getIndexFromOffset(data, VARIABLE_FIELD_OFFSET + SLOT_SIZE); } /** * Extract attester index from SingleAttestation serialized bytes. * Return null if data is not long enough to extract index. - * TODO Electra: Rename getSlotFromOffset to reflect generic usage */ export function getAttesterIndexFromSingleAttestationSerialized(data: Uint8Array): ValidatorIndex | null { if (data.length !== SINGLE_ATTESTATION_SIZE) { return null; } - return getSlotFromOffset(data, SINGLE_ATTESTATION_ATTESTER_INDEX_OFFSET); + return getIndexFromOffset(data, SINGLE_ATTESTATION_ATTESTER_INDEX_OFFSET); } /** @@ -409,6 +404,13 @@ function getSlotFromOffset(data: Uint8Array, offset: number): Slot | null { return checkSlotHighBytes(data, offset) ? getSlotFromOffsetTrusted(data, offset) : null; } +/** + * Alias of `getSlotFromOffset` for readability + */ +function getIndexFromOffset(data: Uint8Array, offset: number): (ValidatorIndex | CommitteeIndex) | null { + return getSlotFromOffset(data, offset); +} + /** * Read only the first 4 bytes of Slot, max value is 4,294,967,295 will be reached 1634 years after genesis */ diff --git a/packages/cli/test/utils/crucible/clients/beacon/lighthouse.ts b/packages/cli/test/utils/crucible/clients/beacon/lighthouse.ts index 4716e8538c3e..38329d09a40c 100644 --- a/packages/cli/test/utils/crucible/clients/beacon/lighthouse.ts +++ b/packages/cli/test/utils/crucible/clients/beacon/lighthouse.ts @@ -28,6 +28,7 @@ export const generateLighthouseBeaconNode: BeaconNodeGenerator = { "testnet-dir": rootDirMounted, datadir: rootDirMounted, + // Enable the RESTful HTTP API server. Disabled by default. http: null, "http-address": "0.0.0.0", "http-port": ports.beacon.httpPort, From 3dcd668ca94a0584fe5dcef222b2099ea0cdb462 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Fri, 17 Jan 2025 17:19:27 +0000 Subject: [PATCH 24/34] feat: check gas limit in header received from builder (#7336) * feat: check gas limit in header received from builder * Add constant for gas limit adjustment factor * Relax header gas limit check to lower / upper bound * Include parent and target gas limit in warning log * Add note on why validator pubkey used as key instead of index --- .../src/api/impl/validator/index.ts | 2 +- .../chain/produceBlock/produceBlockBody.ts | 43 +++++++++++- .../src/execution/builder/cache.ts | 39 +++++++++++ .../beacon-node/src/execution/builder/http.ts | 15 ++++- .../src/execution/builder/index.ts | 1 + .../src/execution/builder/interface.ts | 5 +- .../src/execution/builder/utils.ts | 19 ++++++ .../test/unit/execution/builder/cache.test.ts | 58 ++++++++++++++++ .../test/unit/execution/builder/utils.test.ts | 66 +++++++++++++++++++ 9 files changed, 243 insertions(+), 5 deletions(-) create mode 100644 packages/beacon-node/src/execution/builder/cache.ts create mode 100644 packages/beacon-node/src/execution/builder/utils.ts create mode 100644 packages/beacon-node/test/unit/execution/builder/cache.test.ts create mode 100644 packages/beacon-node/test/unit/execution/builder/utils.test.ts diff --git a/packages/beacon-node/src/api/impl/validator/index.ts b/packages/beacon-node/src/api/impl/validator/index.ts index 0a84136b556f..fc8934ef5617 100644 --- a/packages/beacon-node/src/api/impl/validator/index.ts +++ b/packages/beacon-node/src/api/impl/validator/index.ts @@ -1524,7 +1524,7 @@ export function getValidatorApi( ); }); - await chain.executionBuilder.registerValidator(filteredRegistrations); + await chain.executionBuilder.registerValidator(currentEpoch, filteredRegistrations); logger.debug("Forwarded validator registrations to connected builder", { epoch: currentEpoch, diff --git a/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts b/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts index d7b36e06abc4..cbd46b1655aa 100644 --- a/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts +++ b/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts @@ -32,11 +32,17 @@ import { ssz, sszTypesFor, } from "@lodestar/types"; -import {Logger, sleep, toHex, toRootHex} from "@lodestar/utils"; +import {Logger, sleep, toHex, toPubkeyHex, toRootHex} from "@lodestar/utils"; import {ZERO_HASH, ZERO_HASH_HEX} from "../../constants/index.js"; import {IEth1ForBlockProduction} from "../../eth1/index.js"; import {numToQuantity} from "../../eth1/provider/utils.js"; -import {IExecutionBuilder, IExecutionEngine, PayloadAttributes, PayloadId} from "../../execution/index.js"; +import { + IExecutionBuilder, + IExecutionEngine, + PayloadAttributes, + PayloadId, + getExpectedGasLimit, +} from "../../execution/index.js"; import {fromGraffitiBuffer} from "../../util/graffiti.js"; import type {BeaconChain} from "../chain.js"; import {CommonBlockBody} from "../interface.js"; @@ -223,6 +229,39 @@ export async function produceBlockBody( fetchedTime, }); + const targetGasLimit = this.executionBuilder.getValidatorRegistration(proposerPubKey)?.gasLimit; + if (!targetGasLimit) { + // This should only happen if cache was cleared due to restart of beacon node + this.logger.warn("Failed to get validator registration, could not check header gas limit", { + slot: blockSlot, + proposerIndex, + proposerPubKey: toPubkeyHex(proposerPubKey), + }); + } else { + const headerGasLimit = builderRes.header.gasLimit; + const parentGasLimit = (currentState as CachedBeaconStateBellatrix).latestExecutionPayloadHeader.gasLimit; + const expectedGasLimit = getExpectedGasLimit(parentGasLimit, targetGasLimit); + + const lowerBound = Math.min(parentGasLimit, expectedGasLimit); + const upperBound = Math.max(parentGasLimit, expectedGasLimit); + + if (headerGasLimit < lowerBound || headerGasLimit > upperBound) { + throw Error( + `Header gas limit ${headerGasLimit} is outside of acceptable range [${lowerBound}, ${upperBound}]` + ); + } + + if (headerGasLimit !== expectedGasLimit) { + this.logger.warn("Header gas limit does not match expected value", { + slot: blockSlot, + headerGasLimit, + expectedGasLimit, + parentGasLimit, + targetGasLimit, + }); + } + } + if (ForkSeq[fork] >= ForkSeq.deneb) { const {blobKzgCommitments} = builderRes; if (blobKzgCommitments === undefined) { diff --git a/packages/beacon-node/src/execution/builder/cache.ts b/packages/beacon-node/src/execution/builder/cache.ts new file mode 100644 index 000000000000..3fe192460a0b --- /dev/null +++ b/packages/beacon-node/src/execution/builder/cache.ts @@ -0,0 +1,39 @@ +import {BLSPubkey, Epoch, bellatrix} from "@lodestar/types"; +import {toPubkeyHex} from "@lodestar/utils"; + +const REGISTRATION_PRESERVE_EPOCHS = 2; + +export type ValidatorRegistration = { + epoch: Epoch; + /** Preferred gas limit of validator */ + gasLimit: number; +}; + +export class ValidatorRegistrationCache { + /** + * Map to track registrations by validator pubkey which is used here instead of + * validator index as `bellatrix.ValidatorRegistrationV1` does not contain the index + * and builder flow in general prefers to use pubkey over index. + */ + private readonly registrationByValidatorPubkey: Map; + constructor() { + this.registrationByValidatorPubkey = new Map(); + } + + add(epoch: Epoch, {pubkey, gasLimit}: bellatrix.ValidatorRegistrationV1): void { + this.registrationByValidatorPubkey.set(toPubkeyHex(pubkey), {epoch, gasLimit}); + } + + prune(epoch: Epoch): void { + for (const [pubkeyHex, registration] of this.registrationByValidatorPubkey.entries()) { + // We only retain an registrations for REGISTRATION_PRESERVE_EPOCHS epochs + if (registration.epoch + REGISTRATION_PRESERVE_EPOCHS < epoch) { + this.registrationByValidatorPubkey.delete(pubkeyHex); + } + } + } + + get(pubkey: BLSPubkey): ValidatorRegistration | undefined { + return this.registrationByValidatorPubkey.get(toPubkeyHex(pubkey)); + } +} diff --git a/packages/beacon-node/src/execution/builder/http.ts b/packages/beacon-node/src/execution/builder/http.ts index 12e45412bafc..d743cedea545 100644 --- a/packages/beacon-node/src/execution/builder/http.ts +++ b/packages/beacon-node/src/execution/builder/http.ts @@ -6,6 +6,7 @@ import {ForkExecution, SLOTS_PER_EPOCH} from "@lodestar/params"; import {parseExecutionPayloadAndBlobsBundle, reconstructFullBlockOrContents} from "@lodestar/state-transition"; import { BLSPubkey, + Epoch, ExecutionPayloadHeader, Root, SignedBeaconBlockOrContents, @@ -19,6 +20,7 @@ import { } from "@lodestar/types"; import {toPrintableUrl} from "@lodestar/utils"; import {Metrics} from "../../metrics/metrics.js"; +import {ValidatorRegistration, ValidatorRegistrationCache} from "./cache.js"; import {IExecutionBuilder} from "./interface.js"; export type ExecutionBuilderHttpOpts = { @@ -60,6 +62,7 @@ const BUILDER_PROPOSAL_DELAY_TOLERANCE = 1000; export class ExecutionBuilderHttp implements IExecutionBuilder { readonly api: BuilderApi; readonly config: ChainForkConfig; + readonly registrations: ValidatorRegistrationCache; readonly issueLocalFcUWithFeeRecipient?: string; // Builder needs to be explicity enabled using updateStatus status = false; @@ -93,6 +96,7 @@ export class ExecutionBuilderHttp implements IExecutionBuilder { ); logger?.info("External builder", {url: toPrintableUrl(baseUrl)}); this.config = config; + this.registrations = new ValidatorRegistrationCache(); this.issueLocalFcUWithFeeRecipient = opts.issueLocalFcUWithFeeRecipient; /** @@ -128,8 +132,17 @@ export class ExecutionBuilderHttp implements IExecutionBuilder { } } - async registerValidator(registrations: bellatrix.SignedValidatorRegistrationV1[]): Promise { + async registerValidator(epoch: Epoch, registrations: bellatrix.SignedValidatorRegistrationV1[]): Promise { (await this.api.registerValidator({registrations})).assertOk(); + + for (const registration of registrations) { + this.registrations.add(epoch, registration.message); + } + this.registrations.prune(epoch); + } + + getValidatorRegistration(pubkey: BLSPubkey): ValidatorRegistration | undefined { + return this.registrations.get(pubkey); } async getHeader( diff --git a/packages/beacon-node/src/execution/builder/index.ts b/packages/beacon-node/src/execution/builder/index.ts index fff66a3e8bc5..929e88461c8a 100644 --- a/packages/beacon-node/src/execution/builder/index.ts +++ b/packages/beacon-node/src/execution/builder/index.ts @@ -2,6 +2,7 @@ import {ChainForkConfig} from "@lodestar/config"; import {Logger} from "@lodestar/logger"; import {Metrics} from "../../metrics/metrics.js"; import {IExecutionBuilder} from "./interface.js"; +export {getExpectedGasLimit} from "./utils.js"; import {ExecutionBuilderHttp, ExecutionBuilderHttpOpts, defaultExecutionBuilderHttpOpts} from "./http.js"; diff --git a/packages/beacon-node/src/execution/builder/interface.ts b/packages/beacon-node/src/execution/builder/interface.ts index 06cdc1da4ed0..f326ec4bc451 100644 --- a/packages/beacon-node/src/execution/builder/interface.ts +++ b/packages/beacon-node/src/execution/builder/interface.ts @@ -1,6 +1,7 @@ import {ForkExecution} from "@lodestar/params"; import { BLSPubkey, + Epoch, ExecutionPayloadHeader, Root, SignedBeaconBlockOrContents, @@ -12,6 +13,7 @@ import { deneb, electra, } from "@lodestar/types"; +import {ValidatorRegistration} from "./cache.js"; export interface IExecutionBuilder { /** @@ -28,7 +30,8 @@ export interface IExecutionBuilder { updateStatus(shouldEnable: boolean): void; checkStatus(): Promise; - registerValidator(registrations: bellatrix.SignedValidatorRegistrationV1[]): Promise; + registerValidator(epoch: Epoch, registrations: bellatrix.SignedValidatorRegistrationV1[]): Promise; + getValidatorRegistration(pubkey: BLSPubkey): ValidatorRegistration | undefined; getHeader( fork: ForkExecution, slot: Slot, diff --git a/packages/beacon-node/src/execution/builder/utils.ts b/packages/beacon-node/src/execution/builder/utils.ts new file mode 100644 index 000000000000..76d75fda2b35 --- /dev/null +++ b/packages/beacon-node/src/execution/builder/utils.ts @@ -0,0 +1,19 @@ +/** + * From https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md + */ +const gasLimitAdjustmentFactor = 1024; + +/** + * Calculates expected gas limit based on parent gas limit and target gas limit + */ +export function getExpectedGasLimit(parentGasLimit: number, targetGasLimit: number): number { + const maxGasLimitDifference = Math.max(Math.floor(parentGasLimit / gasLimitAdjustmentFactor) - 1, 0); + + if (targetGasLimit > parentGasLimit) { + const gasDiff = targetGasLimit - parentGasLimit; + return parentGasLimit + Math.min(gasDiff, maxGasLimitDifference); + } + + const gasDiff = parentGasLimit - targetGasLimit; + return parentGasLimit - Math.min(gasDiff, maxGasLimitDifference); +} diff --git a/packages/beacon-node/test/unit/execution/builder/cache.test.ts b/packages/beacon-node/test/unit/execution/builder/cache.test.ts new file mode 100644 index 000000000000..60667caf0a50 --- /dev/null +++ b/packages/beacon-node/test/unit/execution/builder/cache.test.ts @@ -0,0 +1,58 @@ +import {ssz} from "@lodestar/types"; +import {beforeEach, describe, expect, it} from "vitest"; +import {ValidatorRegistrationCache} from "../../../../src/execution/builder/cache.js"; + +describe("ValidatorRegistrationCache", () => { + const gasLimit1 = 30000000; + const gasLimit2 = 36000000; + + const validatorPubkey1 = new Uint8Array(48).fill(1); + const validatorPubkey2 = new Uint8Array(48).fill(2); + + const validatorRegistration1 = ssz.bellatrix.ValidatorRegistrationV1.defaultValue(); + validatorRegistration1.pubkey = validatorPubkey1; + validatorRegistration1.gasLimit = gasLimit1; + + const validatorRegistration2 = ssz.bellatrix.ValidatorRegistrationV1.defaultValue(); + validatorRegistration2.pubkey = validatorPubkey2; + validatorRegistration2.gasLimit = gasLimit2; + + let cache: ValidatorRegistrationCache; + + beforeEach(() => { + // max 2 items + cache = new ValidatorRegistrationCache(); + cache.add(1, validatorRegistration1); + cache.add(3, validatorRegistration2); + }); + + it("get for registered validator", () => { + expect(cache.get(validatorPubkey1)?.gasLimit).toBe(gasLimit1); + }); + + it("get for unknown validator", () => { + const unknownValidatorPubkey = new Uint8Array(48).fill(3); + expect(cache.get(unknownValidatorPubkey)).toBe(undefined); + }); + + it("override and get latest", () => { + const newGasLimit = 60000000; + const registration = ssz.bellatrix.ValidatorRegistrationV1.defaultValue(); + registration.pubkey = validatorPubkey1; + registration.gasLimit = newGasLimit; + + cache.add(5, registration); + + expect(cache.get(validatorPubkey1)?.gasLimit).toBe(newGasLimit); + }); + + it("prune", () => { + cache.prune(4); + + // No registration as it has been pruned + expect(cache.get(validatorPubkey1)).toBe(undefined); + + // Registration hasn't been pruned + expect(cache.get(validatorPubkey2)?.gasLimit).toBe(gasLimit2); + }); +}); diff --git a/packages/beacon-node/test/unit/execution/builder/utils.test.ts b/packages/beacon-node/test/unit/execution/builder/utils.test.ts new file mode 100644 index 000000000000..90520fb00e1f --- /dev/null +++ b/packages/beacon-node/test/unit/execution/builder/utils.test.ts @@ -0,0 +1,66 @@ +import {describe, expect, it} from "vitest"; +import {getExpectedGasLimit} from "../../../../src/execution/builder/utils.js"; + +describe("execution / builder / utils", () => { + describe("getExpectedGasLimit", () => { + const testCases: { + name: string; + parentGasLimit: number; + targetGasLimit: number; + expected: number; + }[] = [ + { + name: "Increase within limit", + parentGasLimit: 30000000, + targetGasLimit: 30000100, + expected: 30000100, + }, + { + name: "Increase exceeding limit", + parentGasLimit: 30000000, + targetGasLimit: 36000000, + expected: 30029295, // maxGasLimitDifference = (30000000 / 1024) - 1 = 29295 + }, + { + name: "Decrease within limit", + parentGasLimit: 30000000, + targetGasLimit: 29999990, + expected: 29999990, + }, + { + name: "Decrease exceeding limit", + parentGasLimit: 36000000, + targetGasLimit: 30000000, + expected: 35964845, // maxGasLimitDifference = (36000000 / 1024) - 1 = 35155 + }, + { + name: "Target equals parent", + parentGasLimit: 30000000, + targetGasLimit: 30000000, + expected: 30000000, // No change + }, + { + name: "Very small parent gas limit", + parentGasLimit: 1025, + targetGasLimit: 2000, + expected: 1025, + }, + { + name: "Target far below parent but limited", + parentGasLimit: 30000000, + targetGasLimit: 10000000, + expected: 29970705, // maxGasLimitDifference = (30000000 / 1024) - 1 = 29295 + }, + { + name: "Parent gas limit underflows", + parentGasLimit: 1023, + targetGasLimit: 30000000, + expected: 1023, + }, + ]; + + it.each(testCases)("$name", ({parentGasLimit, targetGasLimit, expected}) => { + expect(getExpectedGasLimit(parentGasLimit, targetGasLimit)).toBe(expected); + }); + }); +}); From eb2ee60153a5cbdcdfaa70cedf4629190369dbf9 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Fri, 17 Jan 2025 19:07:34 +0000 Subject: [PATCH 25/34] fix: use correct fork constants to limit max request blocks/blobs count (#7368) * fix: use correct fork constants to limit max request blocks/blobs count * Add helper to get max request blob sidecars by fork * Add a more generic getter to fork config * Formatting * Revert "Formatting" This reverts commit 28af07153d40a7181a3c92a87ebb6cbb6642660d. * Revert "Add a more generic getter to fork config" This reverts commit 40b51d2acebbf04b5bba6e5cd0640af41248551b. --- .../network/reqresp/handlers/beaconBlocksByRange.ts | 13 ++++++++----- .../network/reqresp/handlers/blobSidecarsByRange.ts | 12 ++++++++---- .../beacon-node/src/network/reqresp/rateLimit.ts | 6 +++--- packages/beacon-node/src/network/reqresp/types.ts | 4 ++-- packages/beacon-node/src/util/types.ts | 11 ++++------- packages/config/src/forkConfig/index.ts | 3 +++ packages/config/src/forkConfig/types.ts | 2 ++ 7 files changed, 30 insertions(+), 21 deletions(-) diff --git a/packages/beacon-node/src/network/reqresp/handlers/beaconBlocksByRange.ts b/packages/beacon-node/src/network/reqresp/handlers/beaconBlocksByRange.ts index 620b98be63d8..ce563c8f3d82 100644 --- a/packages/beacon-node/src/network/reqresp/handlers/beaconBlocksByRange.ts +++ b/packages/beacon-node/src/network/reqresp/handlers/beaconBlocksByRange.ts @@ -1,4 +1,5 @@ -import {GENESIS_SLOT, MAX_REQUEST_BLOCKS} from "@lodestar/params"; +import {BeaconConfig} from "@lodestar/config"; +import {GENESIS_SLOT, MAX_REQUEST_BLOCKS, MAX_REQUEST_BLOCKS_DENEB, isForkBlobs} from "@lodestar/params"; import {RespStatus, ResponseError, ResponseOutgoing} from "@lodestar/reqresp"; import {deneb, phase0} from "@lodestar/types"; import {fromHex} from "@lodestar/utils"; @@ -12,7 +13,7 @@ export async function* onBeaconBlocksByRange( chain: IBeaconChain, db: IBeaconDb ): AsyncIterable { - const {startSlot, count} = validateBeaconBlocksByRangeRequest(request); + const {startSlot, count} = validateBeaconBlocksByRangeRequest(chain.config, request); const endSlot = startSlot + count; const finalized = db.blockArchive; @@ -69,6 +70,7 @@ export async function* onBeaconBlocksByRange( } export function validateBeaconBlocksByRangeRequest( + config: BeaconConfig, request: deneb.BlobSidecarsByRangeRequest ): deneb.BlobSidecarsByRangeRequest { const {startSlot} = request; @@ -84,9 +86,10 @@ export function validateBeaconBlocksByRangeRequest( // step > 1 is deprecated, see https://github.com/ethereum/consensus-specs/pull/2856 - if (count > MAX_REQUEST_BLOCKS) { - // TODO: This is probably not right as `BeaconBlocksByRangeV2` takes at most `MAX_REQUEST_BLOCKS_DENEB` - count = MAX_REQUEST_BLOCKS; + const maxRequestBlocks = isForkBlobs(config.getForkName(startSlot)) ? MAX_REQUEST_BLOCKS_DENEB : MAX_REQUEST_BLOCKS; + + if (count > maxRequestBlocks) { + count = maxRequestBlocks; } return {startSlot, count}; diff --git a/packages/beacon-node/src/network/reqresp/handlers/blobSidecarsByRange.ts b/packages/beacon-node/src/network/reqresp/handlers/blobSidecarsByRange.ts index 9cac846fdb61..557d01a22840 100644 --- a/packages/beacon-node/src/network/reqresp/handlers/blobSidecarsByRange.ts +++ b/packages/beacon-node/src/network/reqresp/handlers/blobSidecarsByRange.ts @@ -1,4 +1,5 @@ -import {BLOBSIDECAR_FIXED_SIZE, GENESIS_SLOT, MAX_REQUEST_BLOCKS_DENEB} from "@lodestar/params"; +import {BeaconConfig} from "@lodestar/config"; +import {BLOBSIDECAR_FIXED_SIZE, GENESIS_SLOT} from "@lodestar/params"; import {RespStatus, ResponseError, ResponseOutgoing} from "@lodestar/reqresp"; import {Slot, deneb} from "@lodestar/types"; import {fromHex} from "@lodestar/utils"; @@ -12,7 +13,7 @@ export async function* onBlobSidecarsByRange( db: IBeaconDb ): AsyncIterable { // Non-finalized range of blobs - const {startSlot, count} = validateBlobSidecarsByRangeRequest(request); + const {startSlot, count} = validateBlobSidecarsByRangeRequest(chain.config, request); const endSlot = startSlot + count; const finalized = db.blobSidecarsArchive; @@ -90,6 +91,7 @@ export function* iterateBlobBytesFromWrapper( } export function validateBlobSidecarsByRangeRequest( + config: BeaconConfig, request: deneb.BlobSidecarsByRangeRequest ): deneb.BlobSidecarsByRangeRequest { const {startSlot} = request; @@ -103,8 +105,10 @@ export function validateBlobSidecarsByRangeRequest( throw new ResponseError(RespStatus.INVALID_REQUEST, "startSlot < genesis"); } - if (count > MAX_REQUEST_BLOCKS_DENEB) { - count = MAX_REQUEST_BLOCKS_DENEB; + const maxRequestBlobSidecars = config.getMaxRequestBlobSidecars(config.getForkName(startSlot)); + + if (count > maxRequestBlobSidecars) { + count = maxRequestBlobSidecars; } return {startSlot, count}; diff --git a/packages/beacon-node/src/network/reqresp/rateLimit.ts b/packages/beacon-node/src/network/reqresp/rateLimit.ts index ce8a996a49a8..7553310b96d7 100644 --- a/packages/beacon-node/src/network/reqresp/rateLimit.ts +++ b/packages/beacon-node/src/network/reqresp/rateLimit.ts @@ -1,10 +1,10 @@ -import {ChainConfig} from "@lodestar/config"; +import {BeaconConfig} from "@lodestar/config"; import {MAX_REQUEST_BLOCKS, MAX_REQUEST_LIGHT_CLIENT_UPDATES} from "@lodestar/params"; import {InboundRateLimitQuota} from "@lodestar/reqresp"; import {ReqRespMethod, RequestBodyByMethod} from "./types.js"; import {requestSszTypeByMethod} from "./types.js"; -export const rateLimitQuotas: (config: ChainConfig) => Record = (config) => ({ +export const rateLimitQuotas: (config: BeaconConfig) => Record = (config) => ({ [ReqRespMethod.Status]: { // Rationale: https://github.com/sigp/lighthouse/blob/bf533c8e42cc73c35730e285c21df8add0195369/beacon_node/lighthouse_network/src/rpc/mod.rs#L118-L130 byPeer: {quota: 5, quotaTimeMs: 15_000}, @@ -67,7 +67,7 @@ export const rateLimitQuotas: (config: ChainConfig) => Record( - config: ChainConfig, + config: BeaconConfig, method: T, fn: (req: RequestBodyByMethod[T]) => number ): (reqData: Uint8Array) => number { diff --git a/packages/beacon-node/src/network/reqresp/types.ts b/packages/beacon-node/src/network/reqresp/types.ts index ac14b24eaf24..476262bc51a2 100644 --- a/packages/beacon-node/src/network/reqresp/types.ts +++ b/packages/beacon-node/src/network/reqresp/types.ts @@ -1,5 +1,5 @@ import {Type} from "@chainsafe/ssz"; -import {ChainConfig} from "@lodestar/config"; +import {BeaconConfig} from "@lodestar/config"; import {ForkLightClient, ForkName, isForkLightClient} from "@lodestar/params"; import {Protocol, ProtocolHandler, ReqRespRequest} from "@lodestar/reqresp"; import { @@ -72,7 +72,7 @@ type ResponseBodyByMethod = { /** Request SSZ type for each method and ForkName */ // TODO Electra: Currently setting default fork to deneb because not every caller of requestSszTypeByMethod can provide fork info export const requestSszTypeByMethod: ( - config: ChainConfig, + config: BeaconConfig, fork?: ForkName ) => { [K in ReqRespMethod]: RequestBodyByMethod[K] extends null ? null : Type; diff --git a/packages/beacon-node/src/util/types.ts b/packages/beacon-node/src/util/types.ts index 2a99fc34bcc2..4133d6db102f 100644 --- a/packages/beacon-node/src/util/types.ts +++ b/packages/beacon-node/src/util/types.ts @@ -1,6 +1,6 @@ import {ContainerType, ListCompositeType, ValueOf} from "@chainsafe/ssz"; -import {ChainConfig} from "@lodestar/config"; -import {ForkName, isForkPostElectra} from "@lodestar/params"; +import {BeaconConfig} from "@lodestar/config"; +import {ForkName} from "@lodestar/params"; import {ssz} from "@lodestar/types"; // Misc SSZ types used only in the beacon-node package, no need to upstream to types @@ -15,9 +15,6 @@ export const signedBLSToExecutionChangeVersionedType = new ContainerType( ); export type SignedBLSToExecutionChangeVersioned = ValueOf; -export const BlobSidecarsByRootRequestType = (fork: ForkName, config: ChainConfig) => - new ListCompositeType( - ssz.deneb.BlobIdentifier, - isForkPostElectra(fork) ? config.MAX_REQUEST_BLOB_SIDECARS_ELECTRA : config.MAX_REQUEST_BLOB_SIDECARS - ); +export const BlobSidecarsByRootRequestType = (fork: ForkName, config: BeaconConfig) => + new ListCompositeType(ssz.deneb.BlobIdentifier, config.getMaxRequestBlobSidecars(fork)); export type BlobSidecarsByRootRequest = ValueOf>; diff --git a/packages/config/src/forkConfig/index.ts b/packages/config/src/forkConfig/index.ts index 6540106bb886..9551627188d5 100644 --- a/packages/config/src/forkConfig/index.ts +++ b/packages/config/src/forkConfig/index.ts @@ -133,5 +133,8 @@ export function createForkConfig(config: ChainConfig): ForkConfig { getMaxBlobsPerBlock(fork: ForkName): number { return isForkPostElectra(fork) ? config.MAX_BLOBS_PER_BLOCK_ELECTRA : config.MAX_BLOBS_PER_BLOCK; }, + getMaxRequestBlobSidecars(fork: ForkName): number { + return isForkPostElectra(fork) ? config.MAX_REQUEST_BLOB_SIDECARS_ELECTRA : config.MAX_REQUEST_BLOB_SIDECARS; + }, }; } diff --git a/packages/config/src/forkConfig/types.ts b/packages/config/src/forkConfig/types.ts index 420cd6bcd244..c7874482770a 100644 --- a/packages/config/src/forkConfig/types.ts +++ b/packages/config/src/forkConfig/types.ts @@ -41,4 +41,6 @@ export type ForkConfig = { getBlobsForkTypes(slot: Slot): SSZTypesFor; /** Get max blobs per block by hard-fork */ getMaxBlobsPerBlock(fork: ForkName): number; + /** Get max request blob sidecars by hard-fork */ + getMaxRequestBlobSidecars(fork: ForkName): number; }; From 06831cfc79ab667f4ce4ee897320ce3f4a495854 Mon Sep 17 00:00:00 2001 From: NC <17676176+ensi321@users.noreply.github.com> Date: Fri, 17 Jan 2025 12:09:42 -0800 Subject: [PATCH 26/34] fix: timeUntilNext calculation before genesis (#7372) Fix timeUntilNext before genesis --- packages/validator/src/util/clock.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/validator/src/util/clock.ts b/packages/validator/src/util/clock.ts index 6b7e0868233a..d98a84dfe91c 100644 --- a/packages/validator/src/util/clock.ts +++ b/packages/validator/src/util/clock.ts @@ -120,13 +120,13 @@ export class Clock implements IClock { if (msFromGenesis >= 0) { return milliSecondsPerSlot - (msFromGenesis % milliSecondsPerSlot); } - return Math.abs(msFromGenesis % milliSecondsPerSlot); + return Math.abs(msFromGenesis) % milliSecondsPerSlot; } const milliSecondsPerEpoch = SLOTS_PER_EPOCH * milliSecondsPerSlot; if (msFromGenesis >= 0) { return milliSecondsPerEpoch - (msFromGenesis % milliSecondsPerEpoch); } - return Math.abs(msFromGenesis % milliSecondsPerEpoch); + return Math.abs(msFromGenesis) % milliSecondsPerEpoch; } } From fcf82c3b2466eab1a1dc15b7b8ff94d8079a2966 Mon Sep 17 00:00:00 2001 From: Matthew Keil Date: Tue, 21 Jan 2025 00:58:49 +0700 Subject: [PATCH 27/34] fix: update ssz to use non-simd on systems that do not support (#7371) * chore: update ssz to use non-simd on systems that do not support * chore: update as-sha256 and persistent-merkle-tree direct deps * chore: update yarn.lock * chore: update ssz package versions * chore: add ssz resolution stemming from sub packages of prover * chore: update yarn.lock * fix: remove resolution for ssz * chore: update yarn.lock --- packages/api/package.json | 4 +-- packages/beacon-node/package.json | 6 ++-- packages/cli/package.json | 4 +-- packages/config/package.json | 2 +- packages/db/package.json | 2 +- packages/fork-choice/package.json | 2 +- packages/light-client/package.json | 6 ++-- packages/state-transition/package.json | 8 +++--- packages/types/package.json | 2 +- packages/utils/package.json | 2 +- packages/validator/package.json | 2 +- yarn.lock | 38 +++++++++++++------------- 12 files changed, 39 insertions(+), 39 deletions(-) diff --git a/packages/api/package.json b/packages/api/package.json index c2234e0db5ee..a1ecab9cdef9 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -70,8 +70,8 @@ "check-readme": "typescript-docs-verifier" }, "dependencies": { - "@chainsafe/persistent-merkle-tree": "^0.8.0", - "@chainsafe/ssz": "^0.18.0", + "@chainsafe/persistent-merkle-tree": "^0.9.1", + "@chainsafe/ssz": "^0.19.1", "@lodestar/config": "^1.25.0", "@lodestar/params": "^1.25.0", "@lodestar/types": "^1.25.0", diff --git a/packages/beacon-node/package.json b/packages/beacon-node/package.json index 4be101ba92a6..960bf1ee11da 100644 --- a/packages/beacon-node/package.json +++ b/packages/beacon-node/package.json @@ -94,17 +94,17 @@ "check-readme": "typescript-docs-verifier" }, "dependencies": { - "@chainsafe/as-sha256": "^0.5.0", + "@chainsafe/as-sha256": "^0.6.1", "@chainsafe/blst": "^2.1.0", "@chainsafe/discv5": "^9.0.0", "@chainsafe/enr": "^3.0.0", "@chainsafe/libp2p-gossipsub": "^13.0.0", "@chainsafe/libp2p-identify": "^1.0.0", "@chainsafe/libp2p-noise": "^15.0.0", - "@chainsafe/persistent-merkle-tree": "^0.8.0", + "@chainsafe/persistent-merkle-tree": "^0.9.1", "@chainsafe/prometheus-gc-stats": "^1.0.0", "@chainsafe/pubkey-index-map": "2.0.0", - "@chainsafe/ssz": "^0.18.0", + "@chainsafe/ssz": "^0.19.1", "@chainsafe/threads": "^1.11.1", "@ethersproject/abi": "^5.7.0", "@fastify/bearer-auth": "^10.0.1", diff --git a/packages/cli/package.json b/packages/cli/package.json index b9d9ce7c1644..6481cc930c53 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -56,8 +56,8 @@ "@chainsafe/blst": "^2.1.0", "@chainsafe/discv5": "^9.0.0", "@chainsafe/enr": "^3.0.0", - "@chainsafe/persistent-merkle-tree": "^0.8.0", - "@chainsafe/ssz": "^0.18.0", + "@chainsafe/persistent-merkle-tree": "^0.9.1", + "@chainsafe/ssz": "^0.19.1", "@chainsafe/threads": "^1.11.1", "@libp2p/crypto": "^4.1.0", "@libp2p/peer-id": "^4.1.0", diff --git a/packages/config/package.json b/packages/config/package.json index 8e9047a12dba..c35aeffb81db 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -64,7 +64,7 @@ "blockchain" ], "dependencies": { - "@chainsafe/ssz": "^0.18.0", + "@chainsafe/ssz": "^0.19.1", "@lodestar/params": "^1.25.0", "@lodestar/types": "^1.25.0", "@lodestar/utils": "^1.25.0" diff --git a/packages/db/package.json b/packages/db/package.json index 30cf7e6bbc25..e302520dab22 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -35,7 +35,7 @@ "check-readme": "typescript-docs-verifier" }, "dependencies": { - "@chainsafe/ssz": "^0.18.0", + "@chainsafe/ssz": "^0.19.1", "@lodestar/config": "^1.25.0", "@lodestar/utils": "^1.25.0", "classic-level": "^1.4.1", diff --git a/packages/fork-choice/package.json b/packages/fork-choice/package.json index 1102bca3c6b8..f62e1f2a877c 100644 --- a/packages/fork-choice/package.json +++ b/packages/fork-choice/package.json @@ -36,7 +36,7 @@ "check-readme": "typescript-docs-verifier" }, "dependencies": { - "@chainsafe/ssz": "^0.18.0", + "@chainsafe/ssz": "^0.19.1", "@lodestar/config": "^1.25.0", "@lodestar/params": "^1.25.0", "@lodestar/state-transition": "^1.25.0", diff --git a/packages/light-client/package.json b/packages/light-client/package.json index 9f408ac1cab5..95dadc8110c6 100644 --- a/packages/light-client/package.json +++ b/packages/light-client/package.json @@ -75,8 +75,8 @@ "dependencies": { "@chainsafe/bls": "7.1.3", "@chainsafe/blst": "^0.2.0", - "@chainsafe/persistent-merkle-tree": "^0.8.0", - "@chainsafe/ssz": "^0.18.0", + "@chainsafe/persistent-merkle-tree": "^0.9.1", + "@chainsafe/ssz": "^0.19.1", "@lodestar/api": "^1.25.0", "@lodestar/config": "^1.25.0", "@lodestar/params": "^1.25.0", @@ -85,7 +85,7 @@ "mitt": "^3.0.0" }, "devDependencies": { - "@chainsafe/as-sha256": "^0.5.0", + "@chainsafe/as-sha256": "^0.6.1", "@types/qs": "^6.9.7", "fastify": "^5.0.0", "qs": "^6.11.1", diff --git a/packages/state-transition/package.json b/packages/state-transition/package.json index 38c22e84d568..db142ffd24f9 100644 --- a/packages/state-transition/package.json +++ b/packages/state-transition/package.json @@ -58,12 +58,12 @@ }, "types": "lib/index.d.ts", "dependencies": { - "@chainsafe/as-sha256": "^0.5.0", + "@chainsafe/as-sha256": "^0.6.1", "@chainsafe/blst": "^2.1.0", - "@chainsafe/persistent-merkle-tree": "^0.8.0", - "@chainsafe/persistent-ts": "^0.19.1", + "@chainsafe/persistent-merkle-tree": "^0.9.1", + "@chainsafe/persistent-ts": "^0.19.2", "@chainsafe/pubkey-index-map": "2.0.0", - "@chainsafe/ssz": "^0.18.0", + "@chainsafe/ssz": "^0.19.1", "@chainsafe/swap-or-not-shuffle": "^0.0.2", "@lodestar/config": "^1.25.0", "@lodestar/params": "^1.25.0", diff --git a/packages/types/package.json b/packages/types/package.json index 6db16772d3f7..77f0f2e6e38b 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -73,7 +73,7 @@ }, "types": "lib/index.d.ts", "dependencies": { - "@chainsafe/ssz": "^0.18.0", + "@chainsafe/ssz": "^0.19.1", "@lodestar/params": "^1.25.0", "ethereum-cryptography": "^2.0.0" }, diff --git a/packages/utils/package.json b/packages/utils/package.json index 4180998f2caf..c8cf3a502194 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -39,7 +39,7 @@ }, "types": "lib/index.d.ts", "dependencies": { - "@chainsafe/as-sha256": "^0.5.0", + "@chainsafe/as-sha256": "^0.6.1", "any-signal": "3.0.1", "bigint-buffer": "^1.1.5", "case": "^1.6.3", diff --git a/packages/validator/package.json b/packages/validator/package.json index c71c6b68872a..72940759ecbd 100644 --- a/packages/validator/package.json +++ b/packages/validator/package.json @@ -46,7 +46,7 @@ ], "dependencies": { "@chainsafe/blst": "^2.1.0", - "@chainsafe/ssz": "^0.18.0", + "@chainsafe/ssz": "^0.19.1", "@lodestar/api": "^1.25.0", "@lodestar/config": "^1.25.0", "@lodestar/db": "^1.25.0", diff --git a/yarn.lock b/yarn.lock index 60ff9034fd6b..ab6fa59fbdfc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -357,10 +357,10 @@ resolved "https://registry.yarnpkg.com/@chainsafe/as-chacha20poly1305/-/as-chacha20poly1305-0.1.0.tgz#7da6f8796f9b42dac6e830a086d964f1f9189e09" integrity sha512-BpNcL8/lji/GM3+vZ/bgRWqJ1q5kwvTFmGPk7pxm/QQZDbaMI98waOHjEymTjq2JmdD/INdNBFOVSyJofXg7ew== -"@chainsafe/as-sha256@0.5.0", "@chainsafe/as-sha256@^0.5.0": - version "0.5.0" - resolved "https://registry.yarnpkg.com/@chainsafe/as-sha256/-/as-sha256-0.5.0.tgz#2523fbef2b80b5000f9aa71f4a76e5c2c5c076bb" - integrity sha512-dTIY6oUZNdC5yDTVP5Qc9hAlKAsn0QTQ2DnQvvsbTnKSTbYs3p5RPN0aIUqN0liXei/9h24c7V0dkV44cnWIQA== +"@chainsafe/as-sha256@0.6.1", "@chainsafe/as-sha256@^0.6.1": + version "0.6.1" + resolved "https://registry.yarnpkg.com/@chainsafe/as-sha256/-/as-sha256-0.6.1.tgz#0643cf699118f2a0db6d8ce6e8d68fe7c5084158" + integrity sha512-bYDOK5aK7NYE/ZZ/A3C+Q8ZrEWdzpH80fcEotjgX3pmntv3SJfOTTYE53mjnPSEZFlv/rST0H/ZETsz8Wab9iw== "@chainsafe/as-sha256@^0.4.1": version "0.4.1" @@ -595,12 +595,12 @@ dependencies: "@chainsafe/is-ip" "^2.0.1" -"@chainsafe/persistent-merkle-tree@0.8.0", "@chainsafe/persistent-merkle-tree@^0.8.0": - version "0.8.0" - resolved "https://registry.yarnpkg.com/@chainsafe/persistent-merkle-tree/-/persistent-merkle-tree-0.8.0.tgz#18e2f0a5de3a0b59c6e5be8797a78e0d209dd7dc" - integrity sha512-hh6C1JO6SKlr0QGNTNtTLqgGVMA/Bc20wD6CeMHp+wqbFKCULRJuBUxhF4WDx/7mX8QlqF3nFriF/Eo8oYJ4/A== +"@chainsafe/persistent-merkle-tree@0.9.1", "@chainsafe/persistent-merkle-tree@^0.9.1": + version "0.9.1" + resolved "https://registry.yarnpkg.com/@chainsafe/persistent-merkle-tree/-/persistent-merkle-tree-0.9.1.tgz#fd29d36381f53e1d04c3ffbbb92eb91a9f460f96" + integrity sha512-UVMKbWcKr1Y56qgy6m1W4peUaCwwsn/W2Vd5Ffu7Tb9lMW80q0IaW7e97jvyWd2SDfqVQ1p2XP4bBj6WAcfZvg== dependencies: - "@chainsafe/as-sha256" "0.5.0" + "@chainsafe/as-sha256" "0.6.1" "@chainsafe/hashtree" "1.0.1" "@noble/hashes" "^1.3.0" @@ -612,10 +612,10 @@ "@chainsafe/as-sha256" "^0.4.1" "@noble/hashes" "^1.3.0" -"@chainsafe/persistent-ts@^0.19.1": - version "0.19.1" - resolved "https://registry.npmjs.org/@chainsafe/persistent-ts/-/persistent-ts-0.19.1.tgz" - integrity sha512-fUFFFFxdcpYkMAHnjm83EYL/R/smtVmEkJr3FGSI6dwPk4ue9rXjEHf7FTd3V8AbVOcTJGriN4cYf2V+HOYkjQ== +"@chainsafe/persistent-ts@^0.19.2": + version "0.19.2" + resolved "https://registry.yarnpkg.com/@chainsafe/persistent-ts/-/persistent-ts-0.19.2.tgz#f7da892b76f5a159d568bacd96563c2c56c3e99c" + integrity sha512-CFz1jniPezZJmrPdYkm1wbCj7+H0yWfrAs4qE4HJ3ZWWqcTu3KQWgqUVZ7J4WGxuYU9HAXJSYsO3xtAqHWm8YQ== "@chainsafe/prometheus-gc-stats@^1.0.0": version "1.0.2" @@ -666,13 +666,13 @@ "@chainsafe/as-sha256" "^0.4.1" "@chainsafe/persistent-merkle-tree" "^0.6.1" -"@chainsafe/ssz@^0.18.0": - version "0.18.0" - resolved "https://registry.yarnpkg.com/@chainsafe/ssz/-/ssz-0.18.0.tgz#773d40df9dff3b6a2a4c6685d9797abceb9d36f7" - integrity sha512-1ikTjk3JK6+fsGWiT5IvQU0AP6gF3fDzGmPfkKthbcbgTUR8fjB83Ywp9ko/ZoiDGfrSFkATgT4hvRzclu0IAA== +"@chainsafe/ssz@^0.19.1": + version "0.19.1" + resolved "https://registry.yarnpkg.com/@chainsafe/ssz/-/ssz-0.19.1.tgz#e6f88561cf83204d23a401cb8f4344eedcbcbae4" + integrity sha512-LsEx6vbQPxNG3ydjabjIHcvhbW6GvugTw2tAIB39mmLNIsbYk1YUdskHHsM29+Oe3937ekdk2NZpo3QHye5zJA== dependencies: - "@chainsafe/as-sha256" "0.5.0" - "@chainsafe/persistent-merkle-tree" "0.8.0" + "@chainsafe/as-sha256" "0.6.1" + "@chainsafe/persistent-merkle-tree" "0.9.1" "@chainsafe/swap-or-not-shuffle-darwin-arm64@0.0.2": version "0.0.2" From 3eef8ea47061ab420bb137c9c651621c4bdaf05f Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Mon, 20 Jan 2025 21:30:35 +0000 Subject: [PATCH 28/34] fix: update `getPendingBalanceToWithdraw` to work with uncommitted changes (#7375) * fix: commit pending partial withdrawals after adding new entry * Get pending partial withdrawals only once before processing withdrawal requests * Revert last commit, I don't think this is right * Update getPendingBalanceToWithdraw as suggested by Tuyen --- packages/state-transition/src/util/validator.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/state-transition/src/util/validator.ts b/packages/state-transition/src/util/validator.ts index 071d936ead23..732a98802c70 100644 --- a/packages/state-transition/src/util/validator.ts +++ b/packages/state-transition/src/util/validator.ts @@ -83,8 +83,12 @@ export function getMaxEffectiveBalance(withdrawalCredentials: Uint8Array): numbe } export function getPendingBalanceToWithdraw(state: CachedBeaconStateElectra, validatorIndex: ValidatorIndex): number { - return state.pendingPartialWithdrawals - .getAllReadonly() - .filter((item) => item.validatorIndex === validatorIndex) - .reduce((total, item) => total + Number(item.amount), 0); + let total = 0; + for (let i = 0; i < state.pendingPartialWithdrawals.length; i++) { + const item = state.pendingPartialWithdrawals.get(i); + if (item.validatorIndex === validatorIndex) { + total += Number(item.amount); + } + } + return total; } From d584aed680aafedec56e19a77162a847bbd21a33 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Mon, 20 Jan 2025 21:30:44 +0000 Subject: [PATCH 29/34] fix: consistently check no pending withdrawals when processing voluntary exit (#7379) * fix: consistently check no pending withdrawals when processing voluntary exit * Lint --- .../beacon-node/src/chain/opPools/opPool.ts | 2 +- .../src/chain/validation/voluntaryExit.ts | 2 +- .../src/block/processVoluntaryExit.ts | 27 ++++++------------- 3 files changed, 10 insertions(+), 21 deletions(-) diff --git a/packages/beacon-node/src/chain/opPools/opPool.ts b/packages/beacon-node/src/chain/opPools/opPool.ts index 5eec1597a24e..819873d2e835 100644 --- a/packages/beacon-node/src/chain/opPools/opPool.ts +++ b/packages/beacon-node/src/chain/opPools/opPool.ts @@ -247,7 +247,7 @@ export class OpPool { for (const voluntaryExit of this.voluntaryExits.values()) { if ( !toBeSlashedIndices.has(voluntaryExit.message.validatorIndex) && - isValidVoluntaryExit(state, voluntaryExit, false) && + isValidVoluntaryExit(stateFork, state, voluntaryExit, false) && // Signature validation is skipped in `isValidVoluntaryExit(,,false)` since it was already validated in gossip // However we must make sure that the signature fork is the same, or it will become invalid if included through // a future fork. diff --git a/packages/beacon-node/src/chain/validation/voluntaryExit.ts b/packages/beacon-node/src/chain/validation/voluntaryExit.ts index 79da31084a36..60ee133e4b69 100644 --- a/packages/beacon-node/src/chain/validation/voluntaryExit.ts +++ b/packages/beacon-node/src/chain/validation/voluntaryExit.ts @@ -43,7 +43,7 @@ async function validateVoluntaryExit( // [REJECT] All of the conditions within process_voluntary_exit pass validation. // verifySignature = false, verified in batch below - if (!isValidVoluntaryExit(state, voluntaryExit, false)) { + if (!isValidVoluntaryExit(chain.config.getForkSeq(state.slot), state, voluntaryExit, false)) { throw new VoluntaryExitError(GossipAction.REJECT, { code: VoluntaryExitErrorCode.INVALID, }); diff --git a/packages/state-transition/src/block/processVoluntaryExit.ts b/packages/state-transition/src/block/processVoluntaryExit.ts index a0a0271a0d1e..3cc6afb958f8 100644 --- a/packages/state-transition/src/block/processVoluntaryExit.ts +++ b/packages/state-transition/src/block/processVoluntaryExit.ts @@ -1,5 +1,5 @@ import {FAR_FUTURE_EPOCH, ForkSeq} from "@lodestar/params"; -import {phase0} from "@lodestar/types"; +import {ValidatorIndex, phase0} from "@lodestar/types"; import {verifyVoluntaryExitSignature} from "../signatureSets/index.js"; import {CachedBeaconStateAllForks, CachedBeaconStateElectra} from "../types.js"; import {getPendingBalanceToWithdraw, isActiveValidator} from "../util/index.js"; @@ -16,11 +16,7 @@ export function processVoluntaryExit( signedVoluntaryExit: phase0.SignedVoluntaryExit, verifySignature = true ): void { - const isValidExit = - fork >= ForkSeq.electra - ? isValidVoluntaryExitElectra(state as CachedBeaconStateElectra, signedVoluntaryExit, verifySignature) - : isValidVoluntaryExit(state, signedVoluntaryExit, verifySignature); - if (!isValidExit) { + if (!isValidVoluntaryExit(fork, state, signedVoluntaryExit, verifySignature)) { throw Error(`Invalid voluntary exit at forkSeq=${fork}`); } @@ -29,6 +25,7 @@ export function processVoluntaryExit( } export function isValidVoluntaryExit( + fork: ForkSeq, state: CachedBeaconStateAllForks, signedVoluntaryExit: phase0.SignedVoluntaryExit, verifySignature = true @@ -47,20 +44,12 @@ export function isValidVoluntaryExit( currentEpoch >= voluntaryExit.epoch && // verify the validator had been active long enough currentEpoch >= validator.activationEpoch + config.SHARD_COMMITTEE_PERIOD && + (fork >= ForkSeq.electra + ? // only exit validator if it has no pending withdrawals in the queue + getPendingBalanceToWithdraw(state as CachedBeaconStateElectra, voluntaryExit.validatorIndex) === 0 + : // there are no pending withdrawals in previous forks + true) && // verify signature (!verifySignature || verifyVoluntaryExitSignature(state, signedVoluntaryExit)) ); } - -function isValidVoluntaryExitElectra( - state: CachedBeaconStateElectra, - signedVoluntaryExit: phase0.SignedVoluntaryExit, - verifySignature = true -): boolean { - // only exit validator if it has no pending withdrawals in the queue (post-Electra only) - if (getPendingBalanceToWithdraw(state, signedVoluntaryExit.message.validatorIndex) === 0) { - return isValidVoluntaryExit(state, signedVoluntaryExit, verifySignature); - } - - return false; -} From 82f20371a2c19322a470f1b08c151a949ad45228 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Thu, 23 Jan 2025 10:40:54 +0000 Subject: [PATCH 30/34] fix: do not register validator statuses for epoch -1 (#7392) --- packages/beacon-node/src/metrics/validatorMonitor.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/beacon-node/src/metrics/validatorMonitor.ts b/packages/beacon-node/src/metrics/validatorMonitor.ts index 1c9a093656ce..86ab9edaa54d 100644 --- a/packages/beacon-node/src/metrics/validatorMonitor.ts +++ b/packages/beacon-node/src/metrics/validatorMonitor.ts @@ -306,6 +306,11 @@ export function createValidatorMonitor( lastRegisteredStatusEpoch = currentEpoch; const previousEpoch = currentEpoch - 1; + // There won't be any validator activity in epoch -1 + if (previousEpoch === -1) { + return; + } + for (const [index, monitoredValidator] of validators.entries()) { // We subtract two from the state of the epoch that generated these summaries. // From a0cc24a38f40ba31d996bd6c29c8987fdc6c0eaf Mon Sep 17 00:00:00 2001 From: g11tech Date: Fri, 24 Jan 2025 00:17:06 +0530 Subject: [PATCH 31/34] feat: add dashboard for getblobs metrics (#7390) * feat: add dashboard for getblobs metrics * fix lint --- dashboards/lodestar_block_processor.json | 1005 +++++++++++++++++ .../beacon-node/src/metrics/metrics/beacon.ts | 6 +- .../reqresp/beaconBlocksMaybeBlobsByRoot.ts | 4 +- 3 files changed, 1010 insertions(+), 5 deletions(-) diff --git a/dashboards/lodestar_block_processor.json b/dashboards/lodestar_block_processor.json index a945916308ab..0f2d63a0d1f2 100644 --- a/dashboards/lodestar_block_processor.json +++ b/dashboards/lodestar_block_processor.json @@ -7027,6 +7027,1011 @@ ], "title": "Avg block verified to blob availability delay", "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 311 + }, + "id": 569, + "panels": [], + "title": "GetBlobs Metrics", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "unit": "percentunit", + "unitScale": true + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 312 + }, + "id": 575, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "rate(beacon_blockinputs_already_available_total[$rate_interval])/((rate(beacon_blockinputs_already_available_total[$rate_interval]) +\nrate(beacon_datapromise_blockinputs_available_post_blobs_pull_total[$rate_interval])) or vector(1))", + "format": "time_series", + "instant": false, + "interval": "", + "legendFormat": "Already available blocks when arrived", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "rate(beacon_blockinput_blobs_already_available_total[$rate_interval])/\n((\nrate(beacon_blockinput_blobs_already_available_total[$rate_interval]) + \nrate(beacon_datapromise_blockinput_blobs_already_available_total[$rate_interval]) + \nrate(beacon_datapromise_blockinput_blobs_delayed_gossip_available_total[$rate_interval]) + \nrate(beacon_datapromise_blockinput_getblobs_api_nonnull_responses_used_total[$rate_interval]) + \nrate(beacon_datapromise_blockinput_blobs_finally_resolved_from_network_total[$rate_interval])\n) or vector(1))", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "legendFormat": "Blobs of already available blocks", + "range": true, + "refId": "B" + } + ], + "title": "Availability at block arrival", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "unit": "percentunit", + "unitScale": true + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 312 + }, + "id": 584, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "rate(beacon_datapromise_blockinput_blobs_found_nonnull_in_getblobs_cache_total[$rate_interval])/\n((\nrate(beacon_blockinput_blobs_already_available_total[$rate_interval]) + \nrate(beacon_datapromise_blockinput_blobs_already_available_total[$rate_interval]) + \nrate(beacon_datapromise_blockinput_blobs_delayed_gossip_available_total[$rate_interval]) + \nrate(beacon_datapromise_blockinput_getblobs_api_nonnull_responses_used_total[$rate_interval]) + \nrate(beacon_datapromise_blockinput_blobs_finally_resolved_from_network_total[$rate_interval])\n) or vector(1))", + "format": "time_series", + "instant": false, + "interval": "", + "legendFormat": "old unavailable blobs already present in getblobs cache", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "rate(beacon_datapromise_blockinput_blobs_found_null_in_getblobs_cache_total[$rate_interval])/((rate(beacon_blockinput_blobs_already_available_total[$rate_interval])+\nrate(beacon_datapromise_blockinput_blobs_responded_nonnull_in_getblobs_api_total[$rate_interval])+\nrate(beacon_datapromise_blockinput_blobs_finally_resolved_from_network_total[$rate_interval])\n) or vector(1))", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "legendFormat": "old unavailable blobs but null in getblobs cache", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "rate(beacon_datapromise_blockinput_blobs_notfound_in_getblobs_cache_total[$rate_interval])/((rate(beacon_blockinput_blobs_already_available_total[$rate_interval])+\nrate(beacon_datapromise_blockinput_blobs_responded_nonnull_in_getblobs_api_total[$rate_interval])+\nrate(beacon_datapromise_blockinput_blobs_finally_resolved_from_network_total[$rate_interval])\n) or vector(1))", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "legendFormat": "new unavailable blobs for getblobs", + "range": true, + "refId": "C" + } + ], + "title": "Blobs Availability in GetBlobs Cache", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "unit": "percentunit", + "unitScale": true + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 320 + }, + "id": 577, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "rate(beacon_datapromise_blockinput_blobs_queried_in_getblobs_api_total[$rate_interval])/\n((\nrate(beacon_blockinput_blobs_already_available_total[$rate_interval]) + \nrate(beacon_datapromise_blockinput_blobs_already_available_total[$rate_interval]) + \nrate(beacon_datapromise_blockinput_blobs_delayed_gossip_available_total[$rate_interval]) + \nrate(beacon_datapromise_blockinput_getblobs_api_nonnull_responses_used_total[$rate_interval]) + \nrate(beacon_datapromise_blockinput_blobs_finally_resolved_from_network_total[$rate_interval])\n) or vector(1))", + "format": "time_series", + "instant": false, + "interval": "", + "legendFormat": "total blobs queried in api", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "rate(beacon_datapromise_blockinput_blobs_responded_nonnull_in_getblobs_api_total[$rate_interval])/\n((\nrate(beacon_blockinput_blobs_already_available_total[$rate_interval]) + \nrate(beacon_datapromise_blockinput_blobs_already_available_total[$rate_interval]) + \nrate(beacon_datapromise_blockinput_blobs_delayed_gossip_available_total[$rate_interval]) + \nrate(beacon_datapromise_blockinput_getblobs_api_nonnull_responses_used_total[$rate_interval]) + \nrate(beacon_datapromise_blockinput_blobs_finally_resolved_from_network_total[$rate_interval])\n) or vector(1))", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "legendFormat": "blobs available", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "rate(beacon_datapromise_blockinput_blobs_responded_null_in_getblobs_api_total[$rate_interval])/\n((\nrate(beacon_blockinput_blobs_already_available_total[$rate_interval]) + \nrate(beacon_datapromise_blockinput_blobs_already_available_total[$rate_interval]) + \nrate(beacon_datapromise_blockinput_blobs_delayed_gossip_available_total[$rate_interval]) + \nrate(beacon_datapromise_blockinput_getblobs_api_nonnull_responses_used_total[$rate_interval]) + \nrate(beacon_datapromise_blockinput_blobs_finally_resolved_from_network_total[$rate_interval])\n) or vector(1))", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "legendFormat": "blobs not available", + "range": true, + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "rate(beacon_datapromise_blockinput_blobs_errored_as_null_in_getblobs_api_total[$rate_interval])/\n((\nrate(beacon_blockinput_blobs_already_available_total[$rate_interval]) + \nrate(beacon_datapromise_blockinput_blobs_already_available_total[$rate_interval]) + \nrate(beacon_datapromise_blockinput_blobs_delayed_gossip_available_total[$rate_interval]) + \nrate(beacon_datapromise_blockinput_getblobs_api_nonnull_responses_used_total[$rate_interval]) + \nrate(beacon_datapromise_blockinput_blobs_finally_resolved_from_network_total[$rate_interval])\n) or vector(1))", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "legendFormat": "blobs errored", + "range": true, + "refId": "D" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "rate(beacon_datapromise_blockinput_getblobs_api_nonnull_responses_used_total[$rate_interval])/\n((\nrate(beacon_blockinput_blobs_already_available_total[$rate_interval]) + \nrate(beacon_datapromise_blockinput_blobs_already_available_total[$rate_interval]) + \nrate(beacon_datapromise_blockinput_blobs_delayed_gossip_available_total[$rate_interval]) + \nrate(beacon_datapromise_blockinput_getblobs_api_nonnull_responses_used_total[$rate_interval]) + \nrate(beacon_datapromise_blockinput_blobs_finally_resolved_from_network_total[$rate_interval])\n) or vector(1))", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "legendFormat": "actual useful blobs (not delayed goissip available)", + "range": true, + "refId": "E" + } + ], + "title": "GetBlobs API Stats", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "unit": "percentunit", + "unitScale": true + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 320 + }, + "id": 578, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "rate(beacon_datapromise_blockinput_blobs_finally_queried_from_network_total[$rate_interval])/\n((\nrate(beacon_blockinput_blobs_already_available_total[$rate_interval]) + \nrate(beacon_datapromise_blockinput_blobs_already_available_total[$rate_interval]) + \nrate(beacon_datapromise_blockinput_blobs_delayed_gossip_available_total[$rate_interval]) + \nrate(beacon_datapromise_blockinput_getblobs_api_nonnull_responses_used_total[$rate_interval]) + \nrate(beacon_datapromise_blockinput_blobs_finally_resolved_from_network_total[$rate_interval])\n) or vector(1))", + "format": "time_series", + "instant": false, + "interval": "", + "legendFormat": "blobs finally req/resp queried", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "rate(beacon_datapromise_blockinput_blobs_finally_resolved_from_network_total[$rate_interval])/\n((\nrate(beacon_blockinput_blobs_already_available_total[$rate_interval]) + \nrate(beacon_datapromise_blockinput_blobs_already_available_total[$rate_interval]) + \nrate(beacon_datapromise_blockinput_blobs_delayed_gossip_available_total[$rate_interval]) + \nrate(beacon_datapromise_blockinput_getblobs_api_nonnull_responses_used_total[$rate_interval]) + \nrate(beacon_datapromise_blockinput_blobs_finally_resolved_from_network_total[$rate_interval])\n) or vector(1))", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "legendFormat": "blobs finally req/resp fetched", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "rate(beacon_datapromise_blockinput_blobs_retried_from_network_total[$rate_interval])/\n((\nrate(beacon_blockinput_blobs_already_available_total[$rate_interval]) + \nrate(beacon_datapromise_blockinput_blobs_already_available_total[$rate_interval]) + \nrate(beacon_datapromise_blockinput_blobs_delayed_gossip_available_total[$rate_interval]) + \nrate(beacon_datapromise_blockinput_getblobs_api_nonnull_responses_used_total[$rate_interval]) + \nrate(beacon_datapromise_blockinput_blobs_finally_resolved_from_network_total[$rate_interval])\n) or vector(1))", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "legendFormat": "total blobs retried", + "range": true, + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "rate(beacon_datapromise_blockinput_blobs_retried_and_resolved_from_network_total[$rate_interval])/\n((\nrate(beacon_blockinput_blobs_already_available_total[$rate_interval]) + \nrate(beacon_datapromise_blockinput_blobs_already_available_total[$rate_interval]) + \nrate(beacon_datapromise_blockinput_blobs_delayed_gossip_available_total[$rate_interval]) + \nrate(beacon_datapromise_blockinput_getblobs_api_nonnull_responses_used_total[$rate_interval]) + \nrate(beacon_datapromise_blockinput_blobs_finally_resolved_from_network_total[$rate_interval])\n) or vector(1))", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "legendFormat": "total blobs retried and resolved", + "range": true, + "refId": "D" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "rate(beacon_datapromise_blockinput_blobs_delayed_gossip_available_total[$rate_interval])/\n((\nrate(beacon_blockinput_blobs_already_available_total[$rate_interval]) + \nrate(beacon_datapromise_blockinput_blobs_already_available_total[$rate_interval]) + \nrate(beacon_datapromise_blockinput_blobs_delayed_gossip_available_total[$rate_interval]) + \nrate(beacon_datapromise_blockinput_getblobs_api_nonnull_responses_used_total[$rate_interval]) + \nrate(beacon_datapromise_blockinput_blobs_finally_resolved_from_network_total[$rate_interval])\n) or vector(1))", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "legendFormat": "delayed gossip blobs available", + "range": true, + "refId": "E" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "rate(beacon_datapromise_blockinput_blobs_delayed_gossip_saved_computation_total[$rate_interval])/\n((\nrate(beacon_blockinput_blobs_already_available_total[$rate_interval]) + \nrate(beacon_datapromise_blockinput_blobs_already_available_total[$rate_interval]) + \nrate(beacon_datapromise_blockinput_blobs_delayed_gossip_available_total[$rate_interval]) + \nrate(beacon_datapromise_blockinput_getblobs_api_nonnull_responses_used_total[$rate_interval]) + \nrate(beacon_datapromise_blockinput_blobs_finally_resolved_from_network_total[$rate_interval])\n) or vector(1))", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "legendFormat": "delayed gossip blobs used", + "range": true, + "refId": "F" + } + ], + "title": "Network fetch stats", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "unit": "percentunit", + "unitScale": true + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 328 + }, + "id": 580, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "rate(beacon_datapromise_blockinputs_available_using_getblobs_total[$rate_interval])/((rate(beacon_blockinputs_already_available_total[$rate_interval]) +\nrate(beacon_datapromise_blockinputs_available_post_blobs_pull_total[$rate_interval])) or vector(1))", + "format": "time_series", + "instant": false, + "interval": "", + "legendFormat": "blocks used getBlobs blobs", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "rate(beacon_datapromise_blockinputs_tried_for_blobs_pull_total[$rate_interval])/((rate(beacon_blockinputs_already_available_total[$rate_interval]) +\nrate(beacon_datapromise_blockinputs_available_post_blobs_pull_total[$rate_interval])) or vector(1))", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "legendFormat": "blocks queried getblobs", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "rate(beacon_datapromise_blockinputs_available_post_blobs_pull_total[$rate_interval])/((rate(beacon_blockinputs_already_available_total[$rate_interval]) +\nrate(beacon_datapromise_blockinputs_available_post_blobs_pull_total[$rate_interval])) or vector(1))", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "legendFormat": "blocks available from getblobs", + "range": true, + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "rate(beacon_datapromise_blockinputs_retried_and_resolved_from_network_total[$rate_interval])/((rate(beacon_blockinputs_already_available_total[$rate_interval]) +\nrate(beacon_datapromise_blockinputs_available_post_blobs_pull_total[$rate_interval])) or vector(1))", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "legendFormat": "blocks available after network blobs retries", + "range": true, + "refId": "D" + } + ], + "title": "Block availability resolution stats", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "unit": "percentunit", + "unitScale": true + }, + "overrides": [ + { + "__systemRef": "hideSeriesFrom", + "matcher": { + "id": "byNames", + "options": { + "mode": "exclude", + "names": [ + "beacon_datapromise_blockinputs_retried_for_blobs_pull_total" + ], + "prefix": "All except:", + "readOnly": true + } + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": false, + "tooltip": false, + "viz": true + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 328 + }, + "id": 581, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "rate(beacon_datapromise_blockinputs_retried_for_blobs_pull_total[$rate_interval])/((rate(beacon_blockinputs_already_available_total[$rate_interval]) +\nrate(beacon_datapromise_blockinputs_available_post_blobs_pull_total[$rate_interval])) or vector(1))", + "format": "time_series", + "instant": false, + "interval": "", + "legendFormat": "beacon_datapromise_blockinputs_retried_for_blobs_pull_total", + "range": true, + "refId": "A" + } + ], + "title": "Retry", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "unitScale": true + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 336 + }, + "id": 582, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "getblob_cache_size", + "format": "time_series", + "instant": false, + "interval": "", + "legendFormat": "getblob cache size", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "beacon_datapromise_blockinput_retry_tracker_cache_size", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "legendFormat": "blockinput retry cache size", + "range": true, + "refId": "B" + } + ], + "title": "Cache size", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "unitScale": true + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 336 + }, + "id": 585, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "rate(getblob_cache_pruned_total[$rate_interval])", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "legendFormat": "getblob cache pruned", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "rate(beacon_datapromise_blockinput_retry_tracker_cache_pruned[$rate_interval])", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "legendFormat": "blockinput retry cache pruned", + "range": true, + "refId": "B" + } + ], + "title": "Cache prune rate", + "type": "timeseries" } ], "refresh": "10s", diff --git a/packages/beacon-node/src/metrics/metrics/beacon.ts b/packages/beacon-node/src/metrics/metrics/beacon.ts index 60a49b0b673d..e153294da630 100644 --- a/packages/beacon-node/src/metrics/metrics/beacon.ts +++ b/packages/beacon-node/src/metrics/metrics/beacon.ts @@ -227,13 +227,13 @@ export function createBeaconMetrics(register: RegistryMetricCreator) { // of those which need to be fetched dataPromiseBlobsAlreadyAvailable: register.gauge({ name: "beacon_datapromise_blockinput_blobs_already_available_total", - help: "Count of blocks that were already available in blockinput cache via gossip", + help: "Count of data promise blocks' blobs that were already available in blockinput cache via gossip", }), dataPromiseBlobsDelayedGossipAvailable: register.gauge({ name: "beacon_datapromise_blockinput_blobs_delayed_gossip_available_total", - help: "Count of blobs that became available delayed via gossip post block arrival", + help: "Count of data promise blocks' blobs that became available delayed via gossip post block arrival", }), - dataPromiseBlobsDeplayedGossipAvailableSavedGetBlobsCompute: register.gauge({ + dataPromiseBlobsDelayedGossipAvailableSavedGetBlobsCompute: register.gauge({ name: "beacon_datapromise_blockinput_blobs_delayed_gossip_saved_computation_total", help: "Count of late available blobs that saved blob sidecar computation from getblobs", }), diff --git a/packages/beacon-node/src/network/reqresp/beaconBlocksMaybeBlobsByRoot.ts b/packages/beacon-node/src/network/reqresp/beaconBlocksMaybeBlobsByRoot.ts index 3bbe00bfc56b..7680634246cd 100644 --- a/packages/beacon-node/src/network/reqresp/beaconBlocksMaybeBlobsByRoot.ts +++ b/packages/beacon-node/src/network/reqresp/beaconBlocksMaybeBlobsByRoot.ts @@ -181,7 +181,7 @@ export async function unavailableBeaconBlobsByRoot( blobsCache.set(blobSidecar.index, {blobSidecar, blobBytes: null}); } else { metrics?.blockInputFetchStats.dataPromiseBlobsDelayedGossipAvailable.inc(); - metrics?.blockInputFetchStats.dataPromiseBlobsDeplayedGossipAvailableSavedGetBlobsCompute.inc(); + metrics?.blockInputFetchStats.dataPromiseBlobsDelayedGossipAvailableSavedGetBlobsCompute.inc(); } } // may be blobsidecar arrived in the timespan of making the request @@ -255,7 +255,7 @@ export async function unavailableBeaconBlobsByRoot( resolveAvailability(blockData); metrics?.syncUnknownBlock.resolveAvailabilitySource.inc({source: BlockInputAvailabilitySource.UNKNOWN_SYNC}); - metrics?.blockInputFetchStats.totalDataAvailableBlockInputs.inc(); + metrics?.blockInputFetchStats.totalDataPromiseBlockInputsResolvedAvailable.inc(); if (getBlobsUseful) { metrics?.blockInputFetchStats.totalDataPromiseBlockInputsAvailableUsingGetBlobs.inc(); } From c34e1290ff69af10a1c4cdcff46f805447a3e764 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Thu, 23 Jan 2025 19:24:42 +0000 Subject: [PATCH 32/34] refactor: update partially withdrawn balance calculation to only run post-electra (#7389) **Motivation** There is no need to calculate partially withdrawn balance before electra as the value is unused **Description** - only run calculation post-electra by inlining function call - more efficient implementation, filter into reduce iterates over all withdrawals twice in the worst case --- .../src/block/processWithdrawals.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/packages/state-transition/src/block/processWithdrawals.ts b/packages/state-transition/src/block/processWithdrawals.ts index d3d3a5d58d9e..7149bc3c2697 100644 --- a/packages/state-transition/src/block/processWithdrawals.ts +++ b/packages/state-transition/src/block/processWithdrawals.ts @@ -8,7 +8,7 @@ import { MAX_WITHDRAWALS_PER_PAYLOAD, MIN_ACTIVATION_BALANCE, } from "@lodestar/params"; -import {capella, ssz} from "@lodestar/types"; +import {ValidatorIndex, capella, ssz} from "@lodestar/types"; import {toRootHex} from "@lodestar/utils"; import {CachedBeaconStateCapella, CachedBeaconStateElectra} from "../types.js"; @@ -154,13 +154,10 @@ export function getExpectedWithdrawals( for (n = 0; n < bound; n++) { // Get next validator in turn const validatorIndex = (nextWithdrawalValidatorIndex + n) % validators.length; - const partiallyWithdrawnBalance = withdrawals - .filter((withdrawal) => withdrawal.validatorIndex === validatorIndex) - .reduce((acc, withdrawal) => acc + Number(withdrawal.amount), 0); const validator = validators.getReadonly(validatorIndex); const balance = isPostElectra - ? balances.get(validatorIndex) - partiallyWithdrawnBalance + ? balances.get(validatorIndex) - getPartiallyWithdrawnBalance(withdrawals, validatorIndex) : balances.get(validatorIndex); const {withdrawableEpoch, withdrawalCredentials, effectiveBalance} = validator; const hasWithdrawableCredentials = isPostElectra @@ -203,3 +200,13 @@ export function getExpectedWithdrawals( return {withdrawals, sampledValidators: n, processedPartialWithdrawalsCount}; } + +function getPartiallyWithdrawnBalance(withdrawals: capella.Withdrawal[], validatorIndex: ValidatorIndex): number { + let total = BigInt(0); + for (const withdrawal of withdrawals) { + if (withdrawal.validatorIndex === validatorIndex) { + total += withdrawal.amount; + } + } + return Number(total); +} From 6781de96a4910fd152dfb2eb55561ff60838002e Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Thu, 23 Jan 2025 21:01:05 +0000 Subject: [PATCH 33/34] chore: update lodestar script to properly forward exit signals (#7383) **Motivation** Users might use the lodestar script inside docker or with a process manager like systemd (https://github.com/ChainSafe/lodestar/issues/7378) and in those cases we need to make sure Lodestar runs as primary process and not the shell script as otherwise exit signals are not properly forwarded or in case of systemd it does not wait for Lodestar to shut down. **Description** Update lodestar script to properly forward exit signals by execution the main node process via `exec`. This shouldn't change anything in regard to how we use the script during development. --- lodestar | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lodestar b/lodestar index 7cf5301e4de4..dc512c65b35b 100755 --- a/lodestar +++ b/lodestar @@ -4,4 +4,4 @@ # # ./lodestar.sh beacon --network mainnet -node --trace-deprecation --max-old-space-size=8192 ./packages/cli/bin/lodestar.js "$@" \ No newline at end of file +exec node --trace-deprecation --max-old-space-size=8192 ./packages/cli/bin/lodestar.js "$@" From 42631de1faf45c200f3b46b810de721cadc65443 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Thu, 23 Jan 2025 21:44:22 +0000 Subject: [PATCH 34/34] chore: update ssz and accompanying packages to v1.0.0 (#7393) **Motivation** We dropped CJS support :tada: fixed some file resolution issues causing wrong hasher to be loaded and got some clean named imports for hasher instead of having to dig it out of the libs folder. **Description** Update ssz and accompanying packages to v1.0.0 --- packages/api/package.json | 4 +-- packages/beacon-node/package.json | 6 ++-- packages/cli/package.json | 4 +-- packages/cli/src/applyPreset.ts | 4 +-- packages/config/package.json | 2 +- packages/db/package.json | 2 +- packages/fork-choice/package.json | 2 +- packages/light-client/package.json | 6 ++-- packages/state-transition/package.json | 8 +++--- packages/types/package.json | 2 +- packages/utils/package.json | 2 +- packages/validator/package.json | 2 +- yarn.lock | 38 +++++++++++++------------- 13 files changed, 41 insertions(+), 41 deletions(-) diff --git a/packages/api/package.json b/packages/api/package.json index a1ecab9cdef9..061828a0e470 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -70,8 +70,8 @@ "check-readme": "typescript-docs-verifier" }, "dependencies": { - "@chainsafe/persistent-merkle-tree": "^0.9.1", - "@chainsafe/ssz": "^0.19.1", + "@chainsafe/persistent-merkle-tree": "^1.0.1", + "@chainsafe/ssz": "^1.0.1", "@lodestar/config": "^1.25.0", "@lodestar/params": "^1.25.0", "@lodestar/types": "^1.25.0", diff --git a/packages/beacon-node/package.json b/packages/beacon-node/package.json index 960bf1ee11da..d6fbd83b160b 100644 --- a/packages/beacon-node/package.json +++ b/packages/beacon-node/package.json @@ -94,17 +94,17 @@ "check-readme": "typescript-docs-verifier" }, "dependencies": { - "@chainsafe/as-sha256": "^0.6.1", + "@chainsafe/as-sha256": "^1.0.0", "@chainsafe/blst": "^2.1.0", "@chainsafe/discv5": "^9.0.0", "@chainsafe/enr": "^3.0.0", "@chainsafe/libp2p-gossipsub": "^13.0.0", "@chainsafe/libp2p-identify": "^1.0.0", "@chainsafe/libp2p-noise": "^15.0.0", - "@chainsafe/persistent-merkle-tree": "^0.9.1", + "@chainsafe/persistent-merkle-tree": "^1.0.1", "@chainsafe/prometheus-gc-stats": "^1.0.0", "@chainsafe/pubkey-index-map": "2.0.0", - "@chainsafe/ssz": "^0.19.1", + "@chainsafe/ssz": "^1.0.1", "@chainsafe/threads": "^1.11.1", "@ethersproject/abi": "^5.7.0", "@fastify/bearer-auth": "^10.0.1", diff --git a/packages/cli/package.json b/packages/cli/package.json index 6481cc930c53..e65beb7317f6 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -56,8 +56,8 @@ "@chainsafe/blst": "^2.1.0", "@chainsafe/discv5": "^9.0.0", "@chainsafe/enr": "^3.0.0", - "@chainsafe/persistent-merkle-tree": "^0.9.1", - "@chainsafe/ssz": "^0.19.1", + "@chainsafe/persistent-merkle-tree": "^1.0.1", + "@chainsafe/ssz": "^1.0.1", "@chainsafe/threads": "^1.11.1", "@libp2p/crypto": "^4.1.0", "@libp2p/peer-id": "^4.1.0", diff --git a/packages/cli/src/applyPreset.ts b/packages/cli/src/applyPreset.ts index 06d29d353af8..4b666ecfcfc8 100644 --- a/packages/cli/src/applyPreset.ts +++ b/packages/cli/src/applyPreset.ts @@ -1,7 +1,7 @@ // MUST import this file first before anything and not import any Lodestar code. -import {hasher} from "@chainsafe/persistent-merkle-tree/lib/hasher/as-sha256.js"; -import {setHasher} from "@chainsafe/persistent-merkle-tree/lib/hasher/index.js"; +import {setHasher} from "@chainsafe/persistent-merkle-tree"; +import {hasher} from "@chainsafe/persistent-merkle-tree/hasher/as-sha256"; // without setting this first, persistent-merkle-tree will use noble instead setHasher(hasher); diff --git a/packages/config/package.json b/packages/config/package.json index c35aeffb81db..14d65257358c 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -64,7 +64,7 @@ "blockchain" ], "dependencies": { - "@chainsafe/ssz": "^0.19.1", + "@chainsafe/ssz": "^1.0.1", "@lodestar/params": "^1.25.0", "@lodestar/types": "^1.25.0", "@lodestar/utils": "^1.25.0" diff --git a/packages/db/package.json b/packages/db/package.json index e302520dab22..9f91f7bb936b 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -35,7 +35,7 @@ "check-readme": "typescript-docs-verifier" }, "dependencies": { - "@chainsafe/ssz": "^0.19.1", + "@chainsafe/ssz": "^1.0.1", "@lodestar/config": "^1.25.0", "@lodestar/utils": "^1.25.0", "classic-level": "^1.4.1", diff --git a/packages/fork-choice/package.json b/packages/fork-choice/package.json index f62e1f2a877c..8264926c6fc8 100644 --- a/packages/fork-choice/package.json +++ b/packages/fork-choice/package.json @@ -36,7 +36,7 @@ "check-readme": "typescript-docs-verifier" }, "dependencies": { - "@chainsafe/ssz": "^0.19.1", + "@chainsafe/ssz": "^1.0.1", "@lodestar/config": "^1.25.0", "@lodestar/params": "^1.25.0", "@lodestar/state-transition": "^1.25.0", diff --git a/packages/light-client/package.json b/packages/light-client/package.json index 95dadc8110c6..410a5855360f 100644 --- a/packages/light-client/package.json +++ b/packages/light-client/package.json @@ -75,8 +75,8 @@ "dependencies": { "@chainsafe/bls": "7.1.3", "@chainsafe/blst": "^0.2.0", - "@chainsafe/persistent-merkle-tree": "^0.9.1", - "@chainsafe/ssz": "^0.19.1", + "@chainsafe/persistent-merkle-tree": "^1.0.1", + "@chainsafe/ssz": "^1.0.1", "@lodestar/api": "^1.25.0", "@lodestar/config": "^1.25.0", "@lodestar/params": "^1.25.0", @@ -85,7 +85,7 @@ "mitt": "^3.0.0" }, "devDependencies": { - "@chainsafe/as-sha256": "^0.6.1", + "@chainsafe/as-sha256": "^1.0.0", "@types/qs": "^6.9.7", "fastify": "^5.0.0", "qs": "^6.11.1", diff --git a/packages/state-transition/package.json b/packages/state-transition/package.json index db142ffd24f9..ab5782d29217 100644 --- a/packages/state-transition/package.json +++ b/packages/state-transition/package.json @@ -58,12 +58,12 @@ }, "types": "lib/index.d.ts", "dependencies": { - "@chainsafe/as-sha256": "^0.6.1", + "@chainsafe/as-sha256": "^1.0.0", "@chainsafe/blst": "^2.1.0", - "@chainsafe/persistent-merkle-tree": "^0.9.1", - "@chainsafe/persistent-ts": "^0.19.2", + "@chainsafe/persistent-merkle-tree": "^1.0.1", + "@chainsafe/persistent-ts": "^1.0.0", "@chainsafe/pubkey-index-map": "2.0.0", - "@chainsafe/ssz": "^0.19.1", + "@chainsafe/ssz": "^1.0.1", "@chainsafe/swap-or-not-shuffle": "^0.0.2", "@lodestar/config": "^1.25.0", "@lodestar/params": "^1.25.0", diff --git a/packages/types/package.json b/packages/types/package.json index 77f0f2e6e38b..b97b7d87a61b 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -73,7 +73,7 @@ }, "types": "lib/index.d.ts", "dependencies": { - "@chainsafe/ssz": "^0.19.1", + "@chainsafe/ssz": "^1.0.1", "@lodestar/params": "^1.25.0", "ethereum-cryptography": "^2.0.0" }, diff --git a/packages/utils/package.json b/packages/utils/package.json index c8cf3a502194..ae7216b822de 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -39,7 +39,7 @@ }, "types": "lib/index.d.ts", "dependencies": { - "@chainsafe/as-sha256": "^0.6.1", + "@chainsafe/as-sha256": "^1.0.0", "any-signal": "3.0.1", "bigint-buffer": "^1.1.5", "case": "^1.6.3", diff --git a/packages/validator/package.json b/packages/validator/package.json index 72940759ecbd..e92455d81fd1 100644 --- a/packages/validator/package.json +++ b/packages/validator/package.json @@ -46,7 +46,7 @@ ], "dependencies": { "@chainsafe/blst": "^2.1.0", - "@chainsafe/ssz": "^0.19.1", + "@chainsafe/ssz": "^1.0.1", "@lodestar/api": "^1.25.0", "@lodestar/config": "^1.25.0", "@lodestar/db": "^1.25.0", diff --git a/yarn.lock b/yarn.lock index ab6fa59fbdfc..b0297a1be982 100644 --- a/yarn.lock +++ b/yarn.lock @@ -357,10 +357,10 @@ resolved "https://registry.yarnpkg.com/@chainsafe/as-chacha20poly1305/-/as-chacha20poly1305-0.1.0.tgz#7da6f8796f9b42dac6e830a086d964f1f9189e09" integrity sha512-BpNcL8/lji/GM3+vZ/bgRWqJ1q5kwvTFmGPk7pxm/QQZDbaMI98waOHjEymTjq2JmdD/INdNBFOVSyJofXg7ew== -"@chainsafe/as-sha256@0.6.1", "@chainsafe/as-sha256@^0.6.1": - version "0.6.1" - resolved "https://registry.yarnpkg.com/@chainsafe/as-sha256/-/as-sha256-0.6.1.tgz#0643cf699118f2a0db6d8ce6e8d68fe7c5084158" - integrity sha512-bYDOK5aK7NYE/ZZ/A3C+Q8ZrEWdzpH80fcEotjgX3pmntv3SJfOTTYE53mjnPSEZFlv/rST0H/ZETsz8Wab9iw== +"@chainsafe/as-sha256@1.0.0", "@chainsafe/as-sha256@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@chainsafe/as-sha256/-/as-sha256-1.0.0.tgz#9095ad42dce13887b5877fce70592e573940ecd7" + integrity sha512-EYw5IZ99Mhn7K8d1eDDH66AFhPy9GcD7bfiqm9mwFjsg8MViEEicGl62b5YPzufBTFh7X7qWAe6yWpr/gbaVEw== "@chainsafe/as-sha256@^0.4.1": version "0.4.1" @@ -595,12 +595,12 @@ dependencies: "@chainsafe/is-ip" "^2.0.1" -"@chainsafe/persistent-merkle-tree@0.9.1", "@chainsafe/persistent-merkle-tree@^0.9.1": - version "0.9.1" - resolved "https://registry.yarnpkg.com/@chainsafe/persistent-merkle-tree/-/persistent-merkle-tree-0.9.1.tgz#fd29d36381f53e1d04c3ffbbb92eb91a9f460f96" - integrity sha512-UVMKbWcKr1Y56qgy6m1W4peUaCwwsn/W2Vd5Ffu7Tb9lMW80q0IaW7e97jvyWd2SDfqVQ1p2XP4bBj6WAcfZvg== +"@chainsafe/persistent-merkle-tree@1.0.1", "@chainsafe/persistent-merkle-tree@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@chainsafe/persistent-merkle-tree/-/persistent-merkle-tree-1.0.1.tgz#4eb5a8e3367bc3957c88c8b9bad9610209e00fed" + integrity sha512-aQtYdXHmWRowcQK0h91HfHMO3bezQLk9wjQXv2CCcTbTim31BnCbPVpNbvAUWvEbifLQYvM18moygvEtdUNhXg== dependencies: - "@chainsafe/as-sha256" "0.6.1" + "@chainsafe/as-sha256" "1.0.0" "@chainsafe/hashtree" "1.0.1" "@noble/hashes" "^1.3.0" @@ -612,10 +612,10 @@ "@chainsafe/as-sha256" "^0.4.1" "@noble/hashes" "^1.3.0" -"@chainsafe/persistent-ts@^0.19.2": - version "0.19.2" - resolved "https://registry.yarnpkg.com/@chainsafe/persistent-ts/-/persistent-ts-0.19.2.tgz#f7da892b76f5a159d568bacd96563c2c56c3e99c" - integrity sha512-CFz1jniPezZJmrPdYkm1wbCj7+H0yWfrAs4qE4HJ3ZWWqcTu3KQWgqUVZ7J4WGxuYU9HAXJSYsO3xtAqHWm8YQ== +"@chainsafe/persistent-ts@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@chainsafe/persistent-ts/-/persistent-ts-1.0.0.tgz#09ed7ab163a72d8ee9a154be589901bbc570a359" + integrity sha512-Xwu59vDQwJWcF4QbIdi9gvRVnkLBOc7Y5JUpINS4TVRtp4omhjEsqO4rFSCUhC8opyg1HcNSQEjL4IgYLGouuw== "@chainsafe/prometheus-gc-stats@^1.0.0": version "1.0.2" @@ -666,13 +666,13 @@ "@chainsafe/as-sha256" "^0.4.1" "@chainsafe/persistent-merkle-tree" "^0.6.1" -"@chainsafe/ssz@^0.19.1": - version "0.19.1" - resolved "https://registry.yarnpkg.com/@chainsafe/ssz/-/ssz-0.19.1.tgz#e6f88561cf83204d23a401cb8f4344eedcbcbae4" - integrity sha512-LsEx6vbQPxNG3ydjabjIHcvhbW6GvugTw2tAIB39mmLNIsbYk1YUdskHHsM29+Oe3937ekdk2NZpo3QHye5zJA== +"@chainsafe/ssz@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@chainsafe/ssz/-/ssz-1.0.1.tgz#dd1373cb4387fdd869d377f0fc5460edf422bd78" + integrity sha512-+QugG2Wbw3zWmCSIYsjAGoJXmT899ecdfI9OJVG6e3A6pPMJHH4EgENzXYy02ZUDhHXNhJ5c9pA4dElGfT7b4Q== dependencies: - "@chainsafe/as-sha256" "0.6.1" - "@chainsafe/persistent-merkle-tree" "0.9.1" + "@chainsafe/as-sha256" "1.0.0" + "@chainsafe/persistent-merkle-tree" "1.0.1" "@chainsafe/swap-or-not-shuffle-darwin-arm64@0.0.2": version "0.0.2"