@lodestar/beacon-node
Version:
A Typescript implementation of the beacon chain
112 lines • 6.95 kB
JavaScript
import { EFFECTIVE_BALANCE_INCREMENT, ForkName, INACTIVITY_PENALTY_QUOTIENT_ALTAIR, MAX_EFFECTIVE_BALANCE, MAX_EFFECTIVE_BALANCE_ELECTRA, PARTICIPATION_FLAG_WEIGHTS, TIMELY_HEAD_FLAG_INDEX, TIMELY_SOURCE_FLAG_INDEX, TIMELY_TARGET_FLAG_INDEX, WEIGHT_DENOMINATOR, isForkPostElectra, } from "@lodestar/params";
import { FLAG_ELIGIBLE_ATTESTER, FLAG_PREV_HEAD_ATTESTER_UNSLASHED, FLAG_PREV_SOURCE_ATTESTER_UNSLASHED, FLAG_PREV_TARGET_ATTESTER_UNSLASHED, beforeProcessEpoch, hasMarkers, isInInactivityLeak, } from "@lodestar/state-transition";
import { fromHex } from "@lodestar/utils";
const defaultAttestationsReward = { head: 0, target: 0, source: 0, inclusionDelay: 0, inactivity: 0 };
const defaultAttestationsPenalty = { target: 0, source: 0 };
export async function computeAttestationsRewards(_epoch, state, _config, validatorIds) {
const fork = state.config.getForkName(state.slot);
if (fork === ForkName.phase0) {
throw Error("Unsupported fork. Attestations rewards calculation is not available in phase0");
}
const stateAltair = state;
const transitionCache = beforeProcessEpoch(stateAltair);
const [idealRewards, penalties] = computeIdealAttestationsRewardsAndPenaltiesAltair(stateAltair, transitionCache);
const totalRewards = computeTotalAttestationsRewardsAltair(stateAltair, transitionCache, idealRewards, penalties, validatorIds);
return { idealRewards, totalRewards };
}
function computeIdealAttestationsRewardsAndPenaltiesAltair(state, transitionCache) {
const baseRewardPerIncrement = transitionCache.baseRewardPerIncrement;
const activeBalanceByIncrement = transitionCache.totalActiveStakeByIncrement;
const fork = state.config.getForkName(state.slot);
const maxEffectiveBalance = isForkPostElectra(fork) ? MAX_EFFECTIVE_BALANCE_ELECTRA : MAX_EFFECTIVE_BALANCE;
const maxEffectiveBalanceByIncrement = Math.floor(maxEffectiveBalance / EFFECTIVE_BALANCE_INCREMENT);
const idealRewards = Array.from({ length: maxEffectiveBalanceByIncrement + 1 }, (_, effectiveBalanceByIncrement) => ({
...defaultAttestationsReward,
effectiveBalance: effectiveBalanceByIncrement * EFFECTIVE_BALANCE_INCREMENT,
}));
const attestationsPenalties = Array.from({ length: maxEffectiveBalanceByIncrement + 1 }, (_, effectiveBalanceByIncrement) => ({
...defaultAttestationsPenalty,
effectiveBalance: effectiveBalanceByIncrement * EFFECTIVE_BALANCE_INCREMENT,
}));
for (let i = 0; i < PARTICIPATION_FLAG_WEIGHTS.length; i++) {
const weight = PARTICIPATION_FLAG_WEIGHTS[i];
let unslashedStakeByIncrement;
let flagName;
switch (i) {
case TIMELY_SOURCE_FLAG_INDEX: {
unslashedStakeByIncrement = transitionCache.prevEpochUnslashedStake.sourceStakeByIncrement;
flagName = "source";
break;
}
case TIMELY_TARGET_FLAG_INDEX: {
unslashedStakeByIncrement = transitionCache.prevEpochUnslashedStake.targetStakeByIncrement;
flagName = "target";
break;
}
case TIMELY_HEAD_FLAG_INDEX: {
unslashedStakeByIncrement = transitionCache.prevEpochUnslashedStake.headStakeByIncrement;
flagName = "head";
break;
}
default: {
throw Error(`Unable to retrieve unslashed stake. Unknown participation flag index: ${i}`);
}
}
for (let effectiveBalanceByIncrement = 0; effectiveBalanceByIncrement <= maxEffectiveBalanceByIncrement; effectiveBalanceByIncrement++) {
const baseReward = effectiveBalanceByIncrement * baseRewardPerIncrement;
const rewardNumerator = baseReward * weight * unslashedStakeByIncrement;
// Both idealReward and penalty are rounded to nearest integer. Loss of precision is minimal as unit is gwei
const idealReward = Math.round(rewardNumerator / activeBalanceByIncrement / WEIGHT_DENOMINATOR);
const penalty = Math.round((baseReward * weight) / WEIGHT_DENOMINATOR); // Positive number indicates penalty
const idealAttestationsReward = idealRewards[effectiveBalanceByIncrement];
idealAttestationsReward[flagName] = isInInactivityLeak(state) ? 0 : idealReward; // No attestations rewards during inactivity leak
if (flagName !== "head") {
const attestationPenalty = attestationsPenalties[effectiveBalanceByIncrement];
attestationPenalty[flagName] = penalty;
}
}
}
return [idealRewards, attestationsPenalties];
}
// Same calculation as `getRewardsAndPenaltiesAltair` but returns the breakdown of rewards instead of aggregated
function computeTotalAttestationsRewardsAltair(state, transitionCache, idealRewards, penalties, validatorIds = []) {
const rewards = [];
const { flags } = transitionCache;
const { epochCtx, config } = state;
const validatorIndices = validatorIds
.map((id) => (typeof id === "number" ? id : epochCtx.pubkey2index.get(fromHex(id))))
.filter((index) => index !== undefined); // Validator indices to include in the result
const inactivityPenaltyDenominator = config.INACTIVITY_SCORE_BIAS * INACTIVITY_PENALTY_QUOTIENT_ALTAIR;
for (let i = 0; i < flags.length; i++) {
if (validatorIndices.length && !validatorIndices.includes(i)) {
continue;
}
const flag = flags[i];
if (!hasMarkers(flag, FLAG_ELIGIBLE_ATTESTER)) {
continue;
}
const effectiveBalanceIncrement = epochCtx.effectiveBalanceIncrements[i];
const currentRewards = { ...defaultAttestationsReward, validatorIndex: i };
if (hasMarkers(flag, FLAG_PREV_SOURCE_ATTESTER_UNSLASHED)) {
currentRewards.source = idealRewards[effectiveBalanceIncrement].source;
}
else {
currentRewards.source = penalties[effectiveBalanceIncrement].source * -1; // Negative reward to indicate penalty
}
if (hasMarkers(flag, FLAG_PREV_TARGET_ATTESTER_UNSLASHED)) {
currentRewards.target = idealRewards[effectiveBalanceIncrement].target;
}
else {
currentRewards.target = penalties[effectiveBalanceIncrement].target * -1;
// Also incur inactivity penalty if not voting target correctly
const inactivityPenaltyNumerator = effectiveBalanceIncrement * EFFECTIVE_BALANCE_INCREMENT * state.inactivityScores.get(i);
currentRewards.inactivity = Math.floor(inactivityPenaltyNumerator / inactivityPenaltyDenominator) * -1;
}
if (hasMarkers(flag, FLAG_PREV_HEAD_ATTESTER_UNSLASHED)) {
currentRewards.head = idealRewards[effectiveBalanceIncrement].head;
}
rewards.push(currentRewards);
}
return rewards;
}
//# sourceMappingURL=attestationsRewards.js.map