Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement partial spec changes for devnet-5 #7229

Merged
merged 5 commits into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)) {
nflaig marked this conversation as resolved.
Show resolved Hide resolved
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, 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 @@ -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;
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 @@ -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");
}
Expand All @@ -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 {
Expand Down Expand Up @@ -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(
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
Loading