diff --git a/packages/beacon-node/src/chain/seenCache/seenAttestationData.ts b/packages/beacon-node/src/chain/seenCache/seenAttestationData.ts index 17343e386fd7..a0aa6db35893 100644 --- a/packages/beacon-node/src/chain/seenCache/seenAttestationData.ts +++ b/packages/beacon-node/src/chain/seenCache/seenAttestationData.ts @@ -2,9 +2,14 @@ import {BitArray} from "@chainsafe/ssz"; import {CommitteeIndex, phase0, RootHex, Slot} from "@lodestar/types"; import {MapDef} from "@lodestar/utils"; import {Metrics} from "../../metrics/metrics.js"; -import {SeenAttDataKey} from "../../util/sszBytes.js"; import {InsertOutcome} from "../opPools/types.js"; +export type SeenAttDataKey = AttDataBase64 | AttDataCommitteeBitsBase64; +// pre-electra, 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; diff --git a/packages/beacon-node/src/chain/validation/aggregateAndProof.ts b/packages/beacon-node/src/chain/validation/aggregateAndProof.ts index 00dea299ac5b..41a19eefb3fa 100644 --- a/packages/beacon-node/src/chain/validation/aggregateAndProof.ts +++ b/packages/beacon-node/src/chain/validation/aggregateAndProof.ts @@ -9,11 +9,11 @@ import { import {IBeaconChain} from ".."; import {AttestationError, AttestationErrorCode, GossipAction} from "../errors/index.js"; import {RegenCaller} from "../regen/index.js"; -import {getSeenAttDataKeyFromSignedAggregateAndProof} from "../../util/sszBytes.js"; import {getSelectionProofSignatureSet, getAggregateAndProofSignatureSet} from "./signatureSets/index.js"; import { getAttestationDataSigningRoot, getCommitteeIndices, + getSeenAttDataKeyFromSignedAggregateAndProof, getShufflingForAttestationVerification, verifyHeadBlockAndTargetRoot, verifyPropagationSlotRange, @@ -71,9 +71,7 @@ async function validateAggregateAndProof( const attData = aggregate.data; const attSlot = attData.slot; - const seenAttDataKey = serializedData - ? getSeenAttDataKeyFromSignedAggregateAndProof(ForkSeq[fork], serializedData) - : null; + const seenAttDataKey = serializedData ? getSeenAttDataKeyFromSignedAggregateAndProof(fork, serializedData) : null; const cachedAttData = seenAttDataKey ? chain.seenAttestationDatas.get(attSlot, seenAttDataKey) : null; let attIndex; diff --git a/packages/beacon-node/src/chain/validation/attestation.ts b/packages/beacon-node/src/chain/validation/attestation.ts index c27b43586595..5f27fbd75d0c 100644 --- a/packages/beacon-node/src/chain/validation/attestation.ts +++ b/packages/beacon-node/src/chain/validation/attestation.ts @@ -13,7 +13,14 @@ import { IndexedAttestation, } from "@lodestar/types"; import {ProtoBlock} from "@lodestar/fork-choice"; -import {ATTESTATION_SUBNET_COUNT, SLOTS_PER_EPOCH, ForkName, ForkSeq, DOMAIN_BEACON_ATTESTER} from "@lodestar/params"; +import { + ATTESTATION_SUBNET_COUNT, + SLOTS_PER_EPOCH, + ForkName, + ForkSeq, + DOMAIN_BEACON_ATTESTER, + isForkPostElectra, +} from "@lodestar/params"; import { computeEpochAtSlot, createSingleSignatureSetFromComponents, @@ -29,12 +36,15 @@ import {AttestationError, AttestationErrorCode, GossipAction} from "../errors/in import {MAXIMUM_GOSSIP_CLOCK_DISPARITY_SEC} from "../../constants/index.js"; import {RegenCaller} from "../regen/index.js"; import { - SeenAttDataKey, getAggregationBitsFromAttestationSerialized, - getSeenAttDataKey, + getAttDataFromAttestationSerialized, + getAttDataFromSignedAggregateAndProofElectra, + getCommitteeBitsFromAttestationSerialized, + getCommitteeBitsFromSignedAggregateAndProofElectra, + getAttDataFromSignedAggregateAndProofPhase0, getSignatureFromAttestationSerialized, } from "../../util/sszBytes.js"; -import {AttestationDataCacheEntry} from "../seenCache/seenAttestationData.js"; +import {AttestationDataCacheEntry, SeenAttDataKey} from "../seenCache/seenAttestationData.js"; import {sszDeserializeAttestation} from "../../network/gossip/topic.js"; import {Result, wrapError} from "../../util/wrapError.js"; import {IBeaconChain} from "../interface.js"; @@ -66,7 +76,7 @@ export type GossipAttestation = { attSlot: Slot; // for old LIFO linear gossip queue we don't have attDataBase64 // for indexed gossip queue we have attDataBase64 - seenAttestationKey?: SeenAttDataKey | null; + attDataBase64?: SeenAttDataKey | null; }; export type Step0Result = AttestationValidationResult & { @@ -269,11 +279,7 @@ async function validateGossipAttestationNoSignatureCheck( if (attestationOrBytes.serializedData) { // gossip const attSlot = attestationOrBytes.attSlot; - attDataKey = - // we always have seenAttestationKey from the IndexedGossipQueue, getSeenAttDataKey() just for backward - // compatible in case beaconAttestationBatchValidation is false - // TODO: remove beaconAttestationBatchValidation flag since the batch attestation is stable - attestationOrBytes.seenAttestationKey ?? getSeenAttDataKey(ForkSeq[fork], attestationOrBytes.serializedData); + attDataKey = getSeenAttDataKeyFromGossipAttestation(fork, attestationOrBytes); const cachedAttData = attDataKey !== null ? chain.seenAttestationDatas.get(attSlot, attDataKey) : null; if (cachedAttData === null) { const attestation = sszDeserializeAttestation(fork, attestationOrBytes.serializedData); @@ -789,3 +795,45 @@ export function computeSubnetForSlot(shuffling: EpochShuffling, slot: number, co const committeesSinceEpochStart = shuffling.committeesPerSlot * slotsSinceEpochStart; return (committeesSinceEpochStart + committeeIndex) % ATTESTATION_SUBNET_COUNT; } + +/** + * 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 + * + * we always have attDataBase64 from the IndexedGossipQueue, getAttDataFromAttestationSerialized() just for backward compatible when beaconAttestationBatchValidation is false + * TODO: remove beaconAttestationBatchValidation flag since the batch attestation is stable + */ +export function getSeenAttDataKeyFromGossipAttestation( + fork: ForkName, + attestation: GossipAttestation +): SeenAttDataKey | null { + const {attDataBase64, serializedData} = attestation; + if (isForkPostElectra(fork)) { + const attData = attDataBase64 ?? getAttDataFromAttestationSerialized(serializedData); + const committeeBits = getCommitteeBitsFromAttestationSerialized(serializedData); + return attData && committeeBits ? attDataBase64 + committeeBits : null; + } + + // pre-electra + return attDataBase64 ?? getAttDataFromAttestationSerialized(serializedData); +} + +/** + * 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 + */ +export function getSeenAttDataKeyFromSignedAggregateAndProof( + fork: ForkName, + aggregateAndProof: Uint8Array +): SeenAttDataKey | null { + if (isForkPostElectra(fork)) { + const attData = getAttDataFromSignedAggregateAndProofElectra(aggregateAndProof); + const committeeBits = getCommitteeBitsFromSignedAggregateAndProofElectra(aggregateAndProof); + return attData && committeeBits ? attData + committeeBits : null; + } + + // pre-electra + return getAttDataFromSignedAggregateAndProofPhase0(aggregateAndProof); +} diff --git a/packages/beacon-node/src/util/sszBytes.ts b/packages/beacon-node/src/util/sszBytes.ts index 383e0289da2b..73c9172297b6 100644 --- a/packages/beacon-node/src/util/sszBytes.ts +++ b/packages/beacon-node/src/util/sszBytes.ts @@ -10,11 +10,10 @@ import { } from "@lodestar/params"; export type BlockRootHex = RootHex; -export type SeenAttDataKey = AttDataBase64 | AttDataCommitteeBitsBase64; // pre-electra, AttestationData is used to cache attestations export type AttDataBase64 = string; -// electra, AttestationData + CommitteeBits are used to cache attestations -export type AttDataCommitteeBitsBase64 = string; +// electra, CommitteeBits +export type CommitteeBitsBase64 = string; // pre-electra // class Attestation(Container): @@ -71,50 +70,22 @@ export function getBlockRootFromAttestationSerialized(data: Uint8Array): BlockRo } /** - * Extract attestation data key from an attestation Uint8Array in order to index gossip queue and cache later in SeenAttestationDatas - */ -export function getSeenAttDataKey(forkSeq: ForkSeq, data: Uint8Array): SeenAttDataKey | null { - return forkSeq >= ForkSeq.electra ? getSeenAttDataKeyElectra(data) : getSeenAttDataKeyPhase0(data); -} - -/** - * Extract attestation data + committeeBits base64 from electra attestation serialized bytes. - * Return null if data is not long enough to extract attestation data. - */ -export function getSeenAttDataKeyElectra(electraAttestationBytes: Uint8Array): AttDataCommitteeBitsBase64 | null { - const attestationData = getSeenAttDataKeyPhase0(electraAttestationBytes); - - if (attestationData === null) { - return null; - } - - const committeeBits = getCommitteeBitsFromAttestationSerialized(electraAttestationBytes); - - if (committeeBits === null) { - return null; - } - - return attestationData + toBase64(committeeBits.uint8Array); -} - -/** - * Extract attestation data base64 from phase0 attestation serialized bytes. + * Extract attestation data base64 from all forks' attestation serialized bytes. * Return null if data is not long enough to extract attestation data. */ -export function getSeenAttDataKeyPhase0(data: Uint8Array): AttDataBase64 | null { +export function getAttDataFromAttestationSerialized(data: Uint8Array): AttDataBase64 | null { if (data.length < VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE) { return null; } - // base64 is a bit efficient than hex - return toBase64(data.slice(VARIABLE_FIELD_OFFSET, VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE)); + return toBase64(data.subarray(VARIABLE_FIELD_OFFSET, VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE)); } /** - * Alias of `getSeenAttDataKeyPhase0` specifically for batch handling indexing in gossip queue + * Alias of `getAttDataFromAttestationSerialized` specifically for batch handling indexing in gossip queue */ export function getGossipAttestationIndex(data: Uint8Array): AttDataBase64 | null { - return getSeenAttDataKeyPhase0(data); + return getAttDataFromAttestationSerialized(data); } /** @@ -153,7 +124,7 @@ 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. */ -export function getCommitteeBitsFromAttestationSerialized(data: Uint8Array): BitArray | null { +export function getCommitteeBitsFromAttestationSerialized(data: Uint8Array): CommitteeBitsBase64 | null { const committeeBitsStartIndex = VARIABLE_FIELD_OFFSET + ATTESTATION_DATA_SIZE + SIGNATURE_SIZE; if (data.length < committeeBitsStartIndex + COMMITTEE_BITS_SIZE) { @@ -162,7 +133,7 @@ export function getCommitteeBitsFromAttestationSerialized(data: Uint8Array): Bit const uint8Array = data.subarray(committeeBitsStartIndex, committeeBitsStartIndex + COMMITTEE_BITS_SIZE); - return new BitArray(uint8Array, MAX_COMMITTEES_PER_SLOT); + return toBase64(uint8Array); } // @@ -212,49 +183,49 @@ export function getBlockRootFromSignedAggregateAndProofSerialized(data: Uint8Arr } /** - * Extract attestation data key from SignedAggregateAndProof Uint8Array to use cached data from SeenAttestationDatas - */ -export function getSeenAttDataKeyFromSignedAggregateAndProof( - forkSeq: ForkSeq, - data: Uint8Array -): SeenAttDataKey | null { - return forkSeq >= ForkSeq.electra - ? getSeenAttDataKeyFromSignedAggregateAndProofElectra(data) - : getSeenAttDataKeyFromSignedAggregateAndProofPhase0(data); -} - -/** - * Extract AttestationData + CommitteeBits from SignedAggregateAndProof for electra + * Extract AttestationData base64 from SignedAggregateAndProof for electra * Return null if data is not long enough */ -export function getSeenAttDataKeyFromSignedAggregateAndProofElectra(data: Uint8Array): SeenAttDataKey | null { +export function getAttDataFromSignedAggregateAndProofElectra(data: Uint8Array): AttDataBase64 | null { const startIndex = SIGNED_AGGREGATE_AND_PROOF_SLOT_OFFSET; const endIndex = startIndex + ATTESTATION_DATA_SIZE; if (data.length < endIndex + SIGNATURE_SIZE + COMMITTEE_BITS_SIZE) { return null; } + return toBase64(data.subarray(startIndex, endIndex)); +} - // base64 is a bit efficient than hex +/** + * Extract CommitteeBits base64 from SignedAggregateAndProof for electra + * Return null if data is not long enough + */ +export function getCommitteeBitsFromSignedAggregateAndProofElectra(data: Uint8Array): CommitteeBitsBase64 | null { + const startIndex = SIGNED_AGGREGATE_AND_PROOF_SLOT_OFFSET + ATTESTATION_DATA_SIZE + SIGNATURE_SIZE; + const endIndex = startIndex + COMMITTEE_BITS_SIZE; + + if (data.length < endIndex) { + return null; + } - return Buffer.concat([ - data.subarray(startIndex, endIndex), - data.subarray(endIndex + SIGNATURE_SIZE, endIndex + SIGNATURE_SIZE + COMMITTEE_BITS_SIZE), - ]).toString("base64"); + return toBase64(data.subarray(startIndex, endIndex)); } /** * Extract attestation data base64 from signed aggregate and proof serialized bytes. * Return null if data is not long enough to extract attestation data. */ -export function getSeenAttDataKeyFromSignedAggregateAndProofPhase0(data: Uint8Array): AttDataBase64 | null { +export function getAttDataFromSignedAggregateAndProofPhase0(data: Uint8Array): AttDataBase64 | null { if (data.length < SIGNED_AGGREGATE_AND_PROOF_SLOT_OFFSET + ATTESTATION_DATA_SIZE) { return null; } // base64 is a bit efficient than hex return toBase64( - data.slice(SIGNED_AGGREGATE_AND_PROOF_SLOT_OFFSET, SIGNED_AGGREGATE_AND_PROOF_SLOT_OFFSET + ATTESTATION_DATA_SIZE) + data.subarray( + SIGNED_AGGREGATE_AND_PROOF_SLOT_OFFSET, + SIGNED_AGGREGATE_AND_PROOF_SLOT_OFFSET + ATTESTATION_DATA_SIZE + ) ); } @@ -323,6 +294,9 @@ function checkSlotHighBytes(data: Uint8Array, offset: number): boolean { return (data[offset + 4] | data[offset + 5] | data[offset + 6] | data[offset + 7]) === 0; } +/** + * base64 is a bit efficient than hex + */ function toBase64(data: Uint8Array): string { return Buffer.from(data.buffer, data.byteOffset, data.byteLength).toString("base64"); } diff --git a/packages/beacon-node/test/perf/chain/validation/attestation.test.ts b/packages/beacon-node/test/perf/chain/validation/attestation.test.ts index e0e0a1e51169..f285317474d6 100644 --- a/packages/beacon-node/test/perf/chain/validation/attestation.test.ts +++ b/packages/beacon-node/test/perf/chain/validation/attestation.test.ts @@ -5,7 +5,7 @@ import {ssz} from "@lodestar/types"; import {generateTestCachedBeaconStateOnlyValidators} from "../../../../../state-transition/test/perf/util.js"; import {validateAttestation, validateGossipAttestationsSameAttData} from "../../../../src/chain/validation/index.js"; import {getAttestationValidData} from "../../../utils/validationData/attestation.js"; -import {getSeenAttDataKeyPhase0} from "../../../../src/util/sszBytes.js"; +import {getAttDataFromAttestationSerialized} from "../../../../src/util/sszBytes.js"; describe("validate gossip attestation", () => { setBenchOpts({ @@ -42,7 +42,7 @@ describe("validate gossip attestation", () => { attestation: null, serializedData, attSlot, - seenAttestationKey: getSeenAttDataKeyPhase0(serializedData), + attDataBase64: getAttDataFromAttestationSerialized(serializedData), }, subnet0 ); @@ -67,7 +67,7 @@ describe("validate gossip attestation", () => { attestation: null, serializedData, attSlot, - attDataBase64: getSeenAttDataKeyPhase0(serializedData), + attDataBase64: getAttDataFromAttestationSerialized(serializedData), }; }); 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 4a1c3badae50..45d293ffbb33 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 @@ -1,6 +1,6 @@ import {BitArray} from "@chainsafe/ssz"; -import {describe, it} from "vitest"; -import {SLOTS_PER_EPOCH} from "@lodestar/params"; +import {describe, expect, it} from "vitest"; +import {ForkName, SLOTS_PER_EPOCH} from "@lodestar/params"; import {ssz} from "@lodestar/types"; // eslint-disable-next-line import/no-relative-packages import {generateTestCachedBeaconStateOnlyValidators} from "../../../../../../state-transition/test/perf/util.js"; @@ -9,14 +9,17 @@ import {IBeaconChain} from "../../../../../src/chain/index.js"; import { ApiAttestation, GossipAttestation, + getSeenAttDataKeyFromGossipAttestation, + getSeenAttDataKeyFromSignedAggregateAndProof, validateApiAttestation, validateAttestation, } from "../../../../../src/chain/validation/index.js"; -import {getSeenAttDataKeyPhase0} from "../../../../../src/util/sszBytes.js"; +import {getAttDataFromAttestationSerialized} from "../../../../../src/util/sszBytes.js"; import {memoOnce} from "../../../../utils/cache.js"; import {expectRejectedWithLodestarError} from "../../../../utils/errors.js"; import {AttestationValidDataOpts, getAttestationValidData} from "../../../../utils/validationData/attestation.js"; +// TODO: more tests for electra describe("validateAttestation", () => { const vc = 64; const stateSlot = 100; @@ -52,7 +55,7 @@ describe("validateAttestation", () => { const {chain, subnet} = getValidData(); await expectGossipError( chain, - {attestation: null, serializedData: Buffer.alloc(0), attSlot: 0, seenAttestationKey: "invalid"}, + {attestation: null, serializedData: Buffer.alloc(0), attSlot: 0, attDataBase64: "invalid"}, subnet, GossipErrorCode.INVALID_SERIALIZED_BYTES_ERROR_CODE ); @@ -72,7 +75,7 @@ describe("validateAttestation", () => { attestation: null, serializedData, attSlot: attestation.data.slot, - seenAttestationKey: getSeenAttDataKeyPhase0(serializedData), + attDataBase64: getAttDataFromAttestationSerialized(serializedData), }, subnet, AttestationErrorCode.BAD_TARGET_EPOCH @@ -91,7 +94,7 @@ describe("validateAttestation", () => { attestation: null, serializedData, attSlot: attestation.data.slot, - seenAttestationKey: getSeenAttDataKeyPhase0(serializedData), + attDataBase64: getAttDataFromAttestationSerialized(serializedData), }, subnet, AttestationErrorCode.PAST_SLOT @@ -110,7 +113,7 @@ describe("validateAttestation", () => { attestation: null, serializedData, attSlot: attestation.data.slot, - seenAttestationKey: getSeenAttDataKeyPhase0(serializedData), + attDataBase64: getAttDataFromAttestationSerialized(serializedData), }, subnet, AttestationErrorCode.FUTURE_SLOT @@ -135,7 +138,7 @@ describe("validateAttestation", () => { attestation: null, serializedData, attSlot: attestation.data.slot, - seenAttestationKey: getSeenAttDataKeyPhase0(serializedData), + attDataBase64: getAttDataFromAttestationSerialized(serializedData), }, subnet, AttestationErrorCode.NOT_EXACTLY_ONE_AGGREGATION_BIT_SET @@ -155,7 +158,7 @@ describe("validateAttestation", () => { attestation: null, serializedData, attSlot: attestation.data.slot, - seenAttestationKey: getSeenAttDataKeyPhase0(serializedData), + attDataBase64: getAttDataFromAttestationSerialized(serializedData), }, subnet, AttestationErrorCode.NOT_EXACTLY_ONE_AGGREGATION_BIT_SET @@ -179,7 +182,7 @@ describe("validateAttestation", () => { attestation: null, serializedData, attSlot: attestation.data.slot, - seenAttestationKey: getSeenAttDataKeyPhase0(serializedData), + attDataBase64: getAttDataFromAttestationSerialized(serializedData), }, subnet, AttestationErrorCode.UNKNOWN_OR_PREFINALIZED_BEACON_BLOCK_ROOT @@ -199,7 +202,7 @@ describe("validateAttestation", () => { attestation: null, serializedData, attSlot: attestation.data.slot, - seenAttestationKey: getSeenAttDataKeyPhase0(serializedData), + attDataBase64: getAttDataFromAttestationSerialized(serializedData), }, subnet, AttestationErrorCode.INVALID_TARGET_ROOT @@ -226,7 +229,7 @@ describe("validateAttestation", () => { attestation: null, serializedData, attSlot: attestation.data.slot, - seenAttestationKey: getSeenAttDataKeyPhase0(serializedData), + attDataBase64: getAttDataFromAttestationSerialized(serializedData), }, subnet, AttestationErrorCode.WRONG_NUMBER_OF_AGGREGATION_BITS @@ -245,7 +248,7 @@ describe("validateAttestation", () => { attestation: null, serializedData, attSlot: attestation.data.slot, - seenAttestationKey: getSeenAttDataKeyPhase0(serializedData), + attDataBase64: getAttDataFromAttestationSerialized(serializedData), }, invalidSubnet, AttestationErrorCode.INVALID_SUBNET_ID @@ -265,7 +268,7 @@ describe("validateAttestation", () => { attestation: null, serializedData, attSlot: attestation.data.slot, - seenAttestationKey: getSeenAttDataKeyPhase0(serializedData), + attDataBase64: getAttDataFromAttestationSerialized(serializedData), }, subnet, AttestationErrorCode.ATTESTATION_ALREADY_KNOWN @@ -287,7 +290,7 @@ describe("validateAttestation", () => { attestation: null, serializedData, attSlot: attestation.data.slot, - seenAttestationKey: getSeenAttDataKeyPhase0(serializedData), + attDataBase64: getAttDataFromAttestationSerialized(serializedData), }, subnet, AttestationErrorCode.INVALID_SIGNATURE @@ -314,3 +317,53 @@ describe("validateAttestation", () => { await expectRejectedWithLodestarError(validateAttestation(fork, chain, attestationOrBytes, subnet), errorCode); } }); + +describe("getSeenAttDataKey", () => { + const slot = 100; + const index = 0; + const blockRoot = Buffer.alloc(32, 1); + + it("phase0", () => { + const attestationData = ssz.phase0.AttestationData.defaultValue(); + attestationData.slot = slot; + attestationData.index = index; + attestationData.beaconBlockRoot = blockRoot; + const attestation = ssz.phase0.Attestation.defaultValue(); + attestation.data = attestationData; + const attDataBase64 = Buffer.from(ssz.phase0.AttestationData.serialize(attestationData)).toString("base64"); + const attestationBytes = ssz.phase0.Attestation.serialize(attestation); + const gossipAttestation = {attDataBase64, serializedData: attestationBytes, attSlot: slot} as GossipAttestation; + + const signedAggregateAndProof = ssz.phase0.SignedAggregateAndProof.defaultValue(); + signedAggregateAndProof.message.aggregate.data.slot = slot; + signedAggregateAndProof.message.aggregate.data.index = index; + signedAggregateAndProof.message.aggregate.data.beaconBlockRoot = blockRoot; + const aggregateAndProofBytes = ssz.phase0.SignedAggregateAndProof.serialize(signedAggregateAndProof); + + expect(getSeenAttDataKeyFromGossipAttestation(ForkName.phase0, gossipAttestation)).toEqual( + getSeenAttDataKeyFromSignedAggregateAndProof(ForkName.phase0, aggregateAndProofBytes) + ); + }); + + it("electra", () => { + const attestationData = ssz.phase0.AttestationData.defaultValue(); + attestationData.slot = slot; + attestationData.index = index; + attestationData.beaconBlockRoot = blockRoot; + const attestation = ssz.electra.Attestation.defaultValue(); + attestation.data = attestationData; + const attDataBase64 = Buffer.from(ssz.phase0.AttestationData.serialize(attestationData)).toString("base64"); + const attestationBytes = ssz.electra.Attestation.serialize(attestation); + const gossipAttestation = {attDataBase64, serializedData: attestationBytes, attSlot: slot} as GossipAttestation; + + const signedAggregateAndProof = ssz.electra.SignedAggregateAndProof.defaultValue(); + signedAggregateAndProof.message.aggregate.data.slot = slot; + signedAggregateAndProof.message.aggregate.data.index = index; + signedAggregateAndProof.message.aggregate.data.beaconBlockRoot = blockRoot; + const aggregateAndProofBytes = ssz.electra.SignedAggregateAndProof.serialize(signedAggregateAndProof); + + expect(getSeenAttDataKeyFromGossipAttestation(ForkName.electra, 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 643b6b1bac0f..88e1d58c034a 100644 --- a/packages/beacon-node/test/unit/util/sszBytes.test.ts +++ b/packages/beacon-node/test/unit/util/sszBytes.test.ts @@ -4,8 +4,8 @@ import {deneb, electra, Epoch, isElectraAttestation, phase0, RootHex, Slot, ssz} import {fromHex, toHex} from "@lodestar/utils"; import {ForkName, MAX_COMMITTEES_PER_SLOT} from "@lodestar/params"; import { - getSeenAttDataKeyPhase0, - getSeenAttDataKeyFromSignedAggregateAndProofPhase0, + getAttDataFromAttestationSerialized, + getAttDataFromSignedAggregateAndProofPhase0, getAggregationBitsFromAttestationSerialized as getAggregationBitsFromAttestationSerialized, getBlockRootFromAttestationSerialized, getBlockRootFromSignedAggregateAndProofSerialized, @@ -15,7 +15,8 @@ import { getSlotFromSignedBeaconBlockSerialized, getSlotFromBlobSidecarSerialized, getCommitteeBitsFromAttestationSerialized, - getSeenAttDataKeyFromSignedAggregateAndProofElectra, + getCommitteeBitsFromSignedAggregateAndProofElectra, + getAttDataFromSignedAggregateAndProofElectra, } from "../../../src/util/sszBytes.js"; describe("attestation SSZ serialized picking", () => { @@ -53,7 +54,9 @@ describe("attestation SSZ serialized picking", () => { expect(getAggregationBitsFromAttestationSerialized(ForkName.electra, bytes)?.toBoolArray()).toEqual( attestation.aggregationBits.toBoolArray() ); - expect(getCommitteeBitsFromAttestationSerialized(bytes)).toEqual(attestation.committeeBits); + expect(getCommitteeBitsFromAttestationSerialized(bytes)).toEqual( + Buffer.from(attestation.committeeBits.uint8Array).toString("base64") + ); expect(getSignatureFromAttestationSerialized(bytes)).toEqual(attestation.signature); } else { expect(getAggregationBitsFromAttestationSerialized(ForkName.phase0, bytes)?.toBoolArray()).toEqual( @@ -63,7 +66,7 @@ describe("attestation SSZ serialized picking", () => { } const attDataBase64 = ssz.phase0.AttestationData.serialize(attestation.data); - expect(getSeenAttDataKeyPhase0(bytes)).toBe(Buffer.from(attDataBase64).toString("base64")); + expect(getAttDataFromAttestationSerialized(bytes)).toBe(Buffer.from(attDataBase64).toString("base64")); }); } @@ -81,10 +84,10 @@ describe("attestation SSZ serialized picking", () => { } }); - it("getAttDataBase64FromAttestationSerialized - invalid data", () => { + it("getAttDataFromAttestationSerialized - invalid data", () => { const invalidAttDataBase64DataSizes = [0, 4, 100, 128, 131]; for (const size of invalidAttDataBase64DataSizes) { - expect(getSeenAttDataKeyPhase0(Buffer.alloc(size))).toBeNull(); + expect(getAttDataFromAttestationSerialized(Buffer.alloc(size))).toBeNull(); } }); @@ -128,9 +131,7 @@ describe("phase0 SignedAggregateAndProof SSZ serialized picking", () => { ); const attDataBase64 = ssz.phase0.AttestationData.serialize(signedAggregateAndProof.message.aggregate.data); - expect(getSeenAttDataKeyFromSignedAggregateAndProofPhase0(bytes)).toBe( - Buffer.from(attDataBase64).toString("base64") - ); + expect(getAttDataFromSignedAggregateAndProofPhase0(bytes)).toBe(Buffer.from(attDataBase64).toString("base64")); }); } @@ -151,7 +152,7 @@ describe("phase0 SignedAggregateAndProof SSZ serialized picking", () => { it("getAttDataBase64FromSignedAggregateAndProofSerialized - invalid data", () => { const invalidAttDataBase64DataSizes = [0, 4, 100, 128, 339]; for (const size of invalidAttDataBase64DataSizes) { - expect(getSeenAttDataKeyFromSignedAggregateAndProofPhase0(Buffer.alloc(size))).toBeNull(); + expect(getAttDataFromSignedAggregateAndProofPhase0(Buffer.alloc(size))).toBeNull(); } }); }); @@ -182,8 +183,11 @@ describe("electra SignedAggregateAndProof SSZ serialized picking", () => { const committeeBits = ssz.electra.CommitteeBits.serialize( signedAggregateAndProof.message.aggregate.committeeBits ); - const seenKey = Buffer.concat([attDataBase64, committeeBits]).toString("base64"); - expect(getSeenAttDataKeyFromSignedAggregateAndProofElectra(bytes)).toBe(seenKey); + + expect(getAttDataFromSignedAggregateAndProofElectra(bytes)).toBe(Buffer.from(attDataBase64).toString("base64")); + expect(getCommitteeBitsFromSignedAggregateAndProofElectra(bytes)).toBe( + Buffer.from(committeeBits).toString("base64") + ); }); } @@ -204,7 +208,7 @@ describe("electra SignedAggregateAndProof SSZ serialized picking", () => { it("getAttDataBase64FromSignedAggregateAndProofSerialized - invalid data", () => { const invalidAttDataBase64DataSizes = [0, 4, 100, 128, 339]; for (const size of invalidAttDataBase64DataSizes) { - expect(getSeenAttDataKeyFromSignedAggregateAndProofPhase0(Buffer.alloc(size))).toBeNull(); + expect(getAttDataFromSignedAggregateAndProofPhase0(Buffer.alloc(size))).toBeNull(); } }); it("getSlotFromSignedAggregateAndProofSerialized - invalid data - large slots", () => {