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 f1973fef615e..170c358fe417 100644 --- a/packages/beacon-node/src/api/impl/beacon/pool/index.ts +++ b/packages/beacon-node/src/api/impl/beacon/pool/index.ts @@ -52,8 +52,9 @@ export function getBeaconPoolApi({ await Promise.all( attestations.map(async (attestation, i) => { try { + const fork = chain.config.getForkName(chain.clock.currentSlot); // eslint-disable-next-line @typescript-eslint/explicit-function-return-type - const validateFn = () => validateApiAttestation(chain, {attestation, serializedData: null}); + const validateFn = () => validateApiAttestation(fork, chain, {attestation, serializedData: null}); const {slot, beaconBlockRoot} = attestation.data; // 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 diff --git a/packages/beacon-node/src/api/impl/validator/index.ts b/packages/beacon-node/src/api/impl/validator/index.ts index 242b6cccce9e..bc0b25a953d6 100644 --- a/packages/beacon-node/src/api/impl/validator/index.ts +++ b/packages/beacon-node/src/api/impl/validator/index.ts @@ -556,13 +556,14 @@ export function getValidatorApi({ const seenTimestampSec = Date.now() / 1000; const errors: Error[] = []; + const fork = chain.config.getForkName(chain.clock.currentSlot); await Promise.all( signedAggregateAndProofs.map(async (signedAggregateAndProof, i) => { try { // TODO: Validate in batch // eslint-disable-next-line @typescript-eslint/explicit-function-return-type - const validateFn = () => validateApiAggregateAndProof(chain, signedAggregateAndProof); + const validateFn = () => validateApiAggregateAndProof(fork, chain, signedAggregateAndProof); const {slot, beaconBlockRoot} = signedAggregateAndProof.message.aggregate.data; // when a validator is configured with multiple beacon node urls, this attestation may come from another beacon node // and the block hasn't been in our forkchoice since we haven't seen / processing that block diff --git a/packages/beacon-node/src/chain/errors/attestationError.ts b/packages/beacon-node/src/chain/errors/attestationError.ts index fa380d048c7e..a93f5b42e439 100644 --- a/packages/beacon-node/src/chain/errors/attestationError.ts +++ b/packages/beacon-node/src/chain/errors/attestationError.ts @@ -154,7 +154,7 @@ export type AttestationErrorType = | {code: AttestationErrorCode.NOT_EXACTLY_ONE_AGGREGATION_BIT_SET} | {code: AttestationErrorCode.PRIOR_ATTESTATION_KNOWN; validatorIndex: ValidatorIndex; epoch: Epoch} | {code: AttestationErrorCode.FUTURE_EPOCH; attestationEpoch: Epoch; currentEpoch: Epoch} - | {code: AttestationErrorCode.PAST_EPOCH; attestationEpoch: Epoch; currentEpoch: Epoch} + | {code: AttestationErrorCode.PAST_EPOCH; attestationEpoch: Epoch; previousEpoch: Epoch} | {code: AttestationErrorCode.ATTESTS_TO_FUTURE_BLOCK; block: Slot; attestation: Slot} | {code: AttestationErrorCode.INVALID_SUBNET_ID; received: number; expected: number} | {code: AttestationErrorCode.WRONG_NUMBER_OF_AGGREGATION_BITS} diff --git a/packages/beacon-node/src/chain/validation/aggregateAndProof.ts b/packages/beacon-node/src/chain/validation/aggregateAndProof.ts index aa60140e2d9a..0cd96a8278ec 100644 --- a/packages/beacon-node/src/chain/validation/aggregateAndProof.ts +++ b/packages/beacon-node/src/chain/validation/aggregateAndProof.ts @@ -1,4 +1,5 @@ import {toHexString} from "@chainsafe/ssz"; +import {ForkName} from "@lodestar/params"; import {phase0, RootHex, ssz, ValidatorIndex} from "@lodestar/types"; import { computeEpochAtSlot, @@ -26,23 +27,29 @@ export type AggregateAndProofValidationResult = { }; export async function validateApiAggregateAndProof( + fork: ForkName, chain: IBeaconChain, signedAggregateAndProof: phase0.SignedAggregateAndProof ): Promise { const skipValidationKnownAttesters = true; const prioritizeBls = true; - return validateAggregateAndProof(chain, signedAggregateAndProof, null, {skipValidationKnownAttesters, prioritizeBls}); + return validateAggregateAndProof(fork, chain, signedAggregateAndProof, null, { + skipValidationKnownAttesters, + prioritizeBls, + }); } export async function validateGossipAggregateAndProof( + fork: ForkName, chain: IBeaconChain, signedAggregateAndProof: phase0.SignedAggregateAndProof, serializedData: Uint8Array ): Promise { - return validateAggregateAndProof(chain, signedAggregateAndProof, serializedData); + return validateAggregateAndProof(fork, chain, signedAggregateAndProof, serializedData); } async function validateAggregateAndProof( + fork: ForkName, chain: IBeaconChain, signedAggregateAndProof: phase0.SignedAggregateAndProof, serializedData: Uint8Array | null = null, @@ -87,7 +94,7 @@ async function validateAggregateAndProof( // [IGNORE] aggregate.data.slot is within the last ATTESTATION_PROPAGATION_SLOT_RANGE slots (with a MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance) // -- i.e. aggregate.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= aggregate.data.slot // (a client MAY queue future aggregates for processing at the appropriate slot). - verifyPropagationSlotRange(chain, attSlot); + verifyPropagationSlotRange(fork, chain, attSlot); } // [IGNORE] The aggregate is the first valid aggregate received for the aggregator with diff --git a/packages/beacon-node/src/chain/validation/attestation.ts b/packages/beacon-node/src/chain/validation/attestation.ts index 3a17b3d4a0aa..bea4d060e48a 100644 --- a/packages/beacon-node/src/chain/validation/attestation.ts +++ b/packages/beacon-node/src/chain/validation/attestation.ts @@ -1,7 +1,7 @@ import {toHexString} from "@chainsafe/ssz"; import {phase0, Epoch, Root, Slot, RootHex, ssz} from "@lodestar/types"; import {ProtoBlock} from "@lodestar/fork-choice"; -import {ATTESTATION_SUBNET_COUNT, SLOTS_PER_EPOCH} from "@lodestar/params"; +import {ATTESTATION_SUBNET_COUNT, SLOTS_PER_EPOCH, ForkName, ForkSeq} from "@lodestar/params"; import { computeEpochAtSlot, CachedBeaconStateAllForks, @@ -56,12 +56,13 @@ const SHUFFLING_LOOK_AHEAD_EPOCHS = 1; * - do not prioritize bls signature set */ export async function validateGossipAttestation( + fork: ForkName, chain: IBeaconChain, attestationOrBytes: GossipAttestation, /** Optional, to allow verifying attestations through API with unknown subnet */ subnet: number ): Promise { - return validateAttestation(chain, attestationOrBytes, subnet); + return validateAttestation(fork, chain, attestationOrBytes, subnet); } /** @@ -71,11 +72,12 @@ export async function validateGossipAttestation( * - prioritize bls signature set */ export async function validateApiAttestation( + fork: ForkName, chain: IBeaconChain, attestationOrBytes: ApiAttestation ): Promise { const prioritizeBls = true; - return validateAttestation(chain, attestationOrBytes, null, prioritizeBls); + return validateAttestation(fork, chain, attestationOrBytes, null, prioritizeBls); } /** @@ -83,6 +85,7 @@ export async function validateApiAttestation( * This is to avoid deserializing similar attestation multiple times which could help the gc */ async function validateAttestation( + fork: ForkName, chain: IBeaconChain, attestationOrBytes: AttestationOrBytes, /** Optional, to allow verifying attestations through API with unknown subnet */ @@ -146,7 +149,7 @@ async function validateAttestation( // [IGNORE] attestation.data.slot is within the last ATTESTATION_PROPAGATION_SLOT_RANGE slots (within a MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance) // -- i.e. attestation.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= attestation.data.slot // (a client MAY queue future attestations for processing at the appropriate slot). - verifyPropagationSlotRange(chain, attestationOrCache.attestation.data.slot); + verifyPropagationSlotRange(fork, chain, attestationOrCache.attestation.data.slot); } // [REJECT] The attestation is unaggregated -- that is, it has exactly one participating validator @@ -343,22 +346,9 @@ async function validateAttestation( * Accounts for `MAXIMUM_GOSSIP_CLOCK_DISPARITY`. * Note: We do not queue future attestations for later processing */ -export function verifyPropagationSlotRange(chain: IBeaconChain, attestationSlot: Slot): void { +export function verifyPropagationSlotRange(fork: ForkName, chain: IBeaconChain, attestationSlot: Slot): void { // slot with future tolerance of MAXIMUM_GOSSIP_CLOCK_DISPARITY_SEC const latestPermissibleSlot = chain.clock.slotWithFutureTolerance(MAXIMUM_GOSSIP_CLOCK_DISPARITY_SEC); - const earliestPermissibleSlot = Math.max( - // slot with past tolerance of MAXIMUM_GOSSIP_CLOCK_DISPARITY_SEC - // ATTESTATION_PROPAGATION_SLOT_RANGE = SLOTS_PER_EPOCH - chain.clock.slotWithPastTolerance(MAXIMUM_GOSSIP_CLOCK_DISPARITY_SEC) - SLOTS_PER_EPOCH, - 0 - ); - if (attestationSlot < earliestPermissibleSlot) { - throw new AttestationError(GossipAction.IGNORE, { - code: AttestationErrorCode.PAST_SLOT, - earliestPermissibleSlot, - attestationSlot, - }); - } if (attestationSlot > latestPermissibleSlot) { throw new AttestationError(GossipAction.IGNORE, { code: AttestationErrorCode.FUTURE_SLOT, @@ -366,6 +356,49 @@ export function verifyPropagationSlotRange(chain: IBeaconChain, attestationSlot: attestationSlot, }); } + + const earliestPermissibleSlot = Math.max( + // slot with past tolerance of MAXIMUM_GOSSIP_CLOCK_DISPARITY_SEC + // ATTESTATION_PROPAGATION_SLOT_RANGE = SLOTS_PER_EPOCH + chain.clock.slotWithPastTolerance(MAXIMUM_GOSSIP_CLOCK_DISPARITY_SEC) - SLOTS_PER_EPOCH, + 0 + ); + + // Post deneb the attestations are valid for current as well as previous epoch + // while pre deneb they are valid for ATTESTATION_PROPAGATION_SLOT_RANGE + // + // see: https://github.com/ethereum/consensus-specs/pull/3360 + if (ForkSeq[fork] < ForkSeq.deneb) { + if (attestationSlot < earliestPermissibleSlot) { + throw new AttestationError(GossipAction.IGNORE, { + code: AttestationErrorCode.PAST_SLOT, + earliestPermissibleSlot, + attestationSlot, + }); + } + } else { + const attestationEpoch = computeEpochAtSlot(attestationSlot); + + // upper bound for current epoch is same as epoch of latestPermissibleSlot + const latestPermissibleCurrentEpoch = computeEpochAtSlot(latestPermissibleSlot); + if (attestationEpoch > latestPermissibleCurrentEpoch) { + throw new AttestationError(GossipAction.IGNORE, { + code: AttestationErrorCode.FUTURE_EPOCH, + currentEpoch: latestPermissibleCurrentEpoch, + attestationEpoch, + }); + } + + // lower bound for previous epoch is same as epoch of earliestPermissibleSlot + const earliestPermissiblePreviousEpoch = computeEpochAtSlot(earliestPermissibleSlot); + if (attestationEpoch < earliestPermissiblePreviousEpoch) { + throw new AttestationError(GossipAction.IGNORE, { + code: AttestationErrorCode.PAST_EPOCH, + previousEpoch: earliestPermissiblePreviousEpoch, + attestationEpoch, + }); + } + } } /** diff --git a/packages/beacon-node/src/network/processor/gossipHandlers.ts b/packages/beacon-node/src/network/processor/gossipHandlers.ts index e3692b7fb4a3..58d4a9ea3c60 100644 --- a/packages/beacon-node/src/network/processor/gossipHandlers.ts +++ b/packages/beacon-node/src/network/processor/gossipHandlers.ts @@ -192,9 +192,10 @@ export function getGossipHandlers(modules: ValidatorFnsModules, options: GossipH [GossipType.beacon_aggregate_and_proof]: async ({serializedData}, topic, _peer, seenTimestampSec) => { let validationResult: AggregateAndProofValidationResult; const signedAggregateAndProof = sszDeserialize(topic, serializedData); + const {fork} = topic; try { - validationResult = await validateGossipAggregateAndProof(chain, signedAggregateAndProof, serializedData); + validationResult = await validateGossipAggregateAndProof(fork, chain, signedAggregateAndProof, serializedData); } catch (e) { if (e instanceof AttestationError && e.action === GossipAction.REJECT) { chain.persistInvalidSszValue(ssz.phase0.SignedAggregateAndProof, signedAggregateAndProof, "gossip_reject"); @@ -229,14 +230,17 @@ export function getGossipHandlers(modules: ValidatorFnsModules, options: GossipH chain.emitter.emit(routes.events.EventType.attestation, signedAggregateAndProof.message.aggregate); }, - [GossipType.beacon_attestation]: async ({serializedData, msgSlot}, {subnet}, _peer, seenTimestampSec) => { + [GossipType.beacon_attestation]: async ({serializedData, msgSlot}, topic, _peer, seenTimestampSec) => { if (msgSlot == undefined) { throw Error("msgSlot is undefined for beacon_attestation topic"); } + const {subnet, fork} = topic; + // do not deserialize gossipSerializedData here, it's done in validateGossipAttestation only if needed let validationResult: AttestationValidationResult; try { validationResult = await validateGossipAttestation( + fork, chain, {attestation: null, serializedData, attSlot: msgSlot}, subnet diff --git a/packages/beacon-node/test/perf/chain/validation/aggregateAndProof.test.ts b/packages/beacon-node/test/perf/chain/validation/aggregateAndProof.test.ts index fe8ade59cef2..3fc7efe9d675 100644 --- a/packages/beacon-node/test/perf/chain/validation/aggregateAndProof.test.ts +++ b/packages/beacon-node/test/perf/chain/validation/aggregateAndProof.test.ts @@ -26,7 +26,8 @@ describe("validate gossip signedAggregateAndProof", () => { chain.seenAggregatedAttestations["aggregateRootsByEpoch"].clear(); }, fn: async () => { - await validateApiAggregateAndProof(chain, agg); + const fork = chain.config.getForkName(stateSlot); + await validateApiAggregateAndProof(fork, chain, agg); }, }); @@ -37,7 +38,8 @@ describe("validate gossip signedAggregateAndProof", () => { chain.seenAggregatedAttestations["aggregateRootsByEpoch"].clear(); }, fn: async () => { - await validateGossipAggregateAndProof(chain, agg, serializedData); + const fork = chain.config.getForkName(stateSlot); + await validateGossipAggregateAndProof(fork, chain, agg, serializedData); }, }); } 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 b9664c6f5efd..84b423dd7e62 100644 --- a/packages/beacon-node/test/perf/chain/validation/attestation.test.ts +++ b/packages/beacon-node/test/perf/chain/validation/attestation.test.ts @@ -23,7 +23,8 @@ describe("validate attestation", () => { id: `validate api attestation - ${id}`, beforeEach: () => chain.seenAttesters["validatorIndexesByEpoch"].clear(), fn: async () => { - await validateApiAttestation(chain, {attestation: att, serializedData: null}); + const fork = chain.config.getForkName(stateSlot); + await validateApiAttestation(fork, chain, {attestation: att, serializedData: null}); }, }); @@ -31,7 +32,8 @@ describe("validate attestation", () => { id: `validate gossip attestation - ${id}`, beforeEach: () => chain.seenAttesters["validatorIndexesByEpoch"].clear(), fn: async () => { - await validateGossipAttestation(chain, {attestation: null, serializedData, attSlot: slot}, subnet); + const fork = chain.config.getForkName(stateSlot); + await validateGossipAttestation(fork, chain, {attestation: null, serializedData, attSlot: slot}, subnet); }, }); } diff --git a/packages/beacon-node/test/spec/specTestVersioning.ts b/packages/beacon-node/test/spec/specTestVersioning.ts index 2e967e087c69..37f08e3f9e56 100644 --- a/packages/beacon-node/test/spec/specTestVersioning.ts +++ b/packages/beacon-node/test/spec/specTestVersioning.ts @@ -15,7 +15,7 @@ import {DownloadTestsOptions} from "@lodestar/spec-test-util"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); export const ethereumConsensusSpecsTests: DownloadTestsOptions = { - specVersion: "v1.4.0-alpha.3", + specVersion: "v1.4.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/beacon-node/test/unit/chain/validation/aggregateAndProof.test.ts b/packages/beacon-node/test/unit/chain/validation/aggregateAndProof.test.ts index 54191ba34846..20300776a2ec 100644 --- a/packages/beacon-node/test/unit/chain/validation/aggregateAndProof.test.ts +++ b/packages/beacon-node/test/unit/chain/validation/aggregateAndProof.test.ts @@ -42,7 +42,8 @@ describe("chain / validation / aggregateAndProof", () => { it("Valid", async () => { const {chain, signedAggregateAndProof} = getValidData({}); - await validateApiAggregateAndProof(chain, signedAggregateAndProof); + const fork = chain.config.getForkName(stateSlot); + await validateApiAggregateAndProof(fork, chain, signedAggregateAndProof); }); it("BAD_TARGET_EPOCH", async () => { @@ -188,9 +189,10 @@ describe("chain / validation / aggregateAndProof", () => { signedAggregateAndProof: phase0.SignedAggregateAndProof, errorCode: AttestationErrorCode ): Promise { + const fork = chain.config.getForkName(stateSlot); const serializedData = ssz.phase0.SignedAggregateAndProof.serialize(signedAggregateAndProof); await expectRejectedWithLodestarError( - validateGossipAggregateAndProof(chain, signedAggregateAndProof, serializedData), + validateGossipAggregateAndProof(fork, chain, signedAggregateAndProof, serializedData), errorCode ); } diff --git a/packages/beacon-node/test/unit/chain/validation/attestation.test.ts b/packages/beacon-node/test/unit/chain/validation/attestation.test.ts index 6296ecb4c6fc..f28f62abc229 100644 --- a/packages/beacon-node/test/unit/chain/validation/attestation.test.ts +++ b/packages/beacon-node/test/unit/chain/validation/attestation.test.ts @@ -52,7 +52,8 @@ describe("chain / validation / attestation", () => { it("Valid", async () => { const {chain, attestation} = getValidData(); - await validateApiAttestation(chain, {attestation, serializedData: null}); + const fork = chain.config.getForkName(stateSlot); + await validateApiAttestation(fork, chain, {attestation, serializedData: null}); }); it("INVALID_SERIALIZED_BYTES_ERROR_CODE", async () => { @@ -276,7 +277,8 @@ describe("chain / validation / attestation", () => { attestationOrBytes: ApiAttestation, errorCode: string ): Promise { - await expectRejectedWithLodestarError(validateApiAttestation(chain, attestationOrBytes), errorCode); + const fork = chain.config.getForkName(stateSlot); + await expectRejectedWithLodestarError(validateApiAttestation(fork, chain, attestationOrBytes), errorCode); } async function expectGossipError( @@ -285,7 +287,11 @@ describe("chain / validation / attestation", () => { subnet: number, errorCode: string ): Promise { - await expectRejectedWithLodestarError(validateGossipAttestation(chain, attestationOrBytes, subnet), errorCode); + const fork = chain.config.getForkName(stateSlot); + await expectRejectedWithLodestarError( + validateGossipAttestation(fork, chain, attestationOrBytes, subnet), + errorCode + ); } }); diff --git a/packages/state-transition/src/block/processAttestationPhase0.ts b/packages/state-transition/src/block/processAttestationPhase0.ts index 6e7ae506dc93..248ba83b4ed2 100644 --- a/packages/state-transition/src/block/processAttestationPhase0.ts +++ b/packages/state-transition/src/block/processAttestationPhase0.ts @@ -1,7 +1,7 @@ import {toHexString} from "@chainsafe/ssz"; -import {phase0, ssz} from "@lodestar/types"; +import {Slot, phase0, ssz} from "@lodestar/types"; -import {MIN_ATTESTATION_INCLUSION_DELAY, SLOTS_PER_EPOCH} from "@lodestar/params"; +import {MIN_ATTESTATION_INCLUSION_DELAY, SLOTS_PER_EPOCH, ForkSeq} from "@lodestar/params"; import {computeEpochAtSlot} from "../util/index.js"; import {CachedBeaconStatePhase0, CachedBeaconStateAllForks} from "../types.js"; import {isValidIndexedAttestation} from "./index.js"; @@ -22,7 +22,7 @@ export function processAttestationPhase0( const slot = state.slot; const data = attestation.data; - validateAttestation(state, attestation); + validateAttestation(ForkSeq.phase0, state, attestation); const pendingAttestation = ssz.phase0.PendingAttestation.toViewDU({ data: data, @@ -56,7 +56,11 @@ export function processAttestationPhase0( } } -export function validateAttestation(state: CachedBeaconStateAllForks, attestation: phase0.Attestation): void { +export function validateAttestation( + fork: ForkSeq, + state: CachedBeaconStateAllForks, + attestation: phase0.Attestation +): void { const {epochCtx} = state; const slot = state.slot; const data = attestation.data; @@ -80,7 +84,9 @@ export function validateAttestation(state: CachedBeaconStateAllForks, attestatio `targetEpoch=${data.target.epoch} computedEpoch=${computedEpoch}` ); } - if (!(data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= slot && slot <= data.slot + SLOTS_PER_EPOCH)) { + + // post deneb, the attestations are valid till end of next epoch + if (!(data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= slot && isTimelyTarget(fork, slot - data.slot))) { throw new Error( "Attestation slot not within inclusion window: " + `slot=${data.slot} window=${data.slot + MIN_ATTESTATION_INCLUSION_DELAY}..${data.slot + SLOTS_PER_EPOCH}` @@ -96,6 +102,16 @@ export function validateAttestation(state: CachedBeaconStateAllForks, attestatio } } +// Modified https://github.com/ethereum/consensus-specs/pull/3360 +export function isTimelyTarget(fork: ForkSeq, inclusionDistance: Slot): boolean { + // post deneb attestation is valid till end of next epoch for target + if (fork >= ForkSeq.deneb) { + return true; + } else { + return inclusionDistance <= SLOTS_PER_EPOCH; + } +} + export function checkpointToStr(checkpoint: phase0.Checkpoint): string { return `${toHexString(checkpoint.root)}:${checkpoint.epoch}`; } diff --git a/packages/state-transition/src/block/processAttestations.ts b/packages/state-transition/src/block/processAttestations.ts index a2976e6491c3..2b132fa22e0b 100644 --- a/packages/state-transition/src/block/processAttestations.ts +++ b/packages/state-transition/src/block/processAttestations.ts @@ -18,6 +18,6 @@ export function processAttestations( processAttestationPhase0(state as CachedBeaconStatePhase0, attestation, verifySignatures); } } else { - processAttestationsAltair(state as CachedBeaconStateAltair, attestations, verifySignatures); + processAttestationsAltair(fork, state as CachedBeaconStateAltair, attestations, verifySignatures); } } diff --git a/packages/state-transition/src/block/processAttestationsAltair.ts b/packages/state-transition/src/block/processAttestationsAltair.ts index a77a6d1de0bd..cabe28b88b27 100644 --- a/packages/state-transition/src/block/processAttestationsAltair.ts +++ b/packages/state-transition/src/block/processAttestationsAltair.ts @@ -13,12 +13,13 @@ import { TIMELY_TARGET_FLAG_INDEX, TIMELY_TARGET_WEIGHT, WEIGHT_DENOMINATOR, + ForkSeq, } from "@lodestar/params"; import {increaseBalance, verifySignatureSet} from "../util/index.js"; import {CachedBeaconStateAltair} from "../types.js"; import {RootCache} from "../util/rootCache.js"; import {getAttestationWithIndicesSignatureSet} from "../signatureSets/indexedAttestation.js"; -import {checkpointToStr, validateAttestation} from "./processAttestationPhase0.js"; +import {checkpointToStr, isTimelyTarget, validateAttestation} from "./processAttestationPhase0.js"; const PROPOSER_REWARD_DOMINATOR = ((WEIGHT_DENOMINATOR - PROPOSER_WEIGHT) * WEIGHT_DENOMINATOR) / PROPOSER_WEIGHT; @@ -29,6 +30,7 @@ const TIMELY_HEAD = 1 << TIMELY_HEAD_FLAG_INDEX; const SLOTS_PER_EPOCH_SQRT = intSqrt(SLOTS_PER_EPOCH); export function processAttestationsAltair( + fork: ForkSeq, state: CachedBeaconStateAltair, attestations: phase0.Attestation[], verifySignature = true @@ -44,7 +46,7 @@ export function processAttestationsAltair( for (const attestation of attestations) { const data = attestation.data; - validateAttestation(state, attestation); + validateAttestation(fork, state, attestation); // Retrieve the validator indices from the attestation participation bitfield const committeeIndices = epochCtx.getBeaconCommittee(data.slot, data.index); @@ -63,7 +65,13 @@ export function processAttestationsAltair( const inCurrentEpoch = data.target.epoch === currentEpoch; const epochParticipation = inCurrentEpoch ? state.currentEpochParticipation : state.previousEpochParticipation; - const flagsAttestation = getAttestationParticipationStatus(data, stateSlot - data.slot, epochCtx.epoch, rootCache); + const flagsAttestation = getAttestationParticipationStatus( + fork, + data, + stateSlot - data.slot, + epochCtx.epoch, + rootCache + ); // For each participant, update their participation // In epoch processing, this participation info is used to calculate balance updates @@ -121,6 +129,7 @@ export function processAttestationsAltair( * https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/altair/beacon-chain.md#get_attestation_participation_flag_indices */ export function getAttestationParticipationStatus( + fork: ForkSeq, data: phase0.AttestationData, inclusionDelay: number, currentEpoch: Epoch, @@ -152,7 +161,7 @@ export function getAttestationParticipationStatus( let flags = 0; if (isMatchingSource && inclusionDelay <= SLOTS_PER_EPOCH_SQRT) flags |= TIMELY_SOURCE; - if (isMatchingTarget && inclusionDelay <= SLOTS_PER_EPOCH) flags |= TIMELY_TARGET; + if (isMatchingTarget && isTimelyTarget(fork, inclusionDelay)) flags |= TIMELY_TARGET; if (isMatchingHead && inclusionDelay === MIN_ATTESTATION_INCLUSION_DELAY) flags |= TIMELY_HEAD; return flags; diff --git a/packages/state-transition/src/slot/upgradeStateToAltair.ts b/packages/state-transition/src/slot/upgradeStateToAltair.ts index a7af38c8f068..0afa43930ef0 100644 --- a/packages/state-transition/src/slot/upgradeStateToAltair.ts +++ b/packages/state-transition/src/slot/upgradeStateToAltair.ts @@ -1,5 +1,6 @@ import {CompositeViewDU} from "@chainsafe/ssz"; import {ssz} from "@lodestar/types"; +import {ForkSeq} from "@lodestar/params"; import {CachedBeaconStatePhase0, CachedBeaconStateAltair} from "../types.js"; import {newZeroedArray, RootCache} from "../util/index.js"; import {getNextSyncCommittee} from "../util/syncCommittee.js"; @@ -128,6 +129,7 @@ function translateParticipation( for (const attestation of pendingAttesations.getAllReadonly()) { const data = attestation.data; const attestationFlags = getAttestationParticipationStatus( + ForkSeq.altair, data, attestation.inclusionDelay, epochCtx.epoch, diff --git a/packages/state-transition/test/perf/block/processAttestation.test.ts b/packages/state-transition/test/perf/block/processAttestation.test.ts index d1dea1ff15bd..673b0e17430f 100644 --- a/packages/state-transition/test/perf/block/processAttestation.test.ts +++ b/packages/state-transition/test/perf/block/processAttestation.test.ts @@ -72,7 +72,12 @@ describe("altair processAttestation", () => { return {state: stateCloned, attestations}; }, fn: ({state, attestations}) => { - processAttestationsAltair(state as CachedBeaconStateAltair, attestations, false); + processAttestationsAltair( + state.config.getForkSeq(state.slot), + state as CachedBeaconStateAltair, + attestations, + false + ); state.commit(); // After processAttestations normal case vc 250_000 it has to do 6802 hash64 ops // state.hashTreeRoot();