From 0096c9abc10f72367f198ae26710235de622955c Mon Sep 17 00:00:00 2001 From: Navie Chan Date: Mon, 29 Apr 2024 15:59:40 +0800 Subject: [PATCH 01/11] Add types --- packages/beacon-node/src/util/sszBytes.ts | 49 +++++++---- packages/params/src/index.ts | 2 + packages/params/src/presets/mainnet.ts | 2 + packages/params/src/presets/minimal.ts | 2 + packages/params/src/types.ts | 4 + packages/types/src/allForks/sszTypes.ts | 18 +++++ packages/types/src/allForks/types.ts | 6 ++ packages/types/src/electra/sszTypes.ts | 99 +++++++++++++++++++++-- packages/types/src/electra/types.ts | 7 ++ 9 files changed, 170 insertions(+), 19 deletions(-) diff --git a/packages/beacon-node/src/util/sszBytes.ts b/packages/beacon-node/src/util/sszBytes.ts index cd12c4bd9c18..c14075aad14d 100644 --- a/packages/beacon-node/src/util/sszBytes.ts +++ b/packages/beacon-node/src/util/sszBytes.ts @@ -1,7 +1,7 @@ import {BitArray, deserializeUint8ArrayBitListFromBytes} from "@chainsafe/ssz"; import {BLSSignature, RootHex, Slot} from "@lodestar/types"; import {toHex} from "@lodestar/utils"; -import {BYTES_PER_FIELD_ELEMENT, FIELD_ELEMENTS_PER_BLOB} from "@lodestar/params"; +import {BYTES_PER_FIELD_ELEMENT, FIELD_ELEMENTS_PER_BLOB, ForkName, ForkSeq} from "@lodestar/params"; export type BlockRootHex = RootHex; export type AttDataBase64 = string; @@ -9,6 +9,7 @@ export type AttDataBase64 = string; // class Attestation(Container): // aggregation_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE] - offset 4 // data: AttestationData - target data - 128 +// committee_bits: BitVector[MAX_COMMITTEES_PER_SLOT] - Electra only: 8 // signature: BLSSignature - 96 // // class AttestationData(Container): 128 bytes fixed size @@ -23,6 +24,7 @@ const ATTESTATION_BEACON_BLOCK_ROOT_OFFSET = VARIABLE_FIELD_OFFSET + 8 + 8; const ROOT_SIZE = 32; const SLOT_SIZE = 8; const ATTESTATION_DATA_SIZE = 128; +const COMMITTEE_BITS_SIZE = 8; const SIGNATURE_SIZE = 96; /** @@ -68,16 +70,17 @@ export function getAttDataBase64FromAttestationSerialized(data: Uint8Array): Att * Extract aggregation bits from attestation serialized bytes. * Return null if data is not long enough to extract aggregation bits. */ -export function getAggregationBitsFromAttestationSerialized(data: Uint8Array): BitArray | null { - if (data.length < VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE + SIGNATURE_SIZE) { +export function getAggregationBitsFromAttestationSerialized(fork: ForkName, data: Uint8Array): BitArray | null { + const aggregationBitsStartIndex = + ForkSeq[fork] >= ForkSeq.electra + ? VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE + COMMITTEE_BITS_SIZE + SIGNATURE_SIZE + : VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE + SIGNATURE_SIZE; + + if (data.length < aggregationBitsStartIndex) { return null; } - const {uint8Array, bitLen} = deserializeUint8ArrayBitListFromBytes( - data, - VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE + SIGNATURE_SIZE, - data.length - ); + const {uint8Array, bitLen} = deserializeUint8ArrayBitListFromBytes(data, aggregationBitsStartIndex, data.length); return new BitArray(uint8Array, bitLen); } @@ -85,15 +88,33 @@ export function getAggregationBitsFromAttestationSerialized(data: Uint8Array): B * Extract signature from attestation serialized bytes. * Return null if data is not long enough to extract signature. */ -export function getSignatureFromAttestationSerialized(data: Uint8Array): BLSSignature | null { - if (data.length < VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE + SIGNATURE_SIZE) { +export function getSignatureFromAttestationSerialized(fork: ForkName, data: Uint8Array): BLSSignature | null { + const signatureStartIndex = + ForkSeq[fork] >= ForkSeq.electra + ? VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE + COMMITTEE_BITS_SIZE + : VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE; + + if (data.length < signatureStartIndex + SIGNATURE_SIZE) { return null; } - return data.subarray( - VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE, - VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE + SIGNATURE_SIZE - ); + return data.subarray(signatureStartIndex, signatureStartIndex + SIGNATURE_SIZE); +} + +/** + * Extract committee bits from Electra attestation serialized bytes. + * Return null if data is not long enough to extract committee bits. + */ +export function getCommitteeBitsFromAttestationSerialized(data: Uint8Array): BitArray | null { + const committeeBitsStartIndex = VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE; + + if (data.length < committeeBitsStartIndex + COMMITTEE_BITS_SIZE) { + return null; + } + + const uint8Array = data.subarray(committeeBitsStartIndex, committeeBitsStartIndex + COMMITTEE_BITS_SIZE); + + return new BitArray(uint8Array, uint8Array.byteLength * 8); } // diff --git a/packages/params/src/index.ts b/packages/params/src/index.ts index d903a404dfb5..3e56effc4138 100644 --- a/packages/params/src/index.ts +++ b/packages/params/src/index.ts @@ -96,6 +96,8 @@ export const { MAX_DEPOSIT_RECEIPTS_PER_PAYLOAD, MAX_EXECUTION_LAYER_EXITS, + MAX_ATTESTER_SLASHINGS_ELECTRA, + MAX_ATTESTATIONS_ELECTRA, } = activePreset; //////////// diff --git a/packages/params/src/presets/mainnet.ts b/packages/params/src/presets/mainnet.ts index 802a6691c311..27cb7640b2dd 100644 --- a/packages/params/src/presets/mainnet.ts +++ b/packages/params/src/presets/mainnet.ts @@ -122,4 +122,6 @@ export const mainnetPreset: BeaconPreset = { // ELECTRA MAX_DEPOSIT_RECEIPTS_PER_PAYLOAD: 8192, MAX_EXECUTION_LAYER_EXITS: 16, + MAX_ATTESTER_SLASHINGS_ELECTRA: 1, + MAX_ATTESTATIONS_ELECTRA: 8, }; diff --git a/packages/params/src/presets/minimal.ts b/packages/params/src/presets/minimal.ts index d10b420ed97c..022532a49e6f 100644 --- a/packages/params/src/presets/minimal.ts +++ b/packages/params/src/presets/minimal.ts @@ -123,4 +123,6 @@ export const minimalPreset: BeaconPreset = { // ELECTRA MAX_DEPOSIT_RECEIPTS_PER_PAYLOAD: 4, MAX_EXECUTION_LAYER_EXITS: 16, + MAX_ATTESTER_SLASHINGS_ELECTRA: 1, + MAX_ATTESTATIONS_ELECTRA: 8, }; diff --git a/packages/params/src/types.ts b/packages/params/src/types.ts index 7856f1be72ba..34f40a66707e 100644 --- a/packages/params/src/types.ts +++ b/packages/params/src/types.ts @@ -86,6 +86,8 @@ export type BeaconPreset = { // ELECTRA MAX_DEPOSIT_RECEIPTS_PER_PAYLOAD: number; MAX_EXECUTION_LAYER_EXITS: number; + MAX_ATTESTER_SLASHINGS_ELECTRA: number; + MAX_ATTESTATIONS_ELECTRA: number; }; /** @@ -175,6 +177,8 @@ export const beaconPresetTypes: BeaconPresetTypes = { // ELECTRA MAX_DEPOSIT_RECEIPTS_PER_PAYLOAD: "number", MAX_EXECUTION_LAYER_EXITS: "number", + MAX_ATTESTER_SLASHINGS_ELECTRA: "number", + MAX_ATTESTATIONS_ELECTRA: "number", }; type BeaconPresetTypes = { diff --git a/packages/types/src/allForks/sszTypes.ts b/packages/types/src/allForks/sszTypes.ts index 84c6bb86ce5f..0ad3b18b2f8e 100644 --- a/packages/types/src/allForks/sszTypes.ts +++ b/packages/types/src/allForks/sszTypes.ts @@ -16,6 +16,9 @@ export const allForks = { SignedBeaconBlock: phase0.SignedBeaconBlock, BeaconState: phase0.BeaconState, Metadata: phase0.Metadata, + SignedAggregateAndProof: phase0.SignedAggregateAndProof, + Attestation: phase0.Attestation, + AttesterSlashing: phase0.AttesterSlashing, }, altair: { BeaconBlockBody: altair.BeaconBlockBody, @@ -23,6 +26,9 @@ export const allForks = { SignedBeaconBlock: altair.SignedBeaconBlock, BeaconState: altair.BeaconState, Metadata: altair.Metadata, + SignedAggregateAndProof: phase0.SignedAggregateAndProof, + Attestation: phase0.Attestation, + AttesterSlashing: phase0.AttesterSlashing, }, bellatrix: { BeaconBlockBody: bellatrix.BeaconBlockBody, @@ -30,6 +36,9 @@ export const allForks = { SignedBeaconBlock: bellatrix.SignedBeaconBlock, BeaconState: bellatrix.BeaconState, Metadata: altair.Metadata, + SignedAggregateAndProof: phase0.SignedAggregateAndProof, + Attestation: phase0.Attestation, + AttesterSlashing: phase0.AttesterSlashing, }, capella: { BeaconBlockBody: capella.BeaconBlockBody, @@ -37,6 +46,9 @@ export const allForks = { SignedBeaconBlock: capella.SignedBeaconBlock, BeaconState: capella.BeaconState, Metadata: altair.Metadata, + SignedAggregateAndProof: phase0.SignedAggregateAndProof, + Attestation: phase0.Attestation, + AttesterSlashing: phase0.AttesterSlashing, }, deneb: { BeaconBlockBody: deneb.BeaconBlockBody, @@ -44,6 +56,9 @@ export const allForks = { SignedBeaconBlock: deneb.SignedBeaconBlock, BeaconState: deneb.BeaconState, Metadata: altair.Metadata, + SignedAggregateAndProof: phase0.SignedAggregateAndProof, + Attestation: phase0.Attestation, + AttesterSlashing: phase0.AttesterSlashing, }, electra: { BeaconBlockBody: electra.BeaconBlockBody, @@ -51,6 +66,9 @@ export const allForks = { SignedBeaconBlock: electra.SignedBeaconBlock, BeaconState: electra.BeaconState, Metadata: altair.Metadata, + SignedAggregateAndProof: electra.SignedAggregateAndProof, + Attestation: electra.Attestation, + AttesterSlashing: electra.AttesterSlashing, }, }; diff --git a/packages/types/src/allForks/types.ts b/packages/types/src/allForks/types.ts index 416bb051065a..f116bde20b70 100644 --- a/packages/types/src/allForks/types.ts +++ b/packages/types/src/allForks/types.ts @@ -43,6 +43,12 @@ export type BeaconState = | capella.BeaconState | deneb.BeaconState | electra.BeaconState; +export type Attestation = phase0.Attestation | electra.Attestation; +export type AggregateAndProof = phase0.AggregateAndProof | electra.AggregateAndProof; +export type SignedAggregateAndProof = phase0.SignedAggregateAndProof | electra.SignedAggregateAndProof; +export type IndexedAttestation = phase0.IndexedAttestation | electra.IndexedAttestation; +export type IndexedAttestationBigint = phase0.IndexedAttestationBigint | electra.IndexedAttestationBigint; +export type AttesterSlashing = phase0.AttesterSlashing | electra.AttesterSlashing; export type Metadata = phase0.Metadata | altair.Metadata; // For easy reference in the assemble block for building payloads diff --git a/packages/types/src/electra/sszTypes.ts b/packages/types/src/electra/sszTypes.ts index 7b5ed51fb786..09956fac3117 100644 --- a/packages/types/src/electra/sszTypes.ts +++ b/packages/types/src/electra/sszTypes.ts @@ -1,11 +1,21 @@ -import {ContainerType, ListCompositeType, VectorCompositeType} from "@chainsafe/ssz"; +import { + BitListType, + BitVectorType, + ContainerType, + ListBasicType, + ListCompositeType, + VectorCompositeType, +} from "@chainsafe/ssz"; import { HISTORICAL_ROOTS_LIMIT, BLOCK_BODY_EXECUTION_PAYLOAD_DEPTH as EXECUTION_PAYLOAD_DEPTH, EPOCHS_PER_SYNC_COMMITTEE_PERIOD, SLOTS_PER_EPOCH, MAX_DEPOSIT_RECEIPTS_PER_PAYLOAD, - MAX_EXECUTION_LAYER_EXITS, + MAX_VALIDATORS_PER_COMMITTEE, + MAX_COMMITTEES_PER_SLOT, + MAX_ATTESTATIONS_ELECTRA, + MAX_ATTESTER_SLASHINGS_ELECTRA, } from "@lodestar/params"; import {ssz as primitiveSsz} from "../primitive/index.js"; import {ssz as phase0Ssz} from "../phase0/index.js"; @@ -14,9 +24,55 @@ import {ssz as bellatrixSsz} from "../bellatrix/index.js"; import {ssz as capellaSsz} from "../capella/index.js"; import {ssz as denebSsz} from "../deneb/index.js"; -const {UintNum64, Slot, Root, BLSSignature, UintBn256, Bytes32, BLSPubkey, DepositIndex, UintBn64, ExecutionAddress} = +const {UintNum64, Slot, Root, BLSSignature, UintBn256, Bytes32, BLSPubkey, DepositIndex, UintBn64, ExecutionAddress, ValidatorIndex} = primitiveSsz; +export const AggregationBits = new BitListType(MAX_VALIDATORS_PER_COMMITTEE * MAX_COMMITTEES_PER_SLOT); + +export const CommitteeBits = new BitVectorType(MAX_COMMITTEES_PER_SLOT); + +export const AttestingIndices = new ListBasicType( + ValidatorIndex, + MAX_VALIDATORS_PER_COMMITTEE * MAX_COMMITTEES_PER_SLOT +); + +export const Attestation = new ContainerType( + { + aggregationBits: AggregationBits, + data: phase0Ssz.AttestationData, + committeeBits: CommitteeBits, + signature: BLSSignature, + }, + {typeName: "Attestation", jsonCase: "eth2"} +); + +export const IndexedAttestation = new ContainerType( + { + attestingIndices: AttestingIndices, + data: phase0Ssz.AttestationData, + signature: BLSSignature, + }, + {typeName: "IndexedAttestation", jsonCase: "eth2"} +); + +/** Same as `IndexedAttestation` but epoch, slot and index are not bounded and must be a bigint */ +export const IndexedAttestationBigint = new ContainerType( + { + attestingIndices: AttestingIndices, + data: phase0Ssz.AttestationDataBigint, + signature: BLSSignature, + }, + {typeName: "IndexedAttestation", jsonCase: "eth2"} +); + +export const AttesterSlashing = new ContainerType( + { + attestation1: IndexedAttestationBigint, + attestation2: IndexedAttestationBigint, + }, + {typeName: "AttesterSlashing", jsonCase: "eth2"} +); + export const DepositReceipt = new ContainerType( { pubkey: BLSPubkey, @@ -60,7 +116,15 @@ export const ExecutionPayloadHeader = new ContainerType( // We have to preserve Fields ordering while changing the type of ExecutionPayload export const BeaconBlockBody = new ContainerType( { - ...altairSsz.BeaconBlockBody.fields, + randaoReveal: phase0Ssz.BeaconBlockBody.fields.randaoReveal, + eth1Data: phase0Ssz.BeaconBlockBody.fields.eth1Data, + graffiti: phase0Ssz.BeaconBlockBody.fields.graffiti, + proposerSlashings: phase0Ssz.BeaconBlockBody.fields.proposerSlashings, + attesterSlashings: new ListCompositeType(AttesterSlashing, MAX_ATTESTER_SLASHINGS_ELECTRA), // Modified in ELECTRA + attestations: new ListCompositeType(Attestation, MAX_ATTESTATIONS_ELECTRA), // Modified in ELECTRA + deposits: phase0Ssz.BeaconBlockBody.fields.deposits, + voluntaryExits: phase0Ssz.BeaconBlockBody.fields.voluntaryExits, + syncAggregate: altairSsz.BeaconBlockBody.fields.syncAggregate, executionPayload: ExecutionPayload, // Modified in ELECTRA blsToExecutionChanges: capellaSsz.BeaconBlockBody.fields.blsToExecutionChanges, blobKzgCommitments: denebSsz.BeaconBlockBody.fields.blobKzgCommitments, @@ -86,7 +150,15 @@ export const SignedBeaconBlock = new ContainerType( export const BlindedBeaconBlockBody = new ContainerType( { - ...altairSsz.BeaconBlockBody.fields, + randaoReveal: phase0Ssz.BeaconBlockBody.fields.randaoReveal, + eth1Data: phase0Ssz.BeaconBlockBody.fields.eth1Data, + graffiti: phase0Ssz.BeaconBlockBody.fields.graffiti, + proposerSlashings: phase0Ssz.BeaconBlockBody.fields.proposerSlashings, + attesterSlashings: new ListCompositeType(AttesterSlashing, MAX_ATTESTER_SLASHINGS_ELECTRA), // Modified in ELECTRA + attestations: new ListCompositeType(Attestation, MAX_ATTESTATIONS_ELECTRA), // Modified in ELECTRA + deposits: phase0Ssz.BeaconBlockBody.fields.deposits, + voluntaryExits: phase0Ssz.BeaconBlockBody.fields.voluntaryExits, + syncAggregate: altairSsz.SyncAggregate, executionPayloadHeader: ExecutionPayloadHeader, // Modified in ELECTRA blsToExecutionChanges: capellaSsz.BeaconBlockBody.fields.blsToExecutionChanges, blobKzgCommitments: denebSsz.BeaconBlockBody.fields.blobKzgCommitments, @@ -260,3 +332,20 @@ export const SSEPayloadAttributes = new ContainerType( }, {typeName: "SSEPayloadAttributes", jsonCase: "eth2"} ); + +export const AggregateAndProof = new ContainerType( + { + aggregatorIndex: ValidatorIndex, + aggregate: Attestation, + selectionProof: BLSSignature, + }, + {typeName: "AggregateAndProof", jsonCase: "eth2", cachePermanentRootStruct: true} +); + +export const SignedAggregateAndProof = new ContainerType( + { + message: AggregateAndProof, + signature: BLSSignature, + }, + {typeName: "SignedAggregateAndProof", jsonCase: "eth2"} +); diff --git a/packages/types/src/electra/types.ts b/packages/types/src/electra/types.ts index 1b9b42217b8c..7ec8e9273c04 100644 --- a/packages/types/src/electra/types.ts +++ b/packages/types/src/electra/types.ts @@ -34,3 +34,10 @@ export type LightClientUpdate = ValueOf; export type LightClientFinalityUpdate = ValueOf; export type LightClientOptimisticUpdate = ValueOf; export type LightClientStore = ValueOf; + +export type Attestation = ValueOf; +export type IndexedAttestation = ValueOf; +export type AggregateAndProof = ValueOf; +export type SignedAggregateAndProof = ValueOf; +export type AttesterSlashing = ValueOf +export type IndexedAttestationBigint = ValueOf \ No newline at end of file From 3624d7f31f04f7d24b79991d31549716d96a8bf3 Mon Sep 17 00:00:00 2001 From: Navie Chan Date: Mon, 29 Apr 2024 21:14:56 +0800 Subject: [PATCH 02/11] Update unit test --- .../src/chain/validation/attestation.ts | 4 +-- packages/beacon-node/src/util/sszBytes.ts | 14 +++++--- .../test/unit/util/sszBytes.test.ts | 34 ++++++++++++++----- packages/types/src/electra/sszTypes.ts | 1 + packages/validator/src/util/params.ts | 2 ++ 5 files changed, 40 insertions(+), 15 deletions(-) diff --git a/packages/beacon-node/src/chain/validation/attestation.ts b/packages/beacon-node/src/chain/validation/attestation.ts index fc39534b45e6..7b80d75e3063 100644 --- a/packages/beacon-node/src/chain/validation/attestation.ts +++ b/packages/beacon-node/src/chain/validation/attestation.ts @@ -305,7 +305,7 @@ async function validateGossipAttestationNoSignatureCheck( // > TODO: Do this check **before** getting the target state but don't recompute zipIndexes const aggregationBits = attestationOrCache.attestation ? attestationOrCache.attestation.aggregationBits - : getAggregationBitsFromAttestationSerialized(attestationOrCache.serializedData); + : getAggregationBitsFromAttestationSerialized(fork, attestationOrCache.serializedData); if (aggregationBits === null) { throw new AttestationError(GossipAction.REJECT, { code: AttestationErrorCode.INVALID_SERIALIZED_BYTES, @@ -414,7 +414,7 @@ async function validateGossipAttestationNoSignatureCheck( let attDataRootHex: RootHex; const signature = attestationOrCache.attestation ? attestationOrCache.attestation.signature - : getSignatureFromAttestationSerialized(attestationOrCache.serializedData); + : getSignatureFromAttestationSerialized(fork, attestationOrCache.serializedData); if (signature === null) { throw new AttestationError(GossipAction.REJECT, { code: AttestationErrorCode.INVALID_SERIALIZED_BYTES, diff --git a/packages/beacon-node/src/util/sszBytes.ts b/packages/beacon-node/src/util/sszBytes.ts index c14075aad14d..3172f3c756fb 100644 --- a/packages/beacon-node/src/util/sszBytes.ts +++ b/packages/beacon-node/src/util/sszBytes.ts @@ -1,15 +1,21 @@ import {BitArray, deserializeUint8ArrayBitListFromBytes} from "@chainsafe/ssz"; import {BLSSignature, RootHex, Slot} from "@lodestar/types"; import {toHex} from "@lodestar/utils"; -import {BYTES_PER_FIELD_ELEMENT, FIELD_ELEMENTS_PER_BLOB, ForkName, ForkSeq} from "@lodestar/params"; +import { + BYTES_PER_FIELD_ELEMENT, + FIELD_ELEMENTS_PER_BLOB, + ForkName, + ForkSeq, + MAX_COMMITTEES_PER_SLOT, +} from "@lodestar/params"; export type BlockRootHex = RootHex; export type AttDataBase64 = string; // class Attestation(Container): -// aggregation_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE] - offset 4 +// aggregation_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE] (BitList[MAX_VALIDATORS_PER_COMMITTEE * MAX_COMMITTEES_PER_SLOT]) - offset 4 // data: AttestationData - target data - 128 -// committee_bits: BitVector[MAX_COMMITTEES_PER_SLOT] - Electra only: 8 +// committee_bits: BitVector[MAX_COMMITTEES_PER_SLOT] - Electra only: 8 (mainnet) // signature: BLSSignature - 96 // // class AttestationData(Container): 128 bytes fixed size @@ -24,7 +30,7 @@ const ATTESTATION_BEACON_BLOCK_ROOT_OFFSET = VARIABLE_FIELD_OFFSET + 8 + 8; const ROOT_SIZE = 32; const SLOT_SIZE = 8; const ATTESTATION_DATA_SIZE = 128; -const COMMITTEE_BITS_SIZE = 8; +const COMMITTEE_BITS_SIZE = Math.max(Math.floor(MAX_COMMITTEES_PER_SLOT / 8), 1); const SIGNATURE_SIZE = 96; /** diff --git a/packages/beacon-node/test/unit/util/sszBytes.test.ts b/packages/beacon-node/test/unit/util/sszBytes.test.ts index bb5fc67a7ce6..51843c71f9e5 100644 --- a/packages/beacon-node/test/unit/util/sszBytes.test.ts +++ b/packages/beacon-node/test/unit/util/sszBytes.test.ts @@ -1,6 +1,7 @@ import {describe, it, expect} from "vitest"; -import {deneb, Epoch, phase0, RootHex, Slot, ssz} from "@lodestar/types"; +import {allForks, deneb, Epoch, phase0, RootHex, Slot, ssz} from "@lodestar/types"; import {fromHex, toHex} from "@lodestar/utils"; +import {ForkName} from "@lodestar/params"; import { getAttDataBase64FromAttestationSerialized, getAttDataBase64FromSignedAggregateAndProofSerialized, @@ -15,7 +16,7 @@ import { } from "../../../src/util/sszBytes.js"; describe("attestation SSZ serialized picking", () => { - const testCases: phase0.Attestation[] = [ + const testCases: allForks.Attestation[] = [ ssz.phase0.Attestation.defaultValue(), attestationFromValues( 4_000_000, @@ -23,18 +24,30 @@ describe("attestation SSZ serialized picking", () => { 200_00, "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeffffffffffffffffffffffffffffffff" ), + ssz.electra.Attestation.defaultValue(), ]; for (const [i, attestation] of testCases.entries()) { it(`attestation ${i}`, () => { - const bytes = ssz.phase0.Attestation.serialize(attestation); + const isElectra = "committeeBits" in attestation; + const bytes = isElectra + ? ssz.electra.Attestation.serialize(attestation) + : ssz.phase0.Attestation.serialize(attestation); expect(getSlotFromAttestationSerialized(bytes)).toBe(attestation.data.slot); expect(getBlockRootFromAttestationSerialized(bytes)).toBe(toHex(attestation.data.beaconBlockRoot)); - expect(getAggregationBitsFromAttestationSerialized(bytes)?.toBoolArray()).toEqual( - attestation.aggregationBits.toBoolArray() - ); - expect(getSignatureFromAttestationSerialized(bytes)).toEqual(attestation.signature); + + if (isElectra) { + expect(getAggregationBitsFromAttestationSerialized(ForkName.electra, bytes)?.toBoolArray()).toEqual( + attestation.aggregationBits.toBoolArray() + ); + expect(getSignatureFromAttestationSerialized(ForkName.electra, bytes)).toEqual(attestation.signature); + } else { + expect(getAggregationBitsFromAttestationSerialized(ForkName.phase0, bytes)?.toBoolArray()).toEqual( + attestation.aggregationBits.toBoolArray() + ); + expect(getSignatureFromAttestationSerialized(ForkName.phase0, bytes)).toEqual(attestation.signature); + } const attDataBase64 = ssz.phase0.AttestationData.serialize(attestation.data); expect(getAttDataBase64FromAttestationSerialized(bytes)).toBe(Buffer.from(attDataBase64).toString("base64")); @@ -65,14 +78,16 @@ describe("attestation SSZ serialized picking", () => { it("getAggregateionBitsFromAttestationSerialized - invalid data", () => { const invalidAggregationBitsDataSizes = [0, 4, 100, 128, 227]; for (const size of invalidAggregationBitsDataSizes) { - expect(getAggregationBitsFromAttestationSerialized(Buffer.alloc(size))).toBeNull(); + expect(getAggregationBitsFromAttestationSerialized(ForkName.phase0, Buffer.alloc(size))).toBeNull(); + expect(getAggregationBitsFromAttestationSerialized(ForkName.electra, Buffer.alloc(size))).toBeNull(); } }); it("getSignatureFromAttestationSerialized - invalid data", () => { const invalidSignatureDataSizes = [0, 4, 100, 128, 227]; for (const size of invalidSignatureDataSizes) { - expect(getSignatureFromAttestationSerialized(Buffer.alloc(size))).toBeNull(); + expect(getSignatureFromAttestationSerialized(ForkName.phase0, Buffer.alloc(size))).toBeNull(); + expect(getSignatureFromAttestationSerialized(ForkName.electra, Buffer.alloc(size))).toBeNull(); } }); }); @@ -86,6 +101,7 @@ describe("aggregateAndProof SSZ serialized picking", () => { 200_00, "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeffffffffffffffffffffffffffffffff" ), + ssz.electra.SignedAggregateAndProof.defaultValue(), ]; for (const [i, signedAggregateAndProof] of testCases.entries()) { diff --git a/packages/types/src/electra/sszTypes.ts b/packages/types/src/electra/sszTypes.ts index 09956fac3117..742262749985 100644 --- a/packages/types/src/electra/sszTypes.ts +++ b/packages/types/src/electra/sszTypes.ts @@ -16,6 +16,7 @@ import { MAX_COMMITTEES_PER_SLOT, MAX_ATTESTATIONS_ELECTRA, MAX_ATTESTER_SLASHINGS_ELECTRA, + MAX_EXECUTION_LAYER_EXITS, } from "@lodestar/params"; import {ssz as primitiveSsz} from "../primitive/index.js"; import {ssz as phase0Ssz} from "../phase0/index.js"; diff --git a/packages/validator/src/util/params.ts b/packages/validator/src/util/params.ts index ca1b36883a90..1eda005b70c3 100644 --- a/packages/validator/src/util/params.ts +++ b/packages/validator/src/util/params.ts @@ -225,5 +225,7 @@ function getSpecCriticalParams(localConfig: ChainConfig): Record Date: Mon, 29 Apr 2024 21:46:46 +0800 Subject: [PATCH 03/11] lint --- packages/types/src/electra/sszTypes.ts | 15 +++++++++++++-- packages/types/src/electra/types.ts | 4 ++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/packages/types/src/electra/sszTypes.ts b/packages/types/src/electra/sszTypes.ts index 742262749985..26213a3fbf30 100644 --- a/packages/types/src/electra/sszTypes.ts +++ b/packages/types/src/electra/sszTypes.ts @@ -25,8 +25,19 @@ import {ssz as bellatrixSsz} from "../bellatrix/index.js"; import {ssz as capellaSsz} from "../capella/index.js"; import {ssz as denebSsz} from "../deneb/index.js"; -const {UintNum64, Slot, Root, BLSSignature, UintBn256, Bytes32, BLSPubkey, DepositIndex, UintBn64, ExecutionAddress, ValidatorIndex} = - primitiveSsz; +const { + UintNum64, + Slot, + Root, + BLSSignature, + UintBn256, + Bytes32, + BLSPubkey, + DepositIndex, + UintBn64, + ExecutionAddress, + ValidatorIndex, +} = primitiveSsz; export const AggregationBits = new BitListType(MAX_VALIDATORS_PER_COMMITTEE * MAX_COMMITTEES_PER_SLOT); diff --git a/packages/types/src/electra/types.ts b/packages/types/src/electra/types.ts index 7ec8e9273c04..232c7854f45e 100644 --- a/packages/types/src/electra/types.ts +++ b/packages/types/src/electra/types.ts @@ -39,5 +39,5 @@ export type Attestation = ValueOf; export type IndexedAttestation = ValueOf; export type AggregateAndProof = ValueOf; export type SignedAggregateAndProof = ValueOf; -export type AttesterSlashing = ValueOf -export type IndexedAttestationBigint = ValueOf \ No newline at end of file +export type AttesterSlashing = ValueOf; +export type IndexedAttestationBigint = ValueOf; From d935b3cf8993189d24f1a51c636267eef1ec99a8 Mon Sep 17 00:00:00 2001 From: Navie Chan Date: Thu, 2 May 2024 18:00:11 +0800 Subject: [PATCH 04/11] Address comments --- packages/types/src/electra/sszTypes.ts | 38 ++++++++++++++------------ packages/types/src/electra/types.ts | 15 +++++----- 2 files changed, 28 insertions(+), 25 deletions(-) diff --git a/packages/types/src/electra/sszTypes.ts b/packages/types/src/electra/sszTypes.ts index 26213a3fbf30..20bf250c2d49 100644 --- a/packages/types/src/electra/sszTypes.ts +++ b/packages/types/src/electra/sszTypes.ts @@ -41,6 +41,8 @@ const { export const AggregationBits = new BitListType(MAX_VALIDATORS_PER_COMMITTEE * MAX_COMMITTEES_PER_SLOT); +// This CommitteeBits serves a different purpose than CommitteeBits in phase0 +// TODO Electra: Rename phase0.CommitteeBits to ParticipationBits to avoid confusion export const CommitteeBits = new BitVectorType(MAX_COMMITTEES_PER_SLOT); export const AttestingIndices = new ListBasicType( @@ -85,6 +87,23 @@ export const AttesterSlashing = new ContainerType( {typeName: "AttesterSlashing", jsonCase: "eth2"} ); +export const AggregateAndProof = new ContainerType( + { + aggregatorIndex: ValidatorIndex, + aggregate: Attestation, + selectionProof: BLSSignature, + }, + {typeName: "AggregateAndProof", jsonCase: "eth2", cachePermanentRootStruct: true} +); + +export const SignedAggregateAndProof = new ContainerType( + { + message: AggregateAndProof, + signature: BLSSignature, + }, + {typeName: "SignedAggregateAndProof", jsonCase: "eth2"} +); + export const DepositReceipt = new ContainerType( { pubkey: BLSPubkey, @@ -343,21 +362,4 @@ export const SSEPayloadAttributes = new ContainerType( payloadAttributes: PayloadAttributes, }, {typeName: "SSEPayloadAttributes", jsonCase: "eth2"} -); - -export const AggregateAndProof = new ContainerType( - { - aggregatorIndex: ValidatorIndex, - aggregate: Attestation, - selectionProof: BLSSignature, - }, - {typeName: "AggregateAndProof", jsonCase: "eth2", cachePermanentRootStruct: true} -); - -export const SignedAggregateAndProof = new ContainerType( - { - message: AggregateAndProof, - signature: BLSSignature, - }, - {typeName: "SignedAggregateAndProof", jsonCase: "eth2"} -); +); \ No newline at end of file diff --git a/packages/types/src/electra/types.ts b/packages/types/src/electra/types.ts index 232c7854f45e..2925885b3af3 100644 --- a/packages/types/src/electra/types.ts +++ b/packages/types/src/electra/types.ts @@ -1,6 +1,14 @@ import {ValueOf} from "@chainsafe/ssz"; import * as ssz from "./sszTypes.js"; +export type Attestation = ValueOf; +export type IndexedAttestation = ValueOf; +export type IndexedAttestationBigint = ValueOf; +export type AttesterSlashing = ValueOf; + +export type AggregateAndProof = ValueOf; +export type SignedAggregateAndProof = ValueOf; + export type DepositReceipt = ValueOf; export type DepositReceipts = ValueOf; @@ -34,10 +42,3 @@ export type LightClientUpdate = ValueOf; export type LightClientFinalityUpdate = ValueOf; export type LightClientOptimisticUpdate = ValueOf; export type LightClientStore = ValueOf; - -export type Attestation = ValueOf; -export type IndexedAttestation = ValueOf; -export type AggregateAndProof = ValueOf; -export type SignedAggregateAndProof = ValueOf; -export type AttesterSlashing = ValueOf; -export type IndexedAttestationBigint = ValueOf; From 0511b001b71b5543955b993505d59cc0c7dd5a9e Mon Sep 17 00:00:00 2001 From: Navie Chan Date: Thu, 2 May 2024 18:03:52 +0800 Subject: [PATCH 05/11] Address comments --- packages/types/src/electra/sszTypes.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/types/src/electra/sszTypes.ts b/packages/types/src/electra/sszTypes.ts index 20bf250c2d49..8a7e739bd56b 100644 --- a/packages/types/src/electra/sszTypes.ts +++ b/packages/types/src/electra/sszTypes.ts @@ -52,9 +52,9 @@ export const AttestingIndices = new ListBasicType( export const Attestation = new ContainerType( { - aggregationBits: AggregationBits, + aggregationBits: AggregationBits, // Modified in ELECTRA data: phase0Ssz.AttestationData, - committeeBits: CommitteeBits, + committeeBits: CommitteeBits, // New in ELECTRA signature: BLSSignature, }, {typeName: "Attestation", jsonCase: "eth2"} @@ -62,7 +62,7 @@ export const Attestation = new ContainerType( export const IndexedAttestation = new ContainerType( { - attestingIndices: AttestingIndices, + attestingIndices: AttestingIndices, // Modified in ELECTRA data: phase0Ssz.AttestationData, signature: BLSSignature, }, @@ -72,7 +72,7 @@ export const IndexedAttestation = new ContainerType( /** Same as `IndexedAttestation` but epoch, slot and index are not bounded and must be a bigint */ export const IndexedAttestationBigint = new ContainerType( { - attestingIndices: AttestingIndices, + attestingIndices: AttestingIndices, // Modified in ELECTRA data: phase0Ssz.AttestationDataBigint, signature: BLSSignature, }, @@ -81,8 +81,8 @@ export const IndexedAttestationBigint = new ContainerType( export const AttesterSlashing = new ContainerType( { - attestation1: IndexedAttestationBigint, - attestation2: IndexedAttestationBigint, + attestation1: IndexedAttestationBigint, // Modified in ELECTRA + attestation2: IndexedAttestationBigint, // Modified in ELECTRA }, {typeName: "AttesterSlashing", jsonCase: "eth2"} ); @@ -90,7 +90,7 @@ export const AttesterSlashing = new ContainerType( export const AggregateAndProof = new ContainerType( { aggregatorIndex: ValidatorIndex, - aggregate: Attestation, + aggregate: Attestation, // Modified in ELECTRA selectionProof: BLSSignature, }, {typeName: "AggregateAndProof", jsonCase: "eth2", cachePermanentRootStruct: true} @@ -98,7 +98,7 @@ export const AggregateAndProof = new ContainerType( export const SignedAggregateAndProof = new ContainerType( { - message: AggregateAndProof, + message: AggregateAndProof, // Modified in ELECTRA signature: BLSSignature, }, {typeName: "SignedAggregateAndProof", jsonCase: "eth2"} From a3dba5d31931eda6548e12d6667caaf315aa9e51 Mon Sep 17 00:00:00 2001 From: Navie Chan Date: Thu, 2 May 2024 20:51:22 +0800 Subject: [PATCH 06/11] Lint --- packages/types/src/electra/sszTypes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/types/src/electra/sszTypes.ts b/packages/types/src/electra/sszTypes.ts index 8a7e739bd56b..3404baf04110 100644 --- a/packages/types/src/electra/sszTypes.ts +++ b/packages/types/src/electra/sszTypes.ts @@ -362,4 +362,4 @@ export const SSEPayloadAttributes = new ContainerType( payloadAttributes: PayloadAttributes, }, {typeName: "SSEPayloadAttributes", jsonCase: "eth2"} -); \ No newline at end of file +); From b488b60df20db58156f8fc52aa7362378a500be7 Mon Sep 17 00:00:00 2001 From: NC Date: Fri, 3 May 2024 14:46:25 +0800 Subject: [PATCH 07/11] Update packages/beacon-node/src/util/sszBytes.ts Co-authored-by: tuyennhv --- packages/beacon-node/src/util/sszBytes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/beacon-node/src/util/sszBytes.ts b/packages/beacon-node/src/util/sszBytes.ts index 3172f3c756fb..8a309d339d1b 100644 --- a/packages/beacon-node/src/util/sszBytes.ts +++ b/packages/beacon-node/src/util/sszBytes.ts @@ -30,7 +30,7 @@ const ATTESTATION_BEACON_BLOCK_ROOT_OFFSET = VARIABLE_FIELD_OFFSET + 8 + 8; const ROOT_SIZE = 32; const SLOT_SIZE = 8; const ATTESTATION_DATA_SIZE = 128; -const COMMITTEE_BITS_SIZE = Math.max(Math.floor(MAX_COMMITTEES_PER_SLOT / 8), 1); +const COMMITTEE_BITS_SIZE = Math.max(Math.ceil(MAX_COMMITTEES_PER_SLOT / 8), 1); const SIGNATURE_SIZE = 96; /** From e77ebab7afd56eab7d1e864b1d9949452b207602 Mon Sep 17 00:00:00 2001 From: Navie Chan Date: Fri, 3 May 2024 15:00:54 +0800 Subject: [PATCH 08/11] Add isElectraAttestation --- packages/beacon-node/test/unit/util/sszBytes.test.ts | 4 ++-- packages/types/src/utils/typeguards.ts | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/beacon-node/test/unit/util/sszBytes.test.ts b/packages/beacon-node/test/unit/util/sszBytes.test.ts index 51843c71f9e5..271757eaa846 100644 --- a/packages/beacon-node/test/unit/util/sszBytes.test.ts +++ b/packages/beacon-node/test/unit/util/sszBytes.test.ts @@ -1,5 +1,5 @@ import {describe, it, expect} from "vitest"; -import {allForks, deneb, Epoch, phase0, RootHex, Slot, ssz} from "@lodestar/types"; +import {allForks, deneb, Epoch, isElectraAttestation, phase0, RootHex, Slot, ssz} from "@lodestar/types"; import {fromHex, toHex} from "@lodestar/utils"; import {ForkName} from "@lodestar/params"; import { @@ -29,7 +29,7 @@ describe("attestation SSZ serialized picking", () => { for (const [i, attestation] of testCases.entries()) { it(`attestation ${i}`, () => { - const isElectra = "committeeBits" in attestation; + const isElectra = isElectraAttestation(attestation); const bytes = isElectra ? ssz.electra.Attestation.serialize(attestation) : ssz.phase0.Attestation.serialize(attestation); diff --git a/packages/types/src/utils/typeguards.ts b/packages/types/src/utils/typeguards.ts index 781738c3dbad..22c75302c159 100644 --- a/packages/types/src/utils/typeguards.ts +++ b/packages/types/src/utils/typeguards.ts @@ -14,6 +14,7 @@ import { ExecutionPayload, ExecutionPayloadAndBlobsBundle, } from "../allForks/types.js"; +import {allForks, electra} from "../types.js"; export function isBlindedExecution(payload: FullOrBlindedExecutionPayload): payload is ExecutionPayloadHeader { // we just check transactionsRoot for determinging as it the base field @@ -49,3 +50,7 @@ export function isExecutionPayloadAndBlobsBundle( ): data is ExecutionPayloadAndBlobsBundle { return (data as ExecutionPayloadAndBlobsBundle).blobsBundle !== undefined; } + +export function isElectraAttestation(attestation: allForks.Attestation): attestation is electra.Attestation { + return (attestation as electra.Attestation).committeeBits !== undefined; +} From 56c5da59f2d0ee6334f0e93039519e32a3f4cb56 Mon Sep 17 00:00:00 2001 From: Navie Chan Date: Fri, 3 May 2024 15:29:58 +0800 Subject: [PATCH 09/11] Update unit test --- packages/beacon-node/src/util/sszBytes.ts | 2 +- packages/beacon-node/test/unit/util/sszBytes.test.ts | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/beacon-node/src/util/sszBytes.ts b/packages/beacon-node/src/util/sszBytes.ts index 8a309d339d1b..cba9119bcef5 100644 --- a/packages/beacon-node/src/util/sszBytes.ts +++ b/packages/beacon-node/src/util/sszBytes.ts @@ -120,7 +120,7 @@ export function getCommitteeBitsFromAttestationSerialized(data: Uint8Array): Bit const uint8Array = data.subarray(committeeBitsStartIndex, committeeBitsStartIndex + COMMITTEE_BITS_SIZE); - return new BitArray(uint8Array, uint8Array.byteLength * 8); + return new BitArray(uint8Array, MAX_COMMITTEES_PER_SLOT); } // diff --git a/packages/beacon-node/test/unit/util/sszBytes.test.ts b/packages/beacon-node/test/unit/util/sszBytes.test.ts index 271757eaa846..343ad0a25955 100644 --- a/packages/beacon-node/test/unit/util/sszBytes.test.ts +++ b/packages/beacon-node/test/unit/util/sszBytes.test.ts @@ -1,7 +1,8 @@ import {describe, it, expect} from "vitest"; +import {BitArray} from "@chainsafe/ssz"; import {allForks, deneb, Epoch, isElectraAttestation, phase0, RootHex, Slot, ssz} from "@lodestar/types"; import {fromHex, toHex} from "@lodestar/utils"; -import {ForkName} from "@lodestar/params"; +import {ForkName, MAX_COMMITTEES_PER_SLOT} from "@lodestar/params"; import { getAttDataBase64FromAttestationSerialized, getAttDataBase64FromSignedAggregateAndProofSerialized, @@ -13,6 +14,7 @@ import { getSignatureFromAttestationSerialized, getSlotFromSignedBeaconBlockSerialized, getSlotFromBlobSidecarSerialized, + getCommitteeBitsFromAttestationSerialized, } from "../../../src/util/sszBytes.js"; describe("attestation SSZ serialized picking", () => { @@ -24,7 +26,10 @@ describe("attestation SSZ serialized picking", () => { 200_00, "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeffffffffffffffffffffffffffffffff" ), - ssz.electra.Attestation.defaultValue(), + { + ...ssz.electra.Attestation.defaultValue(), + committeeBits: BitArray.fromSingleBit(MAX_COMMITTEES_PER_SLOT, 3), + }, ]; for (const [i, attestation] of testCases.entries()) { @@ -41,6 +46,7 @@ describe("attestation SSZ serialized picking", () => { expect(getAggregationBitsFromAttestationSerialized(ForkName.electra, bytes)?.toBoolArray()).toEqual( attestation.aggregationBits.toBoolArray() ); + expect(getCommitteeBitsFromAttestationSerialized(bytes)).toEqual(attestation.committeeBits); expect(getSignatureFromAttestationSerialized(ForkName.electra, bytes)).toEqual(attestation.signature); } else { expect(getAggregationBitsFromAttestationSerialized(ForkName.phase0, bytes)?.toBoolArray()).toEqual( From c08e67531d1f9b5917e07478a4daaabe537848ad Mon Sep 17 00:00:00 2001 From: Navie Chan Date: Fri, 3 May 2024 16:07:11 +0800 Subject: [PATCH 10/11] Update unit test --- packages/beacon-node/test/unit/util/sszBytes.test.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/beacon-node/test/unit/util/sszBytes.test.ts b/packages/beacon-node/test/unit/util/sszBytes.test.ts index 343ad0a25955..d0dc150cd792 100644 --- a/packages/beacon-node/test/unit/util/sszBytes.test.ts +++ b/packages/beacon-node/test/unit/util/sszBytes.test.ts @@ -26,8 +26,14 @@ describe("attestation SSZ serialized picking", () => { 200_00, "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeffffffffffffffffffffffffffffffff" ), + ssz.electra.Attestation.defaultValue(), { - ...ssz.electra.Attestation.defaultValue(), + ...attestationFromValues( + 4_000_000, + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + 200_00, + "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeffffffffffffffffffffffffffffffff" + ), committeeBits: BitArray.fromSingleBit(MAX_COMMITTEES_PER_SLOT, 3), }, ]; From e245b226e901a36749b055aa13fdcb80e4c225aa Mon Sep 17 00:00:00 2001 From: Tuyen Nguyen Date: Sat, 4 May 2024 15:26:27 +0700 Subject: [PATCH 11/11] chore: add comments for sszBytes.ts --- packages/beacon-node/src/util/sszBytes.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/beacon-node/src/util/sszBytes.ts b/packages/beacon-node/src/util/sszBytes.ts index cba9119bcef5..ff78921bb3c0 100644 --- a/packages/beacon-node/src/util/sszBytes.ts +++ b/packages/beacon-node/src/util/sszBytes.ts @@ -12,12 +12,20 @@ import { export type BlockRootHex = RootHex; export type AttDataBase64 = string; +// pre-electra // class Attestation(Container): -// aggregation_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE] (BitList[MAX_VALIDATORS_PER_COMMITTEE * MAX_COMMITTEES_PER_SLOT]) - offset 4 +// aggregation_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE] - offset 4 // data: AttestationData - target data - 128 -// committee_bits: BitVector[MAX_COMMITTEES_PER_SLOT] - Electra only: 8 (mainnet) +// signature: BLSSignature - 96 + +// electra +// class Attestation(Container): +// aggregation_bits: BitList[MAX_VALIDATORS_PER_COMMITTEE * MAX_COMMITTEES_PER_SLOT] - offset 4 +// data: AttestationData - target data - 128 +// committee_bits: BitVector[MAX_COMMITTEES_PER_SLOT] // signature: BLSSignature - 96 // +// for all forks // class AttestationData(Container): 128 bytes fixed size // slot: Slot - data 8 // index: CommitteeIndex - data 8