Skip to content

Commit

Permalink
feat: implement partial spec changes for devnet-5 (#7229)
Browse files Browse the repository at this point in the history
  • Loading branch information
ensi321 committed Dec 14, 2024
1 parent 6578287 commit 81d2cbd
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 46 deletions.
27 changes: 21 additions & 6 deletions packages/state-transition/src/block/processAttestationPhase0.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,15 +100,30 @@ export function validateAttestation(fork: ForkSeq, state: CachedBeaconStateAllFo
);
}

// Get total number of attestation participant of every committee specified
const participantCount = committeeIndices
.map((committeeIndex) => epochCtx.getBeaconCommittee(data.slot, committeeIndex).length)
.reduce((acc, committeeSize) => acc + committeeSize, 0);
const validatorsByCommittee = epochCtx.getBeaconCommittees(data.slot, committeeIndices);
const aggregationBitsArray = attestationElectra.aggregationBits.toBoolArray();

// Total number of attestation participants of every committee specified
let committeeOffset = 0;
for (const committeeValidators of validatorsByCommittee) {
const committeeAggregationBits = aggregationBitsArray.slice(
committeeOffset,
committeeOffset + committeeValidators.length
);

// Assert aggregation bits in this committee have at least one true bit
if (committeeAggregationBits.every((bit) => !bit)) {
throw new Error("Every committee in aggregation bits must have at least one attester");
}

committeeOffset += committeeValidators.length;
}

// Bitfield length matches total number of participants
assert.equal(
attestationElectra.aggregationBits.bitLen,
participantCount,
`Attestation aggregation bits length does not match total number of committee participant aggregationBitsLength=${attestation.aggregationBits.bitLen} participantCount=${participantCount}`
committeeOffset,
`Attestation aggregation bits length does not match total number of committee participants aggregationBitsLength=${attestation.aggregationBits.bitLen} participantCount=${committeeOffset}`
);
} else {
if (!(data.index < committeeCount)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {CachedBeaconStateElectra} from "../types.js";
import {hasEth1WithdrawalCredential} from "../util/capella.js";
import {hasExecutionWithdrawalCredential, isPubkeyKnown, switchToCompoundingValidator} from "../util/electra.js";
import {computeConsolidationEpochAndUpdateChurn} from "../util/epoch.js";
import {getConsolidationChurnLimit, isActiveValidator} from "../util/validator.js";
import {getConsolidationChurnLimit, getPendingBalanceToWithdraw, isActiveValidator} from "../util/validator.js";

// TODO Electra: Clean up necessary as there is a lot of overlap with isValidSwitchToCompoundRequest
export function processConsolidationRequest(
Expand Down Expand Up @@ -71,6 +71,16 @@ export function processConsolidationRequest(
return;
}

// Verify the source has been active long enough
if (currentEpoch < sourceValidator.activationEpoch + state.config.SHARD_COMMITTEE_PERIOD) {
return;
}

// Verify the source has no pending withdrawals in the queue
if (getPendingBalanceToWithdraw(state, sourceIndex) > 0) {
return;
}

// TODO Electra: See if we can get rid of big int
const exitEpoch = computeConsolidationEpochAndUpdateChurn(state, BigInt(sourceValidator.effectiveBalance));
sourceValidator.exitEpoch = exitEpoch;
Expand Down
24 changes: 15 additions & 9 deletions packages/state-transition/src/block/processWithdrawals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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`);
Expand All @@ -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;
Expand Down Expand Up @@ -140,7 +141,7 @@ export function getExpectedWithdrawals(
});
withdrawalIndex++;
}
partialWithdrawalsCount++;
processedPartialWithdrawalsCount++;
}
}

Expand All @@ -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)
Expand Down Expand Up @@ -193,5 +199,5 @@ export function getExpectedWithdrawals(
}
}

return {withdrawals, sampledValidators: n, partialWithdrawalsCount};
return {withdrawals, sampledValidators: n, processedPartialWithdrawalsCount};
}
37 changes: 16 additions & 21 deletions packages/state-transition/src/cache/epochCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -748,13 +748,13 @@ export class EpochCache {
* Return the beacon committee at slot for index.
*/
getBeaconCommittee(slot: Slot, index: CommitteeIndex): Uint32Array {
return this.getBeaconCommittees(slot, [index]);
return this.getBeaconCommittees(slot, [index])[0];
}

/**
* Return a single Uint32Array representing concatted committees of indices
* Return a Uint32Array[] representing committees of indices
*/
getBeaconCommittees(slot: Slot, indices: CommitteeIndex[]): Uint32Array {
getBeaconCommittees(slot: Slot, indices: CommitteeIndex[]): Uint32Array[] {
if (indices.length === 0) {
throw new Error("Attempt to get committees without providing CommitteeIndex");
}
Expand All @@ -773,22 +773,7 @@ export class EpochCache {
committees.push(slotCommittees[index]);
}

// Early return if only one index
if (committees.length === 1) {
return committees[0];
}

// Create a new Uint32Array to flatten `committees`
const totalLength = committees.reduce((acc, curr) => acc + curr.length, 0);
const result = new Uint32Array(totalLength);

let offset = 0;
for (const committee of committees) {
result.set(committee, offset);
offset += committee.length;
}

return result;
return committees;
}

getCommitteeCountPerSlot(epoch: Epoch): number {
Expand Down Expand Up @@ -911,9 +896,19 @@ export class EpochCache {
// TODO Electra: resolve the naming conflicts
const committeeIndices = committeeBits.getTrueBitIndexes();

const validatorIndices = this.getBeaconCommittees(data.slot, committeeIndices);
const validatorsByCommittee = this.getBeaconCommittees(data.slot, committeeIndices);

// Create a new Uint32Array to flatten `validatorsByCommittee`
const totalLength = validatorsByCommittee.reduce((acc, curr) => acc + curr.length, 0);
const committeeValidators = new Uint32Array(totalLength);

let offset = 0;
for (const committee of validatorsByCommittee) {
committeeValidators.set(committee, offset);
offset += committee.length;
}

return aggregationBits.intersectValues(validatorIndices);
return aggregationBits.intersectValues(committeeValidators);
}

getCommitteeAssignments(
Expand Down
14 changes: 5 additions & 9 deletions packages/state-transition/src/slot/upgradeStateToElectra.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ export function upgradeStateToElectra(stateDeneb: CachedBeaconStateDeneb): Cache
stateElectraView.exitBalanceToConsume = BigInt(0);

const validatorsArr = stateElectraView.validators.getAllReadonly();
const exitEpochs: Epoch[] = [];
const currentEpochPre = stateDeneb.epochCtx.epoch;
let earliestExitEpoch = computeActivationExitEpoch(currentEpochPre);

// [EIP-7251]: add validators that are not yet active to pending balance deposits
const preActivation: ValidatorIndex[] = [];
Expand All @@ -65,17 +66,12 @@ export function upgradeStateToElectra(stateDeneb: CachedBeaconStateDeneb): Cache
if (activationEpoch === FAR_FUTURE_EPOCH) {
preActivation.push(validatorIndex);
}
if (exitEpoch !== FAR_FUTURE_EPOCH) {
exitEpochs.push(exitEpoch);
if (exitEpoch !== FAR_FUTURE_EPOCH && exitEpoch > earliestExitEpoch) {
earliestExitEpoch = exitEpoch;
}
}

const currentEpochPre = stateDeneb.epochCtx.epoch;

if (exitEpochs.length === 0) {
exitEpochs.push(currentEpochPre);
}
stateElectraView.earliestExitEpoch = Math.max(...exitEpochs) + 1;
stateElectraView.earliestExitEpoch = earliestExitEpoch + 1;
stateElectraView.consolidationBalanceToConsume = BigInt(0);
stateElectraView.earliestConsolidationEpoch = computeActivationExitEpoch(currentEpochPre);
// TODO-electra: can we improve this?
Expand Down

0 comments on commit 81d2cbd

Please sign in to comment.