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 EIP-7716 - anti-correlation penalty #6851

Draft
wants to merge 2 commits into
base: unstable
Choose a base branch
from
Draft
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
9 changes: 9 additions & 0 deletions packages/config/src/chainConfig/configs/mainnet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ export const chainConfig: ChainConfig = {
DENEB_FORK_VERSION: b("0x04000000"),
DENEB_FORK_EPOCH: 269568, // March 13, 2024, 01:55:35pm UTC

// EIP-7716
EIP7716_FORK_VERSION: b("0x05000000"),
EIP7716_FORK_EPOCH: Infinity,

// Time parameters
// ---------------------------------------------------------------
// 12 seconds
Expand Down Expand Up @@ -98,4 +102,9 @@ export const chainConfig: ChainConfig = {
// Deneb
// `2**12` (= 4096 epochs, ~18 days)
MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: 4096,

// eip7716
PENALTY_ADJUSTMENT_FACTOR: 4096,
MAX_PENALTY_FACTOR: 4,
PENALTY_RECOVERY_RATE: 1,
};
8 changes: 8 additions & 0 deletions packages/config/src/chainConfig/configs/minimal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ export const chainConfig: ChainConfig = {
// Deneb
DENEB_FORK_VERSION: b("0x04000001"),
DENEB_FORK_EPOCH: Infinity,
// EIP-7716
EIP7716_FORK_VERSION: b("0x05000000"),
EIP7716_FORK_EPOCH: Infinity,

// Time parameters
// ---------------------------------------------------------------
Expand Down Expand Up @@ -96,4 +99,9 @@ export const chainConfig: ChainConfig = {
// Deneb
// `2**12` (= 4096 epochs, ~18 days)
MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: 4096,

// eip7716
PENALTY_ADJUSTMENT_FACTOR: 4096,
MAX_PENALTY_FACTOR: 4,
PENALTY_RECOVERY_RATE: 1,
};
16 changes: 16 additions & 0 deletions packages/config/src/chainConfig/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ export type ChainConfig = {
// DENEB
DENEB_FORK_VERSION: Uint8Array;
DENEB_FORK_EPOCH: number;
// EIP-7716
EIP7716_FORK_VERSION: Uint8Array;
EIP7716_FORK_EPOCH: number;

// Time parameters
SECONDS_PER_SLOT: number;
Expand Down Expand Up @@ -69,6 +72,11 @@ export type ChainConfig = {

// Networking
MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: number;

// EIP-7716
PENALTY_ADJUSTMENT_FACTOR: number;
MAX_PENALTY_FACTOR: number;
PENALTY_RECOVERY_RATE: number;
};

export const chainConfigTypes: SpecTypes<ChainConfig> = {
Expand Down Expand Up @@ -99,6 +107,9 @@ export const chainConfigTypes: SpecTypes<ChainConfig> = {
// DENEB
DENEB_FORK_VERSION: "bytes",
DENEB_FORK_EPOCH: "number",
// EIP-7716
EIP7716_FORK_VERSION: "bytes",
EIP7716_FORK_EPOCH: "number",

// Time parameters
SECONDS_PER_SLOT: "number",
Expand Down Expand Up @@ -128,6 +139,11 @@ export const chainConfigTypes: SpecTypes<ChainConfig> = {

// Networking
MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: "number",

// EIP-7716
PENALTY_ADJUSTMENT_FACTOR: "number",
MAX_PENALTY_FACTOR: "number",
PENALTY_RECOVERY_RATE: "number",
};

/** Allows values in a Spec file */
Expand Down
10 changes: 9 additions & 1 deletion packages/config/src/forkConfig/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,18 @@ export function createForkConfig(config: ChainConfig): ForkConfig {
prevVersion: config.CAPELLA_FORK_VERSION,
prevForkName: ForkName.capella,
};
const eip7716: ForkInfo = {
name: ForkName.eip7716,
seq: ForkSeq.eip7716,
epoch: config.EIP7716_FORK_EPOCH,
version: config.EIP7716_FORK_VERSION,
prevVersion: config.DENEB_FORK_VERSION,
prevForkName: ForkName.deneb,
};

/** Forks in order order of occurence, `phase0` first */
// Note: Downstream code relies on proper ordering.
const forks = {phase0, altair, bellatrix, capella, deneb};
const forks = {phase0, altair, bellatrix, capella, deneb, eip7716};

// Prevents allocating an array on every getForkInfo() call
const forksAscendingEpochOrder = Object.values(forks);
Expand Down
2 changes: 2 additions & 0 deletions packages/params/src/forkName.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export enum ForkName {
bellatrix = "bellatrix",
capella = "capella",
deneb = "deneb",
eip7716 = "eip7716",
}

/**
Expand All @@ -18,6 +19,7 @@ export enum ForkSeq {
bellatrix = 2,
capella = 3,
deneb = 4,
eip7716 = 5,
}

export type ForkPreLightClient = ForkName.phase0;
Expand Down
2 changes: 2 additions & 0 deletions packages/state-transition/src/cache/stateCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
BeaconStateBellatrix,
BeaconStateCapella,
BeaconStateDeneb,
BeaconStateEip7716,
} from "./types.js";
import {RewardCache, createEmptyRewardCache} from "./rewardCache.js";

Expand Down Expand Up @@ -130,6 +131,7 @@ export type CachedBeaconStateAltair = CachedBeaconState<BeaconStateAltair>;
export type CachedBeaconStateBellatrix = CachedBeaconState<BeaconStateBellatrix>;
export type CachedBeaconStateCapella = CachedBeaconState<BeaconStateCapella>;
export type CachedBeaconStateDeneb = CachedBeaconState<BeaconStateDeneb>;
export type CachedBeaconStateEIP7716 = CachedBeaconState<BeaconStateEip7716>;

export type CachedBeaconStateAllForks = CachedBeaconState<BeaconStateAllForks>;
export type CachedBeaconStateExecutions = CachedBeaconState<BeaconStateExecutions>;
Expand Down
6 changes: 4 additions & 2 deletions packages/state-transition/src/cache/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export type BeaconStateAltair = CompositeViewDU<typeof ssz.altair.BeaconState>;
export type BeaconStateBellatrix = CompositeViewDU<typeof ssz.bellatrix.BeaconState>;
export type BeaconStateCapella = CompositeViewDU<typeof ssz.capella.BeaconState>;
export type BeaconStateDeneb = CompositeViewDU<typeof ssz.deneb.BeaconState>;
export type BeaconStateEip7716 = CompositeViewDU<typeof ssz.eip7716.BeaconState>;

// Union at the TreeViewDU level
// - Works well as function argument and as generic type for allForks functions
Expand All @@ -18,8 +19,9 @@ export type BeaconStateAllForks =
| BeaconStateAltair
| BeaconStateBellatrix
| BeaconStateCapella
| BeaconStateDeneb;
| BeaconStateDeneb
| BeaconStateEip7716;

export type BeaconStateExecutions = BeaconStateBellatrix | BeaconStateCapella | BeaconStateDeneb;
export type BeaconStateExecutions = BeaconStateBellatrix | BeaconStateCapella | BeaconStateDeneb | BeaconStateEip7716;

export type ShufflingGetter = (shufflingEpoch: Epoch, dependentRoot: RootHex) => EpochShuffling | null;
6 changes: 6 additions & 0 deletions packages/state-transition/src/epoch/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
EpochTransitionCache,
} from "../types.js";
import {BeaconStateTransitionMetrics} from "../metrics.js";
import {CachedBeaconStateEIP7716} from "../cache/stateCache.js";
import {processEffectiveBalanceUpdates} from "./processEffectiveBalanceUpdates.js";
import {processEth1DataReset} from "./processEth1DataReset.js";
import {processHistoricalRootsUpdate} from "./processHistoricalRootsUpdate.js";
Expand All @@ -27,6 +28,7 @@ import {processRewardsAndPenalties} from "./processRewardsAndPenalties.js";
import {processSlashings} from "./processSlashings.js";
import {processSlashingsReset} from "./processSlashingsReset.js";
import {processSyncCommitteeUpdates} from "./processSyncCommitteeUpdates.js";
import {processNetExcessPenalties} from "./processNetExcessPenalties.js";

// For spec tests
export {getRewardsAndPenalties} from "./processRewardsAndPenalties.js";
Expand All @@ -45,6 +47,7 @@ export {
processParticipationFlagUpdates,
processSyncCommitteeUpdates,
processHistoricalSummariesUpdate,
processNetExcessPenalties,
};

export {computeUnrealizedCheckpoints} from "./computeUnrealizedCheckpoints.js";
Expand Down Expand Up @@ -156,4 +159,7 @@ export function processEpoch(
timer?.();
}
}
if (fork === ForkSeq.eip7716) {
processNetExcessPenalties(state as CachedBeaconStateEIP7716);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import {PARTICIPATION_FLAG_WEIGHTS, SLOTS_PER_EPOCH} from "@lodestar/params";
import {CachedBeaconStateEIP7716} from "../types.js";
import {getPreviousEpoch} from "../util/epoch.js";
import {computePenaltyFactor} from "../util/eip7716.js";

export function processNetExcessPenalties(state: CachedBeaconStateEIP7716): void {
const lastSlotPrevEpoch = getPreviousEpoch(state) + SLOTS_PER_EPOCH - 1;
for (let flagIndex = 0; flagIndex < PARTICIPATION_FLAG_WEIGHTS.length; flagIndex++) {
const {netExcessPenalty} = computePenaltyFactor(state, lastSlotPrevEpoch, flagIndex);
state.netExcessPenalties.set(flagIndex, netExcessPenalty);
}
}
2 changes: 2 additions & 0 deletions packages/state-transition/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export type {
CachedBeaconStateBellatrix,
CachedBeaconStateCapella,
CachedBeaconStateDeneb,
CachedBeaconStateEIP7716,
} from "./cache/stateCache.js";

export type {
Expand All @@ -19,4 +20,5 @@ export type {
BeaconStateBellatrix,
BeaconStateCapella,
BeaconStateDeneb,
BeaconStateEip7716,
} from "./cache/types.js";
79 changes: 79 additions & 0 deletions packages/state-transition/src/util/eip7716.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import {Epoch, Slot, ValidatorIndex} from "@lodestar/types";
import {
SLOTS_PER_EPOCH,
} from "@lodestar/params";
import {CachedBeaconStateEIP7716} from "../types.js";
import {computeEpochAtSlot, computeStartSlotAtEpoch} from "./epoch.js";

export function computePenaltyFactor(
state: CachedBeaconStateEIP7716,
atSlot: Slot,
flagIndex: number
): {penaltyFactor: number; netExcessPenalty: number} {
let netExcessPenalty = state.netExcessPenalties.get(flagIndex);
const epoch = computeEpochAtSlot(atSlot);
const {PENALTY_ADJUSTMENT_FACTOR, MAX_PENALTY_FACTOR, PENALTY_RECOVERY_RATE} = state.config;
let penaltyFactor = 1;

for (let slot = computeStartSlotAtEpoch(epoch); slot < atSlot; slot++) {
const totalBalance = getSlotCommitteeBalance(state, slot);
const participatingBalance = participatingBalanceSlot(state, slot, flagIndex);
penaltyFactor = Math.min(
Math.floor(
((totalBalance - participatingBalance) * PENALTY_ADJUSTMENT_FACTOR) / (netExcessPenalty * totalBalance + 1)
),
MAX_PENALTY_FACTOR
);
netExcessPenalty = Math.max(PENALTY_RECOVERY_RATE, netExcessPenalty + penaltyFactor) - PENALTY_RECOVERY_RATE;
}

return {penaltyFactor, netExcessPenalty};
}

export function committeeSlotOfValidator(state: CachedBeaconStateEIP7716, index: ValidatorIndex, epoch: Epoch) {
for (let slot = epoch * SLOTS_PER_EPOCH; slot < (epoch + 1) * SLOTS_PER_EPOCH; slot++) {
if (index in getSlotCommittees(state, slot)) {
return slot;
}
}
throw new Error(`Validator with index ${index} is not active`);
}

export function participatingBalanceSlot(state: CachedBeaconStateEIP7716, slot: Slot, flagIndex: number) {
const inCurrentEpoch = computeEpochAtSlot(slot) === state.epochCtx.epoch;
const epochParticipation = inCurrentEpoch ? state.currentEpochParticipation : state.previousEpochParticipation;

const flagBit = 1 << flagIndex;
const participatingIndices = getSlotCommittees(state, slot).filter(
(index) => (epochParticipation.get(index) & flagBit) === flagBit
);

return participatingIndices
.map((participatingIndex) => state.balances.get(participatingIndex))
.reduce((total, balance) => total + balance, 0);
}

export function getSlotCommittees(state: CachedBeaconStateEIP7716, slot: Slot): Uint32Array {
const committees = state.epochCtx.getShufflingAtSlot(slot).committees[slot % SLOTS_PER_EPOCH].flat();
// 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;
}

export function getSlotCommitteeBalance(state: CachedBeaconStateEIP7716, slot: Slot): number {
const validatorIndices = getSlotCommittees(state, slot);

// 32eth * 1mil / 32 is still within number range
// WIth maxEB = 2048, total max balance could be 2^46
return validatorIndices
.map((validatorIndex) => state.balances.get(validatorIndex))
.reduce((total, balance) => total + balance, 0);
}
38 changes: 38 additions & 0 deletions packages/types/src/allForks/sszTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {ssz as altair} from "../altair/index.js";
import {ssz as bellatrix} from "../bellatrix/index.js";
import {ssz as capella} from "../capella/index.js";
import {ssz as deneb} from "../deneb/index.js";
import {ssz as eip7716} from "../eip7716/index.js";

/**
* Index the ssz types that differ by fork
Expand Down Expand Up @@ -44,6 +45,13 @@ export const allForks = {
BeaconState: deneb.BeaconState,
Metadata: altair.Metadata,
},
eip7716: {
BeaconBlockBody: deneb.BeaconBlockBody,
BeaconBlock: deneb.BeaconBlock,
SignedBeaconBlock: deneb.SignedBeaconBlock,
Metadata: altair.Metadata,
BeaconState: eip7716.BeaconState,
},
};

/**
Expand Down Expand Up @@ -85,6 +93,17 @@ export const allForksExecution = {
SignedBuilderBid: deneb.SignedBuilderBid,
SSEPayloadAttributes: deneb.SSEPayloadAttributes,
},
eip7716: {
BeaconBlockBody: deneb.BeaconBlockBody,
BeaconBlock: deneb.BeaconBlock,
SignedBeaconBlock: deneb.SignedBeaconBlock,
ExecutionPayload: deneb.ExecutionPayload,
ExecutionPayloadHeader: deneb.ExecutionPayloadHeader,
BuilderBid: deneb.BuilderBid,
SignedBuilderBid: deneb.SignedBuilderBid,
SSEPayloadAttributes: deneb.SSEPayloadAttributes,
BeaconState: eip7716.BeaconState,
},
};

/**
Expand All @@ -107,6 +126,11 @@ export const allForksBlinded = {
BeaconBlock: deneb.BlindedBeaconBlock,
SignedBeaconBlock: deneb.SignedBlindedBeaconBlock,
},
eip7716: {
BeaconBlockBody: deneb.BlindedBeaconBlockBody,
BeaconBlock: deneb.BlindedBeaconBlock,
SignedBeaconBlock: deneb.SignedBlindedBeaconBlock,
},
};

export const allForksLightClient = {
Expand Down Expand Up @@ -150,11 +174,25 @@ export const allForksLightClient = {
LightClientOptimisticUpdate: deneb.LightClientOptimisticUpdate,
LightClientStore: deneb.LightClientStore,
},
eip7716: {
BeaconBlock: deneb.BeaconBlock,
BeaconBlockBody: deneb.BeaconBlockBody,
LightClientHeader: deneb.LightClientHeader,
LightClientBootstrap: deneb.LightClientBootstrap,
LightClientUpdate: deneb.LightClientUpdate,
LightClientFinalityUpdate: deneb.LightClientFinalityUpdate,
LightClientOptimisticUpdate: deneb.LightClientOptimisticUpdate,
LightClientStore: deneb.LightClientStore,
},
};

export const allForksBlobs = {
deneb: {
BlobSidecar: deneb.BlobSidecar,
ExecutionPayloadAndBlobsBundle: deneb.ExecutionPayloadAndBlobsBundle,
},
eip7716: {
BlobSidecar: deneb.BlobSidecar,
ExecutionPayloadAndBlobsBundle: deneb.ExecutionPayloadAndBlobsBundle,
},
};
Loading
Loading