From 355db63ce5b0f2f3a20a7e9f9c92ad36716bc4e4 Mon Sep 17 00:00:00 2001 From: NC <17676176+ensi321@users.noreply.github.com> Date: Sun, 17 Nov 2024 16:50:46 +0700 Subject: [PATCH 1/5] init commit --- .../src/block/processAttestationPhase0.ts | 24 +++++++++--- .../src/block/processConsolidationRequest.ts | 12 +++++- .../src/block/processWithdrawals.ts | 24 +++++++----- .../state-transition/src/cache/epochCache.ts | 37 ++++++++----------- 4 files changed, 60 insertions(+), 37 deletions(-) diff --git a/packages/state-transition/src/block/processAttestationPhase0.ts b/packages/state-transition/src/block/processAttestationPhase0.ts index ab57bd27c80d..abf70a9706be 100644 --- a/packages/state-transition/src/block/processAttestationPhase0.ts +++ b/packages/state-transition/src/block/processAttestationPhase0.ts @@ -100,15 +100,27 @@ export function validateAttestation(fork: ForkSeq, state: CachedBeaconStateAllFo ); } - // Get total number of attestation participant of every committee specified - const participantCount = committeeIndices - .map((committeeIndex) => epochCtx.getBeaconCommittee(data.slot, committeeIndex).length) - .reduce((acc, committeeSize) => acc + committeeSize, 0); + const validatorsByCommittee = epochCtx.getBeaconCommittees(data.slot, committeeIndices); + const aggregationBitsArray = attestationElectra.aggregationBits.toBoolArray(); + // Total number of attestation participant of every committee specified + let committeeOffset = 0; + for (const committee of validatorsByCommittee) { + const committeeAggregationBits = aggregationBitsArray.slice(committeeOffset, committee.length); + + // Assert aggregation bits in this committee have at least one true bit + if (committeeAggregationBits.every((bit) => !bit)) { + throw new Error("Every committee in committee bits must have at least one attester"); + } + + committeeOffset += committee.length; + } + + // Bitfield length matches total number of participants assert.equal( attestationElectra.aggregationBits.bitLen, - participantCount, - `Attestation aggregation bits length does not match total number of committee participant aggregationBitsLength=${attestation.aggregationBits.bitLen} participantCount=${participantCount}` + committeeOffset, + `Attestation aggregation bits length does not match total number of committee participant aggregationBitsLength=${attestation.aggregationBits.bitLen} participantCount=${committeeOffset}` ); } else { if (!(data.index < committeeCount)) { diff --git a/packages/state-transition/src/block/processConsolidationRequest.ts b/packages/state-transition/src/block/processConsolidationRequest.ts index d0650135d0c6..f0e2da3f3571 100644 --- a/packages/state-transition/src/block/processConsolidationRequest.ts +++ b/packages/state-transition/src/block/processConsolidationRequest.ts @@ -5,7 +5,7 @@ import {CachedBeaconStateElectra} from "../types.js"; import {hasEth1WithdrawalCredential} from "../util/capella.js"; import {hasExecutionWithdrawalCredential, switchToCompoundingValidator} from "../util/electra.js"; import {computeConsolidationEpochAndUpdateChurn} from "../util/epoch.js"; -import {getConsolidationChurnLimit, isActiveValidator} from "../util/validator.js"; +import {getConsolidationChurnLimit, getPendingBalanceToWithdraw, isActiveValidator} from "../util/validator.js"; // TODO Electra: Clean up necessary as there is a lot of overlap with isValidSwitchToCompoundRequest export function processConsolidationRequest( @@ -67,6 +67,16 @@ export function processConsolidationRequest( return; } + // Verify the source has been active long enough + if (currentEpoch < sourceValidator.activationEpoch + state.config.SHARD_COMMITTEE_PERIOD) { + return; + } + + // Verify the source has no pending withdrawals in the queue + if (getPendingBalanceToWithdraw(state, sourceIndex) > 0) { + return; + } + // TODO Electra: See if we can get rid of big int const exitEpoch = computeConsolidationEpochAndUpdateChurn(state, BigInt(sourceValidator.effectiveBalance)); sourceValidator.exitEpoch = exitEpoch; diff --git a/packages/state-transition/src/block/processWithdrawals.ts b/packages/state-transition/src/block/processWithdrawals.ts index ab1df570eb30..7f9ab6aa53f1 100644 --- a/packages/state-transition/src/block/processWithdrawals.ts +++ b/packages/state-transition/src/block/processWithdrawals.ts @@ -25,9 +25,8 @@ export function processWithdrawals( state: CachedBeaconStateCapella | CachedBeaconStateElectra, payload: capella.FullOrBlindedExecutionPayload ): void { - // partialWithdrawalsCount is withdrawals coming from EL since electra (EIP-7002) - // TODO - electra: may switch to executionWithdrawalsCount - const {withdrawals: expectedWithdrawals, partialWithdrawalsCount} = getExpectedWithdrawals(fork, state); + // processedPartialWithdrawalsCount is withdrawals coming from EL since electra (EIP-7002) + const {withdrawals: expectedWithdrawals, processedPartialWithdrawalsCount} = getExpectedWithdrawals(fork, state); const numWithdrawals = expectedWithdrawals.length; if (isCapellaPayloadHeader(payload)) { @@ -59,7 +58,9 @@ export function processWithdrawals( if (fork >= ForkSeq.electra) { const stateElectra = state as CachedBeaconStateElectra; - stateElectra.pendingPartialWithdrawals = stateElectra.pendingPartialWithdrawals.sliceFrom(partialWithdrawalsCount); + stateElectra.pendingPartialWithdrawals = stateElectra.pendingPartialWithdrawals.sliceFrom( + processedPartialWithdrawalsCount + ); } // Update the nextWithdrawalIndex @@ -87,7 +88,7 @@ export function getExpectedWithdrawals( ): { withdrawals: capella.Withdrawal[]; sampledValidators: number; - partialWithdrawalsCount: number; + processedPartialWithdrawalsCount: number; } { if (fork < ForkSeq.capella) { throw new Error(`getExpectedWithdrawals not supported at forkSeq=${fork} < ForkSeq.capella`); @@ -100,7 +101,7 @@ export function getExpectedWithdrawals( const withdrawals: capella.Withdrawal[] = []; const isPostElectra = fork >= ForkSeq.electra; // partialWithdrawalsCount is withdrawals coming from EL since electra (EIP-7002) - let partialWithdrawalsCount = 0; + let processedPartialWithdrawalsCount = 0; if (isPostElectra) { const stateElectra = state as CachedBeaconStateElectra; @@ -140,7 +141,7 @@ export function getExpectedWithdrawals( }); withdrawalIndex++; } - partialWithdrawalsCount++; + processedPartialWithdrawalsCount++; } } @@ -151,9 +152,14 @@ export function getExpectedWithdrawals( for (n = 0; n < bound; n++) { // Get next validator in turn const validatorIndex = (nextWithdrawalValidatorIndex + n) % validators.length; + const partiallyWithdrawnBalance = withdrawals + .filter((withdrawal) => withdrawal.validatorIndex === validatorIndex) + .reduce((acc, withdrawal) => acc + Number(withdrawal.amount), 0); const validator = validators.getReadonly(validatorIndex); - const balance = balances.get(validatorIndex); + const balance = isPostElectra + ? balances.get(validatorIndex) - partiallyWithdrawnBalance + : balances.get(validatorIndex); const {withdrawableEpoch, withdrawalCredentials, effectiveBalance} = validator; const hasWithdrawableCredentials = isPostElectra ? hasExecutionWithdrawalCredential(withdrawalCredentials) @@ -193,5 +199,5 @@ export function getExpectedWithdrawals( } } - return {withdrawals, sampledValidators: n, partialWithdrawalsCount}; + return {withdrawals, sampledValidators: n, processedPartialWithdrawalsCount}; } diff --git a/packages/state-transition/src/cache/epochCache.ts b/packages/state-transition/src/cache/epochCache.ts index 86e63c672024..90b4a3550ab4 100644 --- a/packages/state-transition/src/cache/epochCache.ts +++ b/packages/state-transition/src/cache/epochCache.ts @@ -747,13 +747,13 @@ export class EpochCache { * Return the beacon committee at slot for index. */ getBeaconCommittee(slot: Slot, index: CommitteeIndex): Uint32Array { - return this.getBeaconCommittees(slot, [index]); + return this.getBeaconCommittees(slot, [index])[0]; } /** - * Return a single Uint32Array representing concatted committees of indices + * Return a Uint32Array[] representing committees of indices */ - getBeaconCommittees(slot: Slot, indices: CommitteeIndex[]): Uint32Array { + getBeaconCommittees(slot: Slot, indices: CommitteeIndex[]): Uint32Array[] { if (indices.length === 0) { throw new Error("Attempt to get committees without providing CommitteeIndex"); } @@ -772,22 +772,7 @@ export class EpochCache { committees.push(slotCommittees[index]); } - // Early return if only one index - if (committees.length === 1) { - return committees[0]; - } - - // Create a new Uint32Array to flatten `committees` - const totalLength = committees.reduce((acc, curr) => acc + curr.length, 0); - const result = new Uint32Array(totalLength); - - let offset = 0; - for (const committee of committees) { - result.set(committee, offset); - offset += committee.length; - } - - return result; + return committees; } getCommitteeCountPerSlot(epoch: Epoch): number { @@ -910,9 +895,19 @@ export class EpochCache { // TODO Electra: resolve the naming conflicts const committeeIndices = committeeBits.getTrueBitIndexes(); - const validatorIndices = this.getBeaconCommittees(data.slot, committeeIndices); + const validatorsByCommittee = this.getBeaconCommittees(data.slot, committeeIndices); + + // Create a new Uint32Array to flatten `validatorsByCommittee` + const totalLength = validatorsByCommittee.reduce((acc, curr) => acc + curr.length, 0); + const committeeValidators = new Uint32Array(totalLength); + + let offset = 0; + for (const committee of validatorsByCommittee) { + committeeValidators.set(committee, offset); + offset += committee.length; + } - return aggregationBits.intersectValues(validatorIndices); + return aggregationBits.intersectValues(committeeValidators); } getCommitteeAssignments( From ebd4839d5cd910c2754c8f42fe8da12dd3e23d93 Mon Sep 17 00:00:00 2001 From: NC <17676176+ensi321@users.noreply.github.com> Date: Mon, 2 Dec 2024 15:00:11 -0800 Subject: [PATCH 2/5] Apply suggestions from code review Co-authored-by: Nico Flaig --- .../state-transition/src/block/processAttestationPhase0.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/state-transition/src/block/processAttestationPhase0.ts b/packages/state-transition/src/block/processAttestationPhase0.ts index abf70a9706be..877e26aa6575 100644 --- a/packages/state-transition/src/block/processAttestationPhase0.ts +++ b/packages/state-transition/src/block/processAttestationPhase0.ts @@ -103,14 +103,14 @@ export function validateAttestation(fork: ForkSeq, state: CachedBeaconStateAllFo const validatorsByCommittee = epochCtx.getBeaconCommittees(data.slot, committeeIndices); const aggregationBitsArray = attestationElectra.aggregationBits.toBoolArray(); - // Total number of attestation participant of every committee specified + // Total number of attestation participants of every committee specified let committeeOffset = 0; for (const committee of validatorsByCommittee) { const committeeAggregationBits = aggregationBitsArray.slice(committeeOffset, committee.length); // Assert aggregation bits in this committee have at least one true bit if (committeeAggregationBits.every((bit) => !bit)) { - throw new Error("Every committee in committee bits must have at least one attester"); + throw new Error("Every committee in aggregation bits must have at least one attester"); } committeeOffset += committee.length; @@ -120,7 +120,7 @@ export function validateAttestation(fork: ForkSeq, state: CachedBeaconStateAllFo assert.equal( attestationElectra.aggregationBits.bitLen, committeeOffset, - `Attestation aggregation bits length does not match total number of committee participant aggregationBitsLength=${attestation.aggregationBits.bitLen} participantCount=${committeeOffset}` + `Attestation aggregation bits length does not match total number of committee participants aggregationBitsLength=${attestation.aggregationBits.bitLen} participantCount=${committeeOffset}` ); } else { if (!(data.index < committeeCount)) { From 79a814cb3ecfd55fa9915e2824403cc1dcf57bc6 Mon Sep 17 00:00:00 2001 From: NC <17676176+ensi321@users.noreply.github.com> Date: Mon, 2 Dec 2024 15:17:14 -0800 Subject: [PATCH 3/5] Fix aggregation bit check --- .../state-transition/src/block/processAttestationPhase0.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/state-transition/src/block/processAttestationPhase0.ts b/packages/state-transition/src/block/processAttestationPhase0.ts index 877e26aa6575..ac2e0e3ea20d 100644 --- a/packages/state-transition/src/block/processAttestationPhase0.ts +++ b/packages/state-transition/src/block/processAttestationPhase0.ts @@ -105,15 +105,15 @@ export function validateAttestation(fork: ForkSeq, state: CachedBeaconStateAllFo // Total number of attestation participants of every committee specified let committeeOffset = 0; - for (const committee of validatorsByCommittee) { - const committeeAggregationBits = aggregationBitsArray.slice(committeeOffset, committee.length); + for (const committeeValidators of validatorsByCommittee) { + const committeeAggregationBits = aggregationBitsArray.slice(committeeOffset, committeeOffset + committeeValidators.length); // Assert aggregation bits in this committee have at least one true bit if (committeeAggregationBits.every((bit) => !bit)) { throw new Error("Every committee in aggregation bits must have at least one attester"); } - committeeOffset += committee.length; + committeeOffset += committeeValidators.length; } // Bitfield length matches total number of participants From d3fdb52dfa1d0e370b6c16c2058a099657646bda Mon Sep 17 00:00:00 2001 From: NC <17676176+ensi321@users.noreply.github.com> Date: Mon, 2 Dec 2024 15:50:29 -0800 Subject: [PATCH 4/5] Fix earliestExitEpoch --- packages/state-transition/src/slot/upgradeStateToElectra.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/state-transition/src/slot/upgradeStateToElectra.ts b/packages/state-transition/src/slot/upgradeStateToElectra.ts index f030f9d572fe..e09b4169d180 100644 --- a/packages/state-transition/src/slot/upgradeStateToElectra.ts +++ b/packages/state-transition/src/slot/upgradeStateToElectra.ts @@ -57,6 +57,7 @@ export function upgradeStateToElectra(stateDeneb: CachedBeaconStateDeneb): Cache const validatorsArr = stateElectraView.validators.getAllReadonly(); const exitEpochs: Epoch[] = []; + const currentEpochPre = stateDeneb.epochCtx.epoch; // [EIP-7251]: add validators that are not yet active to pending balance deposits const preActivation: ValidatorIndex[] = []; @@ -70,10 +71,9 @@ export function upgradeStateToElectra(stateDeneb: CachedBeaconStateDeneb): Cache } } - const currentEpochPre = stateDeneb.epochCtx.epoch; if (exitEpochs.length === 0) { - exitEpochs.push(currentEpochPre); + exitEpochs.push(computeActivationExitEpoch(currentEpochPre)); } stateElectraView.earliestExitEpoch = Math.max(...exitEpochs) + 1; stateElectraView.consolidationBalanceToConsume = BigInt(0); From 75480a3e19076f87449fc01743747223735ce524 Mon Sep 17 00:00:00 2001 From: NC <17676176+ensi321@users.noreply.github.com> Date: Mon, 2 Dec 2024 16:12:55 -0800 Subject: [PATCH 5/5] Fix earliestExitEpoch --- .../src/block/processAttestationPhase0.ts | 5 ++++- .../src/slot/upgradeStateToElectra.ts | 12 ++++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/packages/state-transition/src/block/processAttestationPhase0.ts b/packages/state-transition/src/block/processAttestationPhase0.ts index ac2e0e3ea20d..3a44d11e7afc 100644 --- a/packages/state-transition/src/block/processAttestationPhase0.ts +++ b/packages/state-transition/src/block/processAttestationPhase0.ts @@ -106,7 +106,10 @@ export function validateAttestation(fork: ForkSeq, state: CachedBeaconStateAllFo // Total number of attestation participants of every committee specified let committeeOffset = 0; for (const committeeValidators of validatorsByCommittee) { - const committeeAggregationBits = aggregationBitsArray.slice(committeeOffset, committeeOffset + committeeValidators.length); + const committeeAggregationBits = aggregationBitsArray.slice( + committeeOffset, + committeeOffset + committeeValidators.length + ); // Assert aggregation bits in this committee have at least one true bit if (committeeAggregationBits.every((bit) => !bit)) { diff --git a/packages/state-transition/src/slot/upgradeStateToElectra.ts b/packages/state-transition/src/slot/upgradeStateToElectra.ts index e09b4169d180..3aae0f5b487e 100644 --- a/packages/state-transition/src/slot/upgradeStateToElectra.ts +++ b/packages/state-transition/src/slot/upgradeStateToElectra.ts @@ -56,8 +56,8 @@ export function upgradeStateToElectra(stateDeneb: CachedBeaconStateDeneb): Cache stateElectraView.exitBalanceToConsume = BigInt(0); const validatorsArr = stateElectraView.validators.getAllReadonly(); - const exitEpochs: Epoch[] = []; const currentEpochPre = stateDeneb.epochCtx.epoch; + let earliestExitEpoch = computeActivationExitEpoch(currentEpochPre); // [EIP-7251]: add validators that are not yet active to pending balance deposits const preActivation: ValidatorIndex[] = []; @@ -66,16 +66,12 @@ export function upgradeStateToElectra(stateDeneb: CachedBeaconStateDeneb): Cache if (activationEpoch === FAR_FUTURE_EPOCH) { preActivation.push(validatorIndex); } - if (exitEpoch !== FAR_FUTURE_EPOCH) { - exitEpochs.push(exitEpoch); + if (exitEpoch !== FAR_FUTURE_EPOCH && exitEpoch > earliestExitEpoch) { + earliestExitEpoch = exitEpoch; } } - - if (exitEpochs.length === 0) { - exitEpochs.push(computeActivationExitEpoch(currentEpochPre)); - } - stateElectraView.earliestExitEpoch = Math.max(...exitEpochs) + 1; + stateElectraView.earliestExitEpoch = earliestExitEpoch + 1; stateElectraView.consolidationBalanceToConsume = BigInt(0); stateElectraView.earliestConsolidationEpoch = computeActivationExitEpoch(currentEpochPre); // TODO-electra: can we improve this?