From 667911942ec6e4ef45a79ea03a83736606f4ac44 Mon Sep 17 00:00:00 2001 From: naviechan Date: Tue, 5 Dec 2023 16:57:44 +0800 Subject: [PATCH 01/26] Add block rewards api --- .../api/src/beacon/routes/beacon/index.ts | 9 +- .../api/src/beacon/routes/beacon/rewards.ts | 88 +++++++++ .../beacon-node/src/api/impl/beacon/index.ts | 3 + .../src/api/impl/beacon/rewards/index.ts | 17 ++ packages/beacon-node/src/chain/chain.ts | 8 + packages/beacon-node/src/chain/interface.ts | 3 + .../src/chain/rewards/blockRewards.ts | 179 ++++++++++++++++++ packages/state-transition/src/index.ts | 2 + 8 files changed, 308 insertions(+), 1 deletion(-) create mode 100644 packages/api/src/beacon/routes/beacon/rewards.ts create mode 100644 packages/beacon-node/src/api/impl/beacon/rewards/index.ts create mode 100644 packages/beacon-node/src/chain/rewards/blockRewards.ts diff --git a/packages/api/src/beacon/routes/beacon/index.ts b/packages/api/src/beacon/routes/beacon/index.ts index 4d0c8186fd22..28937ab2b6c7 100644 --- a/packages/api/src/beacon/routes/beacon/index.ts +++ b/packages/api/src/beacon/routes/beacon/index.ts @@ -6,6 +6,7 @@ import {RoutesData, ReturnTypes, reqEmpty, ContainerData} from "../../../utils/i import * as block from "./block.js"; import * as pool from "./pool.js"; import * as state from "./state.js"; +import * as rewards from "./rewards.js"; // See /packages/api/src/routes/index.ts for reasoning and instructions to add new routes @@ -15,9 +16,11 @@ import * as state from "./state.js"; export * as block from "./block.js"; export * as pool from "./pool.js"; export * as state from "./state.js"; +export * as rewards from "./rewards.js"; export {BroadcastValidation} from "./block.js"; export type {BlockId, BlockHeaderResponse} from "./block.js"; export type {AttestationFilters} from "./pool.js"; +export type {ProposerRewardsResponse as ProposerRewards} from "./rewards.js"; // TODO: Review if re-exporting all these types is necessary export type { StateId, @@ -34,7 +37,8 @@ export type { export type Api = block.Api & pool.Api & - state.Api & { + state.Api & + rewards.Api & { getGenesis(): Promise>; }; @@ -43,6 +47,7 @@ export const routesData: RoutesData = { ...block.routesData, ...pool.routesData, ...state.routesData, + ...rewards.routesData, }; export type ReqTypes = { @@ -56,6 +61,7 @@ export function getReqSerializers(config: ChainForkConfig) { ...block.getReqSerializers(config), ...pool.getReqSerializers(), ...state.getReqSerializers(), + ...rewards.getReqSerializers(), }; } @@ -65,5 +71,6 @@ export function getReturnTypes(): ReturnTypes { ...block.getReturnTypes(), ...pool.getReturnTypes(), ...state.getReturnTypes(), + ...rewards.getReturnTypes(), }; } diff --git a/packages/api/src/beacon/routes/beacon/rewards.ts b/packages/api/src/beacon/routes/beacon/rewards.ts new file mode 100644 index 000000000000..487069120e6c --- /dev/null +++ b/packages/api/src/beacon/routes/beacon/rewards.ts @@ -0,0 +1,88 @@ +import {ContainerType} from "@chainsafe/ssz"; +import {ssz, ValidatorIndex} from "@lodestar/types"; + +import { + RoutesData, + ReturnTypes, + Schema, + ReqSerializers, + ContainerDataExecutionOptimistic, +} from "../../../utils/index.js"; +import {HttpStatusCode} from "../../../utils/client/httpStatusCode.js"; +import {ApiClientResponse} from "../../../interfaces.js"; +import {BlockId} from "./block.js"; + +// See /packages/api/src/routes/index.ts for reasoning and instructions to add new routes + +/** + * True if the response references an unverified execution payload. Optimistic information may be invalidated at + * a later time. If the field is not present, assume the False value. + */ +export type ExecutionOptimistic = boolean; + +export type ProposerRewardsResponse = { + proposerIndex: ValidatorIndex; + total: number; + attestations: number; + syncAggregate: number; + proposerSlashings: number; + attesterSlashings: number; +}; + +export type Api = { + /** + * Get block + * Returns the complete `SignedBeaconBlock` for a given block ID. + * Depending on the `Accept` header it can be returned either as JSON or SSZ-serialized bytes. + * + * @param blockId Block identifier. + * Can be one of: "head" (canonical head in node's view), "genesis", "finalized", \, \. + */ + getProposerRewards( + blockId: BlockId + ): Promise< + ApiClientResponse< + {[HttpStatusCode.OK]: {data: ProposerRewardsResponse; executionOptimistic: ExecutionOptimistic}}, + HttpStatusCode.BAD_REQUEST | HttpStatusCode.NOT_FOUND + > + >; +}; + +/** + * Define javascript values for each route + */ +export const routesData: RoutesData = { + getProposerRewards: {url: "/eth/v1/beacon/rewards/blocks/{block_id}", method: "GET"}, +}; + +export type ReqTypes = { + getProposerRewards: {params: {block_id: string}}; +}; + +export function getReqSerializers(): ReqSerializers { + return { + getProposerRewards: { + writeReq: (block_id) => ({params: {block_id: String(block_id)}}), + parseReq: ({params}) => [params.block_id], + schema: {params: {block_id: Schema.StringRequired}}, + }, + }; +} + +export function getReturnTypes(): ReturnTypes { + const ProposerRewardsResponse = new ContainerType( + { + proposerIndex: ssz.ValidatorIndex, + total: ssz.UintNum64, + attestations: ssz.UintNum64, + syncAggregate: ssz.UintNum64, + proposerSlashings: ssz.UintNum64, + attesterSlashings: ssz.UintNum64, + }, + {jsonCase: "eth2"} + ); + + return { + getProposerRewards: ContainerDataExecutionOptimistic(ProposerRewardsResponse), + }; +} diff --git a/packages/beacon-node/src/api/impl/beacon/index.ts b/packages/beacon-node/src/api/impl/beacon/index.ts index d613e3c2d394..7df6c800ac29 100644 --- a/packages/beacon-node/src/api/impl/beacon/index.ts +++ b/packages/beacon-node/src/api/impl/beacon/index.ts @@ -3,6 +3,7 @@ import {ApiModules} from "../types.js"; import {getBeaconBlockApi} from "./blocks/index.js"; import {getBeaconPoolApi} from "./pool/index.js"; import {getBeaconStateApi} from "./state/index.js"; +import { getBeaconRewardsApi } from "./rewards/index.js"; export function getBeaconApi( modules: Pick @@ -10,6 +11,7 @@ export function getBeaconApi( const block = getBeaconBlockApi(modules); const pool = getBeaconPoolApi(modules); const state = getBeaconStateApi(modules); + const rewards = getBeaconRewardsApi(modules); const {chain, config} = modules; @@ -17,6 +19,7 @@ export function getBeaconApi( ...block, ...pool, ...state, + ...rewards, async getGenesis() { return { diff --git a/packages/beacon-node/src/api/impl/beacon/rewards/index.ts b/packages/beacon-node/src/api/impl/beacon/rewards/index.ts new file mode 100644 index 000000000000..e24cf92bc8ee --- /dev/null +++ b/packages/beacon-node/src/api/impl/beacon/rewards/index.ts @@ -0,0 +1,17 @@ +import {routes, ServerApi} from "@lodestar/api"; +import {ApiModules} from "../../types.js"; +import { resolveBlockId } from "../blocks/utils.js"; + +export function getBeaconRewardsApi({ + chain, + config, +}: Pick): ServerApi { + + return { + async getProposerRewards(blockId) { + const {block, executionOptimistic} = await resolveBlockId(chain, blockId); + const data = await chain.getBlockRewards(block.message); + return {data, executionOptimistic}; + } + }; +} diff --git a/packages/beacon-node/src/chain/chain.ts b/packages/beacon-node/src/chain/chain.ts index f20bc0dbffa2..8dc4bd1d2ac6 100644 --- a/packages/beacon-node/src/chain/chain.ts +++ b/packages/beacon-node/src/chain/chain.ts @@ -76,6 +76,7 @@ import {BlockAttributes, produceBlockBody, produceCommonBlockBody} from "./produ import {computeNewStateRoot} from "./produceBlock/computeNewStateRoot.js"; import {BlockInput} from "./blocks/types.js"; import {SeenAttestationDatas} from "./seenCache/seenAttestationData.js"; +import {BlockRewards, computeBlockRewards} from "./rewards/blockRewards.js"; import {ShufflingCache} from "./shufflingCache.js"; import {StateContextCache} from "./stateCache/stateContextCache.js"; import {SeenGossipBlockInput} from "./seenCache/index.js"; @@ -991,4 +992,11 @@ export class BeaconChain implements IBeaconChain { } } } + + async getBlockRewards(block: allForks.FullOrBlindedBeaconBlock): Promise { + const preState = (await this.regen.getPreState(block, {dontTransferCache: false}, RegenCaller.restApi)).clone(); + preState.slot = block.slot; // regen.getPreState guarantees pre_state of the same epoch but not the same slot + const result = computeBlockRewards(block, preState); + return result; + } } diff --git a/packages/beacon-node/src/chain/interface.ts b/packages/beacon-node/src/chain/interface.ts index 3939457a8ac3..0e38794eef5a 100644 --- a/packages/beacon-node/src/chain/interface.ts +++ b/packages/beacon-node/src/chain/interface.ts @@ -52,6 +52,7 @@ import {AssembledBlockType, BlockAttributes, BlockType} from "./produceBlock/pro import {SeenAttestationDatas} from "./seenCache/seenAttestationData.js"; import {SeenGossipBlockInput} from "./seenCache/index.js"; import {ShufflingCache} from "./shufflingCache.js"; +import { BlockRewards } from "./rewards/blockRewards.js"; export {BlockType, type AssembledBlockType}; export {type ProposerPreparationData}; @@ -198,6 +199,8 @@ export interface IBeaconChain { regenCanAcceptWork(): boolean; blsThreadPoolCanAcceptWork(): boolean; + + getBlockRewards(blockRef: allForks.FullOrBlindedBeaconBlock): Promise; } export type SSZObjectType = diff --git a/packages/beacon-node/src/chain/rewards/blockRewards.ts b/packages/beacon-node/src/chain/rewards/blockRewards.ts new file mode 100644 index 000000000000..0c94e4034180 --- /dev/null +++ b/packages/beacon-node/src/chain/rewards/blockRewards.ts @@ -0,0 +1,179 @@ +import { + CachedBeaconStateAllForks, + CachedBeaconStateAltair, + CachedBeaconStatePhase0, + RootCache, + getAttesterSlashableIndices, +} from "@lodestar/state-transition"; +import {getAttestationParticipationStatus} from "@lodestar/state-transition"; +import {Gwei, UintNum64, ValidatorIndex, allForks, altair, phase0} from "@lodestar/types"; +import { + PROPOSER_WEIGHT, + TIMELY_HEAD_FLAG_INDEX, + TIMELY_HEAD_WEIGHT, + TIMELY_SOURCE_FLAG_INDEX, + TIMELY_SOURCE_WEIGHT, + TIMELY_TARGET_FLAG_INDEX, + TIMELY_TARGET_WEIGHT, + WEIGHT_DENOMINATOR, + ForkName, + WHISTLEBLOWER_REWARD_QUOTIENT, +} from "@lodestar/params"; + +const PROPOSER_REWARD_DOMINATOR = ((WEIGHT_DENOMINATOR - PROPOSER_WEIGHT) * WEIGHT_DENOMINATOR) / PROPOSER_WEIGHT; + +/** Same to https://github.com/ethereum/eth2.0-specs/blob/v1.1.0-alpha.5/specs/altair/beacon-chain.md#has_flag */ +const TIMELY_SOURCE = 1 << TIMELY_SOURCE_FLAG_INDEX; +const TIMELY_TARGET = 1 << TIMELY_TARGET_FLAG_INDEX; +const TIMELY_HEAD = 1 << TIMELY_HEAD_FLAG_INDEX; + +type SubRewardValue = number; // All reward values should be integer + +export type BlockRewards = { + proposerIndex: ValidatorIndex; + total: SubRewardValue; + attestations: SubRewardValue; + syncAggregate: SubRewardValue; + proposerSlashings: SubRewardValue; + attesterSlashings: SubRewardValue; +}; + +/** + * Calculate total proposer block rewards given block and the beacon state of the same slot before the block is applied + * Standard (Non MEV) rewards for proposing a block consists of: + * 1) Including attestations from (beacon) committee + * 2) Including attestations from sync committee + * 3) Reporting slashable behaviours from proposer and attester + * TODO: Return the breakdown of the rewards and other metadata to comply with the beacon rewards endpoint + */ +export async function computeBlockRewards( + block: allForks.BeaconBlock, + state: CachedBeaconStateAllForks +): Promise { + if (block.slot !== state.slot) { + throw Error(`Block slot and state slot mismatch. Block slot: ${block.slot}, state slot: ${state.slot}`); + } + + const fork = state.config.getForkName(block.slot); + const blockAttestationReward = + fork === ForkName.phase0 + ? computeBlockAttestationRewardPhase0(block as phase0.BeaconBlock, state as CachedBeaconStatePhase0) + : computeBlockAttestationRewardAltair(block as altair.BeaconBlock, state as CachedBeaconStateAltair); + const syncAggregateReward = computeSyncAggregateReward(block as altair.BeaconBlock, state as CachedBeaconStateAltair); + const blockProposerSlashingReward = computeBlockProposerSlashingReward(block, state); + const blockAttesterSlashingReward = computeBlockAttesterSlashingReward(block, state); + + const total = + blockAttestationReward + syncAggregateReward + blockProposerSlashingReward + blockAttesterSlashingReward; + + return {proposerIndex: block.proposerIndex, total, attestations: blockAttestationReward, syncAggregate: syncAggregateReward, proposerSlashings: blockProposerSlashingReward, attesterSlashings: blockAttestationReward}; +} + +/** + * TODO: Calculate rewards received by block proposer for incuding attestations. + */ +function computeBlockAttestationRewardPhase0(_block: phase0.BeaconBlock, _state: CachedBeaconStatePhase0): SubRewardValue { + throw new Error("Unsupported fork! Block attestation reward calculation is not yet available in phase0"); +} + +/** + * Calculate rewards received by block proposer for incuding attestations since Altair. Mimics `processAttestationsAltair()` + */ +function computeBlockAttestationRewardAltair(block: altair.BeaconBlock, state: CachedBeaconStateAltair): SubRewardValue { + const {epochCtx} = state; + const {effectiveBalanceIncrements} = epochCtx; + const stateSlot = state.slot; + const rootCache = new RootCache(state); + const currentEpoch = epochCtx.epoch; + const fork = state.config.getForkSeq(block.slot); + + let blockAttestationReward = 0; + + for (const attestation of block.body.attestations) { + const data = attestation.data; + + const committeeIndices = epochCtx.getBeaconCommittee(data.slot, data.index); + const attestingIndices = attestation.aggregationBits.intersectValues(committeeIndices); + + const inCurrentEpoch = data.target.epoch === currentEpoch; + const epochParticipation = inCurrentEpoch ? state.currentEpochParticipation : state.previousEpochParticipation; + + const flagsAttestation = getAttestationParticipationStatus( + fork, + data, + stateSlot - data.slot, + epochCtx.epoch, + rootCache + ); + + let totalBalanceIncrementsWithWeight = 0; + for (const index of attestingIndices) { + const flags = epochParticipation.get(index); + epochParticipation.set(index, flagsAttestation); + const flagsNewSet = ~flags & flagsAttestation; + + // Spec: + // baseReward = state.validators[index].effectiveBalance / EFFECTIVE_BALANCE_INCREMENT * baseRewardPerIncrement; + // proposerRewardNumerator += baseReward * totalWeight + let totalWeight = 0; + if ((flagsNewSet & TIMELY_SOURCE) === TIMELY_SOURCE) totalWeight += TIMELY_SOURCE_WEIGHT; + if ((flagsNewSet & TIMELY_TARGET) === TIMELY_TARGET) totalWeight += TIMELY_TARGET_WEIGHT; + if ((flagsNewSet & TIMELY_HEAD) === TIMELY_HEAD) totalWeight += TIMELY_HEAD_WEIGHT; + + if (totalWeight > 0) { + totalBalanceIncrementsWithWeight += effectiveBalanceIncrements[index] * totalWeight; + } + } + + const totalIncrements = totalBalanceIncrementsWithWeight; + const proposerRewardNumerator = totalIncrements * state.epochCtx.baseRewardPerIncrement; + blockAttestationReward += Math.floor(proposerRewardNumerator / PROPOSER_REWARD_DOMINATOR); + } + + return blockAttestationReward; +} + +function computeSyncAggregateReward(block: altair.BeaconBlock, state: CachedBeaconStateAltair): SubRewardValue { + if (block.body.syncAggregate !== undefined) { + const {syncCommitteeBits} = block.body.syncAggregate; + const {syncProposerReward} = state.epochCtx; + + return syncCommitteeBits.getTrueBitIndexes().length * Math.floor(syncProposerReward); // syncProposerReward should already be integer + } else { + return 0; // phase0 block does not have syncAggregate + } +} +/** + * Calculate rewards received by block proposer for include proposer slashings. + * All proposer slashing rewards go to block proposer and none to whistleblower as of Deneb + */ +function computeBlockProposerSlashingReward(block: allForks.BeaconBlock, state: CachedBeaconStateAllForks): SubRewardValue { + let proposerSlashingReward = 0; + + for (const proposerSlashing of block.body.proposerSlashings) { + const offendingProposerIndex = proposerSlashing.signedHeader1.message.proposerIndex; + const offendingProposerBalance = state.validators.get(offendingProposerIndex).effectiveBalance; + + proposerSlashingReward += Math.floor(offendingProposerBalance / WHISTLEBLOWER_REWARD_QUOTIENT); + } + + return proposerSlashingReward; +} + +/** + * Calculate rewards received by block proposer for include attester slashings. + * All attester slashing rewards go to block proposer and none to whistleblower as of Deneb + */ +function computeBlockAttesterSlashingReward(block: allForks.BeaconBlock, state: CachedBeaconStateAllForks): SubRewardValue { + let attesterSlashingReward = 0; + + for (const attesterSlashing of block.body.attesterSlashings) { + for (const offendingAttesterIndex of getAttesterSlashableIndices(attesterSlashing)) { + const offendingAttesterBalance = state.validators.get(offendingAttesterIndex).effectiveBalance; + + attesterSlashingReward += Math.floor(offendingAttesterBalance / WHISTLEBLOWER_REWARD_QUOTIENT); + } + } + + return attesterSlashingReward; +} diff --git a/packages/state-transition/src/index.ts b/packages/state-transition/src/index.ts index 8786c0f6e358..7080ed0bb2b3 100644 --- a/packages/state-transition/src/index.ts +++ b/packages/state-transition/src/index.ts @@ -61,3 +61,5 @@ export {ExecutionPayloadStatus, DataAvailableStatus, type BlockExternalData} fro export {becomesNewEth1Data} from "./block/processEth1Data.js"; // Withdrawals for new blocks export {getExpectedWithdrawals} from "./block/processWithdrawals.js"; + +export {getAttestationParticipationStatus} from "./block/processAttestationsAltair.js" \ No newline at end of file From f67421b0f1b92be43627727c88598f7d408e35a6 Mon Sep 17 00:00:00 2001 From: naviechan Date: Tue, 5 Dec 2023 20:29:45 +0800 Subject: [PATCH 02/26] Add test --- packages/api/test/unit/beacon/testData/beacon.ts | 7 +++++++ packages/beacon-node/src/chain/rewards/blockRewards.ts | 1 - 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/api/test/unit/beacon/testData/beacon.ts b/packages/api/test/unit/beacon/testData/beacon.ts index 7fa8368c590b..85785bc356a1 100644 --- a/packages/api/test/unit/beacon/testData/beacon.ts +++ b/packages/api/test/unit/beacon/testData/beacon.ts @@ -168,6 +168,13 @@ export const testData: GenericServerTestCases = { res: {executionOptimistic: true, data: {validators: [1300], validatorAggregates: [[1300]]}}, }, + // reward + + getProposerRewards: { + args: ["head"], + res: {executionOptimistic: true, data: {proposerIndex: 0, total: 15, attestations: 8, syncAggregate: 4, proposerSlashings: 2, attesterSlashings: 1}} + }, + // - getGenesis: { diff --git a/packages/beacon-node/src/chain/rewards/blockRewards.ts b/packages/beacon-node/src/chain/rewards/blockRewards.ts index 0c94e4034180..2abc031183c8 100644 --- a/packages/beacon-node/src/chain/rewards/blockRewards.ts +++ b/packages/beacon-node/src/chain/rewards/blockRewards.ts @@ -44,7 +44,6 @@ export type BlockRewards = { * 1) Including attestations from (beacon) committee * 2) Including attestations from sync committee * 3) Reporting slashable behaviours from proposer and attester - * TODO: Return the breakdown of the rewards and other metadata to comply with the beacon rewards endpoint */ export async function computeBlockRewards( block: allForks.BeaconBlock, From 0b030f2f87747730c5456ae922a181b40f470aad Mon Sep 17 00:00:00 2001 From: naviechan Date: Mon, 11 Dec 2023 17:01:45 +0800 Subject: [PATCH 03/26] Add unit test --- .../src/chain/rewards/blockRewards.ts | 5 +- .../unit/chain/rewards/blockRewards.test.ts | 120 ++++++++++++++++++ 2 files changed, 121 insertions(+), 4 deletions(-) create mode 100644 packages/beacon-node/test/unit/chain/rewards/blockRewards.test.ts diff --git a/packages/beacon-node/src/chain/rewards/blockRewards.ts b/packages/beacon-node/src/chain/rewards/blockRewards.ts index 2abc031183c8..83a5bc2c1887 100644 --- a/packages/beacon-node/src/chain/rewards/blockRewards.ts +++ b/packages/beacon-node/src/chain/rewards/blockRewards.ts @@ -49,9 +49,6 @@ export async function computeBlockRewards( block: allForks.BeaconBlock, state: CachedBeaconStateAllForks ): Promise { - if (block.slot !== state.slot) { - throw Error(`Block slot and state slot mismatch. Block slot: ${block.slot}, state slot: ${state.slot}`); - } const fork = state.config.getForkName(block.slot); const blockAttestationReward = @@ -65,7 +62,7 @@ export async function computeBlockRewards( const total = blockAttestationReward + syncAggregateReward + blockProposerSlashingReward + blockAttesterSlashingReward; - return {proposerIndex: block.proposerIndex, total, attestations: blockAttestationReward, syncAggregate: syncAggregateReward, proposerSlashings: blockProposerSlashingReward, attesterSlashings: blockAttestationReward}; + return {proposerIndex: block.proposerIndex, total, attestations: blockAttestationReward, syncAggregate: syncAggregateReward, proposerSlashings: blockProposerSlashingReward, attesterSlashings: blockAttesterSlashingReward}; } /** diff --git a/packages/beacon-node/test/unit/chain/rewards/blockRewards.test.ts b/packages/beacon-node/test/unit/chain/rewards/blockRewards.test.ts new file mode 100644 index 000000000000..c09fd6aacc59 --- /dev/null +++ b/packages/beacon-node/test/unit/chain/rewards/blockRewards.test.ts @@ -0,0 +1,120 @@ +import {describe, it, expect} from "vitest"; +import {SYNC_COMMITTEE_SIZE} from "@lodestar/params"; +import {ssz} from "@lodestar/types"; +import {CachedBeaconStateAllForks, DataAvailableStatus, ExecutionPayloadStatus, stateTransition} from "../../../../../state-transition"; +import { generatePerfTestCachedStateAltair, cachedStateAltairPopulateCaches } from "../../../../../state-transition/test/perf/util.js"; +// eslint-disable-next-line import/no-relative-packages +import {BlockAltairOpts, getBlockAltair} from "../../../../../state-transition/test/perf/block/util.js"; +import { computeBlockRewards } from "../../../../src/chain/rewards/blockRewards.js"; + +describe("chain / rewards / blockRewards", () => { + const testCases: {id: string; opts: BlockAltairOpts}[] = [ + { + id: "Normal case", + opts: { + proposerSlashingLen: 1, + attesterSlashingLen: 2, + attestationLen: 90, + depositsLen: 0, + voluntaryExitLen: 0, + bitsLen: 90, + syncCommitteeBitsLen: Math.round(SYNC_COMMITTEE_SIZE * 0.7), + }, + }, + { + id: "Attestation only", + opts: { + proposerSlashingLen: 0, + attesterSlashingLen: 0, + attestationLen: 90, + depositsLen: 0, + voluntaryExitLen: 0, + bitsLen: 90, + syncCommitteeBitsLen: 0, + }, + }, + { + id: "Sync aggregate only", + opts: { + proposerSlashingLen: 0, + attesterSlashingLen: 0, + attestationLen: 0, + depositsLen: 0, + voluntaryExitLen: 0, + bitsLen: 90, + syncCommitteeBitsLen: Math.round(SYNC_COMMITTEE_SIZE * 0.7), + }, + }, + { + id: "Proposer slashing only", + opts: { + proposerSlashingLen: 2, + attesterSlashingLen: 0, + attestationLen: 0, + depositsLen: 0, + voluntaryExitLen: 0, + bitsLen: 90, + syncCommitteeBitsLen: 0, + }, + }, + { + id: "Attester slashing only", + opts: { + proposerSlashingLen: 0, + attesterSlashingLen: 5, + attestationLen: 0, + depositsLen: 0, + voluntaryExitLen: 0, + bitsLen: 90, + syncCommitteeBitsLen: 0, + }, + } + ]; + + for (const {id, opts} of testCases) { + it(`${id}`, async () => { + const state = generatePerfTestCachedStateAltair(); + const block = getBlockAltair(state, opts); + // Populate permanent root caches of the block + ssz.altair.BeaconBlock.hashTreeRoot(block.message); + // Populate tree root caches of the state + state.hashTreeRoot(); + cachedStateAltairPopulateCaches(state); + const calculatedBlockReward = await computeBlockRewards(block.message, state as CachedBeaconStateAllForks); + const {proposerIndex, total, attestations, syncAggregate, proposerSlashings, attesterSlashings} = calculatedBlockReward; + + // Sanity check + expect(proposerIndex).toBe(block.message.proposerIndex); + expect(total).toBe(attestations + syncAggregate + proposerSlashings + attesterSlashings); + if (opts.syncCommitteeBitsLen === 0) { + expect(syncAggregate).toBe(0); + } + if (opts.attestationLen === 0) { + expect(attestations).toBe(0); + } + if (opts.proposerSlashingLen === 0) { + expect(proposerSlashings).toBe(0); + } + if (opts.attesterSlashingLen === 0) { + expect(attesterSlashings).toBe(0); + } + + const postState = stateTransition(state as CachedBeaconStateAllForks, block, { + executionPayloadStatus: ExecutionPayloadStatus.valid, + dataAvailableStatus: DataAvailableStatus.available, + verifyProposer: false, + verifySignatures: false, + verifyStateRoot: false, + }); + + // Cross check with rewardCache + const rewardCache = postState.proposerRewards; + expect(total).toBe(rewardCache.attestations + rewardCache.syncAggregate + rewardCache.slashing); + expect(attestations).toBe(rewardCache.attestations); + expect(syncAggregate).toBe(rewardCache.syncAggregate); + expect(proposerSlashings + attesterSlashings).toBe(rewardCache.slashing); + + }); + } + +}); From 5e3f0f5a06ba99c84e9783f2e567e1d873c26024 Mon Sep 17 00:00:00 2001 From: naviechan Date: Mon, 11 Dec 2023 17:56:50 +0800 Subject: [PATCH 04/26] Lint --- .../api/src/beacon/routes/beacon/rewards.ts | 6 ++-- .../api/test/unit/beacon/testData/beacon.ts | 12 ++++++- .../beacon-node/src/api/impl/beacon/index.ts | 2 +- .../src/api/impl/beacon/rewards/index.ts | 10 ++---- packages/beacon-node/src/chain/interface.ts | 2 +- .../src/chain/rewards/blockRewards.ts | 32 +++++++++++++++---- .../unit/chain/rewards/blockRewards.test.ts | 22 +++++++++---- packages/state-transition/src/index.ts | 2 +- 8 files changed, 60 insertions(+), 28 deletions(-) diff --git a/packages/api/src/beacon/routes/beacon/rewards.ts b/packages/api/src/beacon/routes/beacon/rewards.ts index 487069120e6c..e7455c00bd37 100644 --- a/packages/api/src/beacon/routes/beacon/rewards.ts +++ b/packages/api/src/beacon/routes/beacon/rewards.ts @@ -31,9 +31,8 @@ export type ProposerRewardsResponse = { export type Api = { /** - * Get block - * Returns the complete `SignedBeaconBlock` for a given block ID. - * Depending on the `Accept` header it can be returned either as JSON or SSZ-serialized bytes. + * Get block rewards + * Returns the info of rewards received by the block proposer * * @param blockId Block identifier. * Can be one of: "head" (canonical head in node's view), "genesis", "finalized", \, \. @@ -56,6 +55,7 @@ export const routesData: RoutesData = { }; export type ReqTypes = { + /* eslint-disable @typescript-eslint/naming-convention */ getProposerRewards: {params: {block_id: string}}; }; diff --git a/packages/api/test/unit/beacon/testData/beacon.ts b/packages/api/test/unit/beacon/testData/beacon.ts index 85785bc356a1..cd62bcbdf705 100644 --- a/packages/api/test/unit/beacon/testData/beacon.ts +++ b/packages/api/test/unit/beacon/testData/beacon.ts @@ -172,7 +172,17 @@ export const testData: GenericServerTestCases = { getProposerRewards: { args: ["head"], - res: {executionOptimistic: true, data: {proposerIndex: 0, total: 15, attestations: 8, syncAggregate: 4, proposerSlashings: 2, attesterSlashings: 1}} + res: { + executionOptimistic: true, + data: { + proposerIndex: 0, + total: 15, + attestations: 8, + syncAggregate: 4, + proposerSlashings: 2, + attesterSlashings: 1, + }, + }, }, // - diff --git a/packages/beacon-node/src/api/impl/beacon/index.ts b/packages/beacon-node/src/api/impl/beacon/index.ts index 7df6c800ac29..492e2f8ff8b1 100644 --- a/packages/beacon-node/src/api/impl/beacon/index.ts +++ b/packages/beacon-node/src/api/impl/beacon/index.ts @@ -3,7 +3,7 @@ import {ApiModules} from "../types.js"; import {getBeaconBlockApi} from "./blocks/index.js"; import {getBeaconPoolApi} from "./pool/index.js"; import {getBeaconStateApi} from "./state/index.js"; -import { getBeaconRewardsApi } from "./rewards/index.js"; +import {getBeaconRewardsApi} from "./rewards/index.js"; export function getBeaconApi( modules: Pick diff --git a/packages/beacon-node/src/api/impl/beacon/rewards/index.ts b/packages/beacon-node/src/api/impl/beacon/rewards/index.ts index e24cf92bc8ee..a19be8b5efbd 100644 --- a/packages/beacon-node/src/api/impl/beacon/rewards/index.ts +++ b/packages/beacon-node/src/api/impl/beacon/rewards/index.ts @@ -1,17 +1,13 @@ import {routes, ServerApi} from "@lodestar/api"; import {ApiModules} from "../../types.js"; -import { resolveBlockId } from "../blocks/utils.js"; - -export function getBeaconRewardsApi({ - chain, - config, -}: Pick): ServerApi { +import {resolveBlockId} from "../blocks/utils.js"; +export function getBeaconRewardsApi({chain}: Pick): ServerApi { return { async getProposerRewards(blockId) { const {block, executionOptimistic} = await resolveBlockId(chain, blockId); const data = await chain.getBlockRewards(block.message); return {data, executionOptimistic}; - } + }, }; } diff --git a/packages/beacon-node/src/chain/interface.ts b/packages/beacon-node/src/chain/interface.ts index 0e38794eef5a..99c1b7ea0c4a 100644 --- a/packages/beacon-node/src/chain/interface.ts +++ b/packages/beacon-node/src/chain/interface.ts @@ -52,7 +52,7 @@ import {AssembledBlockType, BlockAttributes, BlockType} from "./produceBlock/pro import {SeenAttestationDatas} from "./seenCache/seenAttestationData.js"; import {SeenGossipBlockInput} from "./seenCache/index.js"; import {ShufflingCache} from "./shufflingCache.js"; -import { BlockRewards } from "./rewards/blockRewards.js"; +import {BlockRewards} from "./rewards/blockRewards.js"; export {BlockType, type AssembledBlockType}; export {type ProposerPreparationData}; diff --git a/packages/beacon-node/src/chain/rewards/blockRewards.ts b/packages/beacon-node/src/chain/rewards/blockRewards.ts index 83a5bc2c1887..28fc1aca1476 100644 --- a/packages/beacon-node/src/chain/rewards/blockRewards.ts +++ b/packages/beacon-node/src/chain/rewards/blockRewards.ts @@ -6,7 +6,7 @@ import { getAttesterSlashableIndices, } from "@lodestar/state-transition"; import {getAttestationParticipationStatus} from "@lodestar/state-transition"; -import {Gwei, UintNum64, ValidatorIndex, allForks, altair, phase0} from "@lodestar/types"; +import {ValidatorIndex, allForks, altair, phase0} from "@lodestar/types"; import { PROPOSER_WEIGHT, TIMELY_HEAD_FLAG_INDEX, @@ -49,7 +49,6 @@ export async function computeBlockRewards( block: allForks.BeaconBlock, state: CachedBeaconStateAllForks ): Promise { - const fork = state.config.getForkName(block.slot); const blockAttestationReward = fork === ForkName.phase0 @@ -62,20 +61,33 @@ export async function computeBlockRewards( const total = blockAttestationReward + syncAggregateReward + blockProposerSlashingReward + blockAttesterSlashingReward; - return {proposerIndex: block.proposerIndex, total, attestations: blockAttestationReward, syncAggregate: syncAggregateReward, proposerSlashings: blockProposerSlashingReward, attesterSlashings: blockAttesterSlashingReward}; + return { + proposerIndex: block.proposerIndex, + total, + attestations: blockAttestationReward, + syncAggregate: syncAggregateReward, + proposerSlashings: blockProposerSlashingReward, + attesterSlashings: blockAttesterSlashingReward, + }; } /** * TODO: Calculate rewards received by block proposer for incuding attestations. */ -function computeBlockAttestationRewardPhase0(_block: phase0.BeaconBlock, _state: CachedBeaconStatePhase0): SubRewardValue { +function computeBlockAttestationRewardPhase0( + _block: phase0.BeaconBlock, + _state: CachedBeaconStatePhase0 +): SubRewardValue { throw new Error("Unsupported fork! Block attestation reward calculation is not yet available in phase0"); } /** * Calculate rewards received by block proposer for incuding attestations since Altair. Mimics `processAttestationsAltair()` */ -function computeBlockAttestationRewardAltair(block: altair.BeaconBlock, state: CachedBeaconStateAltair): SubRewardValue { +function computeBlockAttestationRewardAltair( + block: altair.BeaconBlock, + state: CachedBeaconStateAltair +): SubRewardValue { const {epochCtx} = state; const {effectiveBalanceIncrements} = epochCtx; const stateSlot = state.slot; @@ -143,7 +155,10 @@ function computeSyncAggregateReward(block: altair.BeaconBlock, state: CachedBeac * Calculate rewards received by block proposer for include proposer slashings. * All proposer slashing rewards go to block proposer and none to whistleblower as of Deneb */ -function computeBlockProposerSlashingReward(block: allForks.BeaconBlock, state: CachedBeaconStateAllForks): SubRewardValue { +function computeBlockProposerSlashingReward( + block: allForks.BeaconBlock, + state: CachedBeaconStateAllForks +): SubRewardValue { let proposerSlashingReward = 0; for (const proposerSlashing of block.body.proposerSlashings) { @@ -160,7 +175,10 @@ function computeBlockProposerSlashingReward(block: allForks.BeaconBlock, state: * Calculate rewards received by block proposer for include attester slashings. * All attester slashing rewards go to block proposer and none to whistleblower as of Deneb */ -function computeBlockAttesterSlashingReward(block: allForks.BeaconBlock, state: CachedBeaconStateAllForks): SubRewardValue { +function computeBlockAttesterSlashingReward( + block: allForks.BeaconBlock, + state: CachedBeaconStateAllForks +): SubRewardValue { let attesterSlashingReward = 0; for (const attesterSlashing of block.body.attesterSlashings) { diff --git a/packages/beacon-node/test/unit/chain/rewards/blockRewards.test.ts b/packages/beacon-node/test/unit/chain/rewards/blockRewards.test.ts index c09fd6aacc59..a629558e5f00 100644 --- a/packages/beacon-node/test/unit/chain/rewards/blockRewards.test.ts +++ b/packages/beacon-node/test/unit/chain/rewards/blockRewards.test.ts @@ -1,11 +1,20 @@ import {describe, it, expect} from "vitest"; import {SYNC_COMMITTEE_SIZE} from "@lodestar/params"; import {ssz} from "@lodestar/types"; -import {CachedBeaconStateAllForks, DataAvailableStatus, ExecutionPayloadStatus, stateTransition} from "../../../../../state-transition"; -import { generatePerfTestCachedStateAltair, cachedStateAltairPopulateCaches } from "../../../../../state-transition/test/perf/util.js"; +import { + CachedBeaconStateAllForks, + DataAvailableStatus, + ExecutionPayloadStatus, + stateTransition, +} from "@lodestar/state-transition"; +import { + generatePerfTestCachedStateAltair, + cachedStateAltairPopulateCaches, + // eslint-disable-next-line import/no-relative-packages +} from "../../../../../state-transition/test/perf/util.js"; // eslint-disable-next-line import/no-relative-packages import {BlockAltairOpts, getBlockAltair} from "../../../../../state-transition/test/perf/block/util.js"; -import { computeBlockRewards } from "../../../../src/chain/rewards/blockRewards.js"; +import {computeBlockRewards} from "../../../../src/chain/rewards/blockRewards.js"; describe("chain / rewards / blockRewards", () => { const testCases: {id: string; opts: BlockAltairOpts}[] = [ @@ -68,7 +77,7 @@ describe("chain / rewards / blockRewards", () => { bitsLen: 90, syncCommitteeBitsLen: 0, }, - } + }, ]; for (const {id, opts} of testCases) { @@ -81,7 +90,8 @@ describe("chain / rewards / blockRewards", () => { state.hashTreeRoot(); cachedStateAltairPopulateCaches(state); const calculatedBlockReward = await computeBlockRewards(block.message, state as CachedBeaconStateAllForks); - const {proposerIndex, total, attestations, syncAggregate, proposerSlashings, attesterSlashings} = calculatedBlockReward; + const {proposerIndex, total, attestations, syncAggregate, proposerSlashings, attesterSlashings} = + calculatedBlockReward; // Sanity check expect(proposerIndex).toBe(block.message.proposerIndex); @@ -113,8 +123,6 @@ describe("chain / rewards / blockRewards", () => { expect(attestations).toBe(rewardCache.attestations); expect(syncAggregate).toBe(rewardCache.syncAggregate); expect(proposerSlashings + attesterSlashings).toBe(rewardCache.slashing); - }); } - }); diff --git a/packages/state-transition/src/index.ts b/packages/state-transition/src/index.ts index 7080ed0bb2b3..020681d300ab 100644 --- a/packages/state-transition/src/index.ts +++ b/packages/state-transition/src/index.ts @@ -62,4 +62,4 @@ export {becomesNewEth1Data} from "./block/processEth1Data.js"; // Withdrawals for new blocks export {getExpectedWithdrawals} from "./block/processWithdrawals.js"; -export {getAttestationParticipationStatus} from "./block/processAttestationsAltair.js" \ No newline at end of file +export {getAttestationParticipationStatus} from "./block/processAttestationsAltair.js"; From c2da4572d5009925c1876eedfbef334155794bfe Mon Sep 17 00:00:00 2001 From: naviechan Date: Tue, 12 Dec 2023 12:32:24 +0800 Subject: [PATCH 05/26] Address comment --- packages/beacon-node/src/chain/chain.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/beacon-node/src/chain/chain.ts b/packages/beacon-node/src/chain/chain.ts index 8dc4bd1d2ac6..f3e84f3a7f81 100644 --- a/packages/beacon-node/src/chain/chain.ts +++ b/packages/beacon-node/src/chain/chain.ts @@ -994,7 +994,7 @@ export class BeaconChain implements IBeaconChain { } async getBlockRewards(block: allForks.FullOrBlindedBeaconBlock): Promise { - const preState = (await this.regen.getPreState(block, {dontTransferCache: false}, RegenCaller.restApi)).clone(); + const preState = (await this.regen.getPreState(block, {dontTransferCache: true}, RegenCaller.restApi)).clone(); preState.slot = block.slot; // regen.getPreState guarantees pre_state of the same epoch but not the same slot const result = computeBlockRewards(block, preState); return result; From 1e47858d1225d0fc6d94de04c4e2173e629c7023 Mon Sep 17 00:00:00 2001 From: navie Date: Tue, 12 Dec 2023 15:34:35 +0800 Subject: [PATCH 06/26] Reduce code redundancy --- .../src/chain/rewards/blockRewards.ts | 77 ++----------------- packages/state-transition/src/index.ts | 2 +- 2 files changed, 8 insertions(+), 71 deletions(-) diff --git a/packages/beacon-node/src/chain/rewards/blockRewards.ts b/packages/beacon-node/src/chain/rewards/blockRewards.ts index 28fc1aca1476..55e02a6c4610 100644 --- a/packages/beacon-node/src/chain/rewards/blockRewards.ts +++ b/packages/beacon-node/src/chain/rewards/blockRewards.ts @@ -2,30 +2,11 @@ import { CachedBeaconStateAllForks, CachedBeaconStateAltair, CachedBeaconStatePhase0, - RootCache, getAttesterSlashableIndices, + processAttestationsAltair, } from "@lodestar/state-transition"; -import {getAttestationParticipationStatus} from "@lodestar/state-transition"; import {ValidatorIndex, allForks, altair, phase0} from "@lodestar/types"; -import { - PROPOSER_WEIGHT, - TIMELY_HEAD_FLAG_INDEX, - TIMELY_HEAD_WEIGHT, - TIMELY_SOURCE_FLAG_INDEX, - TIMELY_SOURCE_WEIGHT, - TIMELY_TARGET_FLAG_INDEX, - TIMELY_TARGET_WEIGHT, - WEIGHT_DENOMINATOR, - ForkName, - WHISTLEBLOWER_REWARD_QUOTIENT, -} from "@lodestar/params"; - -const PROPOSER_REWARD_DOMINATOR = ((WEIGHT_DENOMINATOR - PROPOSER_WEIGHT) * WEIGHT_DENOMINATOR) / PROPOSER_WEIGHT; - -/** Same to https://github.com/ethereum/eth2.0-specs/blob/v1.1.0-alpha.5/specs/altair/beacon-chain.md#has_flag */ -const TIMELY_SOURCE = 1 << TIMELY_SOURCE_FLAG_INDEX; -const TIMELY_TARGET = 1 << TIMELY_TARGET_FLAG_INDEX; -const TIMELY_HEAD = 1 << TIMELY_HEAD_FLAG_INDEX; +import {ForkName, WHISTLEBLOWER_REWARD_QUOTIENT} from "@lodestar/params"; type SubRewardValue = number; // All reward values should be integer @@ -82,63 +63,19 @@ function computeBlockAttestationRewardPhase0( } /** - * Calculate rewards received by block proposer for incuding attestations since Altair. Mimics `processAttestationsAltair()` + * Calculate rewards received by block proposer for incuding attestations since Altair. + * Reuses `processAttestationsAltair()`. Has dependency on RewardCache */ function computeBlockAttestationRewardAltair( block: altair.BeaconBlock, state: CachedBeaconStateAltair ): SubRewardValue { - const {epochCtx} = state; - const {effectiveBalanceIncrements} = epochCtx; - const stateSlot = state.slot; - const rootCache = new RootCache(state); - const currentEpoch = epochCtx.epoch; const fork = state.config.getForkSeq(block.slot); + const {attestations} = block.body; - let blockAttestationReward = 0; - - for (const attestation of block.body.attestations) { - const data = attestation.data; - - const committeeIndices = epochCtx.getBeaconCommittee(data.slot, data.index); - const attestingIndices = attestation.aggregationBits.intersectValues(committeeIndices); - - const inCurrentEpoch = data.target.epoch === currentEpoch; - const epochParticipation = inCurrentEpoch ? state.currentEpochParticipation : state.previousEpochParticipation; - - const flagsAttestation = getAttestationParticipationStatus( - fork, - data, - stateSlot - data.slot, - epochCtx.epoch, - rootCache - ); - - let totalBalanceIncrementsWithWeight = 0; - for (const index of attestingIndices) { - const flags = epochParticipation.get(index); - epochParticipation.set(index, flagsAttestation); - const flagsNewSet = ~flags & flagsAttestation; - - // Spec: - // baseReward = state.validators[index].effectiveBalance / EFFECTIVE_BALANCE_INCREMENT * baseRewardPerIncrement; - // proposerRewardNumerator += baseReward * totalWeight - let totalWeight = 0; - if ((flagsNewSet & TIMELY_SOURCE) === TIMELY_SOURCE) totalWeight += TIMELY_SOURCE_WEIGHT; - if ((flagsNewSet & TIMELY_TARGET) === TIMELY_TARGET) totalWeight += TIMELY_TARGET_WEIGHT; - if ((flagsNewSet & TIMELY_HEAD) === TIMELY_HEAD) totalWeight += TIMELY_HEAD_WEIGHT; - - if (totalWeight > 0) { - totalBalanceIncrementsWithWeight += effectiveBalanceIncrements[index] * totalWeight; - } - } - - const totalIncrements = totalBalanceIncrementsWithWeight; - const proposerRewardNumerator = totalIncrements * state.epochCtx.baseRewardPerIncrement; - blockAttestationReward += Math.floor(proposerRewardNumerator / PROPOSER_REWARD_DOMINATOR); - } + processAttestationsAltair(fork, state, attestations, false); - return blockAttestationReward; + return state.proposerRewards.attestations; } function computeSyncAggregateReward(block: altair.BeaconBlock, state: CachedBeaconStateAltair): SubRewardValue { diff --git a/packages/state-transition/src/index.ts b/packages/state-transition/src/index.ts index 020681d300ab..0ef460e784af 100644 --- a/packages/state-transition/src/index.ts +++ b/packages/state-transition/src/index.ts @@ -62,4 +62,4 @@ export {becomesNewEth1Data} from "./block/processEth1Data.js"; // Withdrawals for new blocks export {getExpectedWithdrawals} from "./block/processWithdrawals.js"; -export {getAttestationParticipationStatus} from "./block/processAttestationsAltair.js"; +export {getAttestationParticipationStatus, processAttestationsAltair} from "./block/processAttestationsAltair.js"; From ac9bb42ab682cde064c4b1bfa238393186c45f46 Mon Sep 17 00:00:00 2001 From: NC Date: Wed, 27 Dec 2023 18:18:48 +0800 Subject: [PATCH 07/26] Read reward cache first before calculate --- .../src/chain/rewards/blockRewards.ts | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/packages/beacon-node/src/chain/rewards/blockRewards.ts b/packages/beacon-node/src/chain/rewards/blockRewards.ts index 55e02a6c4610..5ebb6b6c77ae 100644 --- a/packages/beacon-node/src/chain/rewards/blockRewards.ts +++ b/packages/beacon-node/src/chain/rewards/blockRewards.ts @@ -30,12 +30,23 @@ export async function computeBlockRewards( block: allForks.BeaconBlock, state: CachedBeaconStateAllForks ): Promise { + const fork = state.config.getForkName(block.slot); - const blockAttestationReward = - fork === ForkName.phase0 - ? computeBlockAttestationRewardPhase0(block as phase0.BeaconBlock, state as CachedBeaconStatePhase0) - : computeBlockAttestationRewardAltair(block as altair.BeaconBlock, state as CachedBeaconStateAltair); - const syncAggregateReward = computeSyncAggregateReward(block as altair.BeaconBlock, state as CachedBeaconStateAltair); + const {attestations: cachedAttestationsReward, syncAggregate: cachedSyncAggregateReward} = state.proposerRewards; + let blockAttestationReward = cachedAttestationsReward; + let syncAggregateReward = cachedSyncAggregateReward; + + if (blockAttestationReward === 0) { + blockAttestationReward = + fork === ForkName.phase0 + ? computeBlockAttestationRewardPhase0(block as phase0.BeaconBlock, state as CachedBeaconStatePhase0) + : computeBlockAttestationRewardAltair(block as altair.BeaconBlock, state as CachedBeaconStateAltair); + } + + if (syncAggregateReward === 0) { + syncAggregateReward = computeSyncAggregateReward(block as altair.BeaconBlock, state as CachedBeaconStateAltair); + } + const blockProposerSlashingReward = computeBlockProposerSlashingReward(block, state); const blockAttesterSlashingReward = computeBlockAttesterSlashingReward(block, state); From 1fc3201e8cbb717ec31a77bb78b22f446815a5fa Mon Sep 17 00:00:00 2001 From: NC Date: Wed, 27 Dec 2023 18:21:32 +0800 Subject: [PATCH 08/26] Lint --- packages/beacon-node/src/chain/rewards/blockRewards.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/beacon-node/src/chain/rewards/blockRewards.ts b/packages/beacon-node/src/chain/rewards/blockRewards.ts index 5ebb6b6c77ae..57a564a10f45 100644 --- a/packages/beacon-node/src/chain/rewards/blockRewards.ts +++ b/packages/beacon-node/src/chain/rewards/blockRewards.ts @@ -30,7 +30,6 @@ export async function computeBlockRewards( block: allForks.BeaconBlock, state: CachedBeaconStateAllForks ): Promise { - const fork = state.config.getForkName(block.slot); const {attestations: cachedAttestationsReward, syncAggregate: cachedSyncAggregateReward} = state.proposerRewards; let blockAttestationReward = cachedAttestationsReward; From f702c2b610dba21f5985021cdcd1e21faf2beb98 Mon Sep 17 00:00:00 2001 From: NC Date: Thu, 11 Jan 2024 17:29:16 +0800 Subject: [PATCH 09/26] Partially address comments --- .../src/chain/rewards/blockRewards.ts | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/beacon-node/src/chain/rewards/blockRewards.ts b/packages/beacon-node/src/chain/rewards/blockRewards.ts index 57a564a10f45..c962ff2645f3 100644 --- a/packages/beacon-node/src/chain/rewards/blockRewards.ts +++ b/packages/beacon-node/src/chain/rewards/blockRewards.ts @@ -28,26 +28,26 @@ export type BlockRewards = { */ export async function computeBlockRewards( block: allForks.BeaconBlock, - state: CachedBeaconStateAllForks + preState: CachedBeaconStateAllForks ): Promise { - const fork = state.config.getForkName(block.slot); - const {attestations: cachedAttestationsReward, syncAggregate: cachedSyncAggregateReward} = state.proposerRewards; + const fork = preState.config.getForkName(block.slot); + const {attestations: cachedAttestationsReward, syncAggregate: cachedSyncAggregateReward} = preState.proposerRewards; let blockAttestationReward = cachedAttestationsReward; let syncAggregateReward = cachedSyncAggregateReward; if (blockAttestationReward === 0) { blockAttestationReward = fork === ForkName.phase0 - ? computeBlockAttestationRewardPhase0(block as phase0.BeaconBlock, state as CachedBeaconStatePhase0) - : computeBlockAttestationRewardAltair(block as altair.BeaconBlock, state as CachedBeaconStateAltair); + ? computeBlockAttestationRewardPhase0(block as phase0.BeaconBlock, preState as CachedBeaconStatePhase0) + : computeBlockAttestationRewardAltair(block as altair.BeaconBlock, preState as CachedBeaconStateAltair); } if (syncAggregateReward === 0) { - syncAggregateReward = computeSyncAggregateReward(block as altair.BeaconBlock, state as CachedBeaconStateAltair); + syncAggregateReward = computeSyncAggregateReward(block as altair.BeaconBlock, preState as CachedBeaconStateAltair); } - const blockProposerSlashingReward = computeBlockProposerSlashingReward(block, state); - const blockAttesterSlashingReward = computeBlockAttesterSlashingReward(block, state); + const blockProposerSlashingReward = computeBlockProposerSlashingReward(block, preState); + const blockAttesterSlashingReward = computeBlockAttesterSlashingReward(block, preState); const total = blockAttestationReward + syncAggregateReward + blockProposerSlashingReward + blockAttesterSlashingReward; @@ -110,7 +110,7 @@ function computeBlockProposerSlashingReward( for (const proposerSlashing of block.body.proposerSlashings) { const offendingProposerIndex = proposerSlashing.signedHeader1.message.proposerIndex; - const offendingProposerBalance = state.validators.get(offendingProposerIndex).effectiveBalance; + const offendingProposerBalance = state.validators.getReadonly(offendingProposerIndex).effectiveBalance; proposerSlashingReward += Math.floor(offendingProposerBalance / WHISTLEBLOWER_REWARD_QUOTIENT); } @@ -124,13 +124,13 @@ function computeBlockProposerSlashingReward( */ function computeBlockAttesterSlashingReward( block: allForks.BeaconBlock, - state: CachedBeaconStateAllForks + preState: CachedBeaconStateAllForks ): SubRewardValue { let attesterSlashingReward = 0; for (const attesterSlashing of block.body.attesterSlashings) { for (const offendingAttesterIndex of getAttesterSlashableIndices(attesterSlashing)) { - const offendingAttesterBalance = state.validators.get(offendingAttesterIndex).effectiveBalance; + const offendingAttesterBalance = preState.validators.getReadonly(offendingAttesterIndex).effectiveBalance; attesterSlashingReward += Math.floor(offendingAttesterBalance / WHISTLEBLOWER_REWARD_QUOTIENT); } From 6c14d0c7f749c9b79dfa9effd0c9d878d874b232 Mon Sep 17 00:00:00 2001 From: NC Date: Thu, 11 Jan 2024 19:06:02 +0800 Subject: [PATCH 10/26] Accept optional postState to get the reward cache --- packages/beacon-node/src/chain/chain.ts | 4 ++-- packages/beacon-node/src/chain/rewards/blockRewards.ts | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/beacon-node/src/chain/chain.ts b/packages/beacon-node/src/chain/chain.ts index f3e84f3a7f81..016e5b5cb836 100644 --- a/packages/beacon-node/src/chain/chain.ts +++ b/packages/beacon-node/src/chain/chain.ts @@ -995,8 +995,8 @@ export class BeaconChain implements IBeaconChain { async getBlockRewards(block: allForks.FullOrBlindedBeaconBlock): Promise { const preState = (await this.regen.getPreState(block, {dontTransferCache: true}, RegenCaller.restApi)).clone(); - preState.slot = block.slot; // regen.getPreState guarantees pre_state of the same epoch but not the same slot - const result = computeBlockRewards(block, preState); + const postState = this.regen.getStateSync(toHexString(block.stateRoot)) ?? undefined; + const result = computeBlockRewards(block, preState, postState); return result; } } diff --git a/packages/beacon-node/src/chain/rewards/blockRewards.ts b/packages/beacon-node/src/chain/rewards/blockRewards.ts index c962ff2645f3..2c2df099279d 100644 --- a/packages/beacon-node/src/chain/rewards/blockRewards.ts +++ b/packages/beacon-node/src/chain/rewards/blockRewards.ts @@ -20,7 +20,8 @@ export type BlockRewards = { }; /** - * Calculate total proposer block rewards given block and the beacon state of the same slot before the block is applied + * Calculate total proposer block rewards given block and the beacon state of the same slot before the block is applied (preState) + * postState can be passed in to read reward cache if available * Standard (Non MEV) rewards for proposing a block consists of: * 1) Including attestations from (beacon) committee * 2) Including attestations from sync committee @@ -28,10 +29,11 @@ export type BlockRewards = { */ export async function computeBlockRewards( block: allForks.BeaconBlock, - preState: CachedBeaconStateAllForks + preState: CachedBeaconStateAllForks, + postState?: CachedBeaconStateAllForks ): Promise { const fork = preState.config.getForkName(block.slot); - const {attestations: cachedAttestationsReward, syncAggregate: cachedSyncAggregateReward} = preState.proposerRewards; + const {attestations: cachedAttestationsReward = 0, syncAggregate: cachedSyncAggregateReward = 0} = postState?.proposerRewards || {}; let blockAttestationReward = cachedAttestationsReward; let syncAggregateReward = cachedSyncAggregateReward; From f331e5a56ea4fd05144ca3ad12468255f29b2af0 Mon Sep 17 00:00:00 2001 From: NC Date: Thu, 11 Jan 2024 19:06:08 +0800 Subject: [PATCH 11/26] Update test --- .../unit/chain/rewards/blockRewards.test.ts | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/packages/beacon-node/test/unit/chain/rewards/blockRewards.test.ts b/packages/beacon-node/test/unit/chain/rewards/blockRewards.test.ts index a629558e5f00..1d397d57a318 100644 --- a/packages/beacon-node/test/unit/chain/rewards/blockRewards.test.ts +++ b/packages/beacon-node/test/unit/chain/rewards/blockRewards.test.ts @@ -125,4 +125,53 @@ describe("chain / rewards / blockRewards", () => { expect(proposerSlashings + attesterSlashings).toBe(rewardCache.slashing); }); } + + // Check if `computeBlockRewards` consults reward cache in the post state first + it("Check reward cache", async () => { + const preState = generatePerfTestCachedStateAltair(); + const {opts} = testCases[0]; // Use opts of `normal case` + const block = getBlockAltair(preState, testCases[0].opts); + // Populate permanent root caches of the block + ssz.altair.BeaconBlock.hashTreeRoot(block.message); + // Populate tree root caches of the state + preState.hashTreeRoot(); + cachedStateAltairPopulateCaches(preState); + + const postState = stateTransition(preState as CachedBeaconStateAllForks, block, { + executionPayloadStatus: ExecutionPayloadStatus.valid, + dataAvailableStatus: DataAvailableStatus.available, + verifyProposer: false, + verifySignatures: false, + verifyStateRoot: false, + }); + + // Set postState's reward cache + const rewardCache = postState.proposerRewards; // Grab original reward cache before overwritten + postState.proposerRewards = {attestations: 1000, syncAggregate: 1001, slashing: 1002}; + + const calculatedBlockReward = await computeBlockRewards(block.message, preState as CachedBeaconStateAllForks, postState); + const {proposerIndex, total, attestations, syncAggregate, proposerSlashings, attesterSlashings} = + calculatedBlockReward; + + expect(proposerIndex).toBe(block.message.proposerIndex); + expect(total).toBe(attestations + syncAggregate + proposerSlashings + attesterSlashings); + if (opts.syncCommitteeBitsLen === 0) { + expect(syncAggregate).toBe(0); + } + if (opts.attestationLen === 0) { + expect(attestations).toBe(0); + } + if (opts.proposerSlashingLen === 0) { + expect(proposerSlashings).toBe(0); + } + if (opts.attesterSlashingLen === 0) { + expect(attesterSlashings).toBe(0); + } + + // Cross check with rewardCache + expect(attestations).toBe(1000); + expect(syncAggregate).toBe(1001); + expect((proposerSlashings + attesterSlashings) === 1002).toBeFalsy(); + expect(proposerSlashings + attesterSlashings).toBe(rewardCache.slashing); + }); }); From db5cc41def3ed847e6b374fba6fe675b056d3b55 Mon Sep 17 00:00:00 2001 From: NC Date: Thu, 11 Jan 2024 19:13:47 +0800 Subject: [PATCH 12/26] lint --- .../src/chain/rewards/blockRewards.ts | 3 +- .../unit/chain/rewards/blockRewards.test.ts | 84 ++++++++++--------- 2 files changed, 46 insertions(+), 41 deletions(-) diff --git a/packages/beacon-node/src/chain/rewards/blockRewards.ts b/packages/beacon-node/src/chain/rewards/blockRewards.ts index 2c2df099279d..226e6cc739ce 100644 --- a/packages/beacon-node/src/chain/rewards/blockRewards.ts +++ b/packages/beacon-node/src/chain/rewards/blockRewards.ts @@ -33,7 +33,8 @@ export async function computeBlockRewards( postState?: CachedBeaconStateAllForks ): Promise { const fork = preState.config.getForkName(block.slot); - const {attestations: cachedAttestationsReward = 0, syncAggregate: cachedSyncAggregateReward = 0} = postState?.proposerRewards || {}; + const {attestations: cachedAttestationsReward = 0, syncAggregate: cachedSyncAggregateReward = 0} = + postState?.proposerRewards || {}; let blockAttestationReward = cachedAttestationsReward; let syncAggregateReward = cachedSyncAggregateReward; diff --git a/packages/beacon-node/test/unit/chain/rewards/blockRewards.test.ts b/packages/beacon-node/test/unit/chain/rewards/blockRewards.test.ts index 1d397d57a318..f0d85ce3220f 100644 --- a/packages/beacon-node/test/unit/chain/rewards/blockRewards.test.ts +++ b/packages/beacon-node/test/unit/chain/rewards/blockRewards.test.ts @@ -128,50 +128,54 @@ describe("chain / rewards / blockRewards", () => { // Check if `computeBlockRewards` consults reward cache in the post state first it("Check reward cache", async () => { - const preState = generatePerfTestCachedStateAltair(); - const {opts} = testCases[0]; // Use opts of `normal case` - const block = getBlockAltair(preState, testCases[0].opts); - // Populate permanent root caches of the block - ssz.altair.BeaconBlock.hashTreeRoot(block.message); - // Populate tree root caches of the state - preState.hashTreeRoot(); - cachedStateAltairPopulateCaches(preState); + const preState = generatePerfTestCachedStateAltair(); + const {opts} = testCases[0]; // Use opts of `normal case` + const block = getBlockAltair(preState, testCases[0].opts); + // Populate permanent root caches of the block + ssz.altair.BeaconBlock.hashTreeRoot(block.message); + // Populate tree root caches of the state + preState.hashTreeRoot(); + cachedStateAltairPopulateCaches(preState); - const postState = stateTransition(preState as CachedBeaconStateAllForks, block, { - executionPayloadStatus: ExecutionPayloadStatus.valid, - dataAvailableStatus: DataAvailableStatus.available, - verifyProposer: false, - verifySignatures: false, - verifyStateRoot: false, - }); + const postState = stateTransition(preState as CachedBeaconStateAllForks, block, { + executionPayloadStatus: ExecutionPayloadStatus.valid, + dataAvailableStatus: DataAvailableStatus.available, + verifyProposer: false, + verifySignatures: false, + verifyStateRoot: false, + }); - // Set postState's reward cache - const rewardCache = postState.proposerRewards; // Grab original reward cache before overwritten - postState.proposerRewards = {attestations: 1000, syncAggregate: 1001, slashing: 1002}; + // Set postState's reward cache + const rewardCache = postState.proposerRewards; // Grab original reward cache before overwritten + postState.proposerRewards = {attestations: 1000, syncAggregate: 1001, slashing: 1002}; - const calculatedBlockReward = await computeBlockRewards(block.message, preState as CachedBeaconStateAllForks, postState); - const {proposerIndex, total, attestations, syncAggregate, proposerSlashings, attesterSlashings} = - calculatedBlockReward; + const calculatedBlockReward = await computeBlockRewards( + block.message, + preState as CachedBeaconStateAllForks, + postState + ); + const {proposerIndex, total, attestations, syncAggregate, proposerSlashings, attesterSlashings} = + calculatedBlockReward; - expect(proposerIndex).toBe(block.message.proposerIndex); - expect(total).toBe(attestations + syncAggregate + proposerSlashings + attesterSlashings); - if (opts.syncCommitteeBitsLen === 0) { - expect(syncAggregate).toBe(0); - } - if (opts.attestationLen === 0) { - expect(attestations).toBe(0); - } - if (opts.proposerSlashingLen === 0) { - expect(proposerSlashings).toBe(0); - } - if (opts.attesterSlashingLen === 0) { - expect(attesterSlashings).toBe(0); - } + expect(proposerIndex).toBe(block.message.proposerIndex); + expect(total).toBe(attestations + syncAggregate + proposerSlashings + attesterSlashings); + if (opts.syncCommitteeBitsLen === 0) { + expect(syncAggregate).toBe(0); + } + if (opts.attestationLen === 0) { + expect(attestations).toBe(0); + } + if (opts.proposerSlashingLen === 0) { + expect(proposerSlashings).toBe(0); + } + if (opts.attesterSlashingLen === 0) { + expect(attesterSlashings).toBe(0); + } - // Cross check with rewardCache - expect(attestations).toBe(1000); - expect(syncAggregate).toBe(1001); - expect((proposerSlashings + attesterSlashings) === 1002).toBeFalsy(); - expect(proposerSlashings + attesterSlashings).toBe(rewardCache.slashing); + // Cross check with rewardCache + expect(attestations).toBe(1000); + expect(syncAggregate).toBe(1001); + expect(proposerSlashings + attesterSlashings).not.toBe(1002); + expect(proposerSlashings + attesterSlashings).toBe(rewardCache.slashing); }); }); From edc8c3c2bc53f5018a443b4bb08a499e6ba0ed4b Mon Sep 17 00:00:00 2001 From: NC Date: Mon, 22 Jan 2024 16:06:29 +0800 Subject: [PATCH 13/26] Update packages/beacon-node/src/chain/rewards/blockRewards.ts Co-authored-by: Nico Flaig --- packages/beacon-node/src/chain/rewards/blockRewards.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/beacon-node/src/chain/rewards/blockRewards.ts b/packages/beacon-node/src/chain/rewards/blockRewards.ts index 226e6cc739ce..2bed2a799e59 100644 --- a/packages/beacon-node/src/chain/rewards/blockRewards.ts +++ b/packages/beacon-node/src/chain/rewards/blockRewards.ts @@ -66,7 +66,7 @@ export async function computeBlockRewards( } /** - * TODO: Calculate rewards received by block proposer for incuding attestations. + * TODO: Calculate rewards received by block proposer for including attestations. */ function computeBlockAttestationRewardPhase0( _block: phase0.BeaconBlock, From f5430451dbb8d836ba6e4fae71e5d7c2334cc7f9 Mon Sep 17 00:00:00 2001 From: NC Date: Mon, 22 Jan 2024 16:06:39 +0800 Subject: [PATCH 14/26] Update packages/beacon-node/src/chain/rewards/blockRewards.ts Co-authored-by: Nico Flaig --- packages/beacon-node/src/chain/rewards/blockRewards.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/beacon-node/src/chain/rewards/blockRewards.ts b/packages/beacon-node/src/chain/rewards/blockRewards.ts index 2bed2a799e59..3397df605acc 100644 --- a/packages/beacon-node/src/chain/rewards/blockRewards.ts +++ b/packages/beacon-node/src/chain/rewards/blockRewards.ts @@ -76,7 +76,7 @@ function computeBlockAttestationRewardPhase0( } /** - * Calculate rewards received by block proposer for incuding attestations since Altair. + * Calculate rewards received by block proposer for including attestations since Altair. * Reuses `processAttestationsAltair()`. Has dependency on RewardCache */ function computeBlockAttestationRewardAltair( From b605529dee67f5b22357ef9948a2c410a671eadc Mon Sep 17 00:00:00 2001 From: NC Date: Mon, 22 Jan 2024 16:06:50 +0800 Subject: [PATCH 15/26] Update packages/beacon-node/src/chain/rewards/blockRewards.ts Co-authored-by: Nico Flaig --- packages/beacon-node/src/chain/rewards/blockRewards.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/beacon-node/src/chain/rewards/blockRewards.ts b/packages/beacon-node/src/chain/rewards/blockRewards.ts index 3397df605acc..2f7eef63c042 100644 --- a/packages/beacon-node/src/chain/rewards/blockRewards.ts +++ b/packages/beacon-node/src/chain/rewards/blockRewards.ts @@ -102,7 +102,7 @@ function computeSyncAggregateReward(block: altair.BeaconBlock, state: CachedBeac } } /** - * Calculate rewards received by block proposer for include proposer slashings. + * Calculate rewards received by block proposer for including proposer slashings. * All proposer slashing rewards go to block proposer and none to whistleblower as of Deneb */ function computeBlockProposerSlashingReward( From 45734fa1e02df9bb6e8aa77b7367cad5de246440 Mon Sep 17 00:00:00 2001 From: NC Date: Mon, 22 Jan 2024 16:07:01 +0800 Subject: [PATCH 16/26] Update packages/beacon-node/src/chain/rewards/blockRewards.ts Co-authored-by: Nico Flaig --- packages/beacon-node/src/chain/rewards/blockRewards.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/beacon-node/src/chain/rewards/blockRewards.ts b/packages/beacon-node/src/chain/rewards/blockRewards.ts index 2f7eef63c042..4820b2223631 100644 --- a/packages/beacon-node/src/chain/rewards/blockRewards.ts +++ b/packages/beacon-node/src/chain/rewards/blockRewards.ts @@ -122,7 +122,7 @@ function computeBlockProposerSlashingReward( } /** - * Calculate rewards received by block proposer for include attester slashings. + * Calculate rewards received by block proposer for including attester slashings. * All attester slashing rewards go to block proposer and none to whistleblower as of Deneb */ function computeBlockAttesterSlashingReward( From f80daf42bd5a3774fa8cabf4cef2b2aedf39d629 Mon Sep 17 00:00:00 2001 From: navie Date: Mon, 22 Jan 2024 23:02:15 +0800 Subject: [PATCH 17/26] Rename proposerRewards to blockRewards. Fix import --- packages/api/src/beacon/index.ts | 1 + packages/api/src/beacon/routes/beacon/index.ts | 2 +- packages/api/src/beacon/routes/beacon/rewards.ts | 16 ++++++++-------- packages/api/src/beacon/routes/index.ts | 2 ++ packages/api/test/unit/beacon/testData/beacon.ts | 2 +- .../src/api/impl/beacon/rewards/index.ts | 2 +- packages/beacon-node/src/chain/chain.ts | 3 ++- packages/beacon-node/src/chain/interface.ts | 2 +- .../src/chain/rewards/blockRewards.ts | 12 ++---------- 9 files changed, 19 insertions(+), 23 deletions(-) diff --git a/packages/api/src/beacon/index.ts b/packages/api/src/beacon/index.ts index d07f6f4bed53..147e6ff86df2 100644 --- a/packages/api/src/beacon/index.ts +++ b/packages/api/src/beacon/index.ts @@ -5,6 +5,7 @@ import type {Api} from "./routes/index.js"; export * as routes from "./routes/index.js"; export {getClient} from "./client/index.js"; export type {Api}; +export type {BlockRewards} from "./routes/index.js"; // Declare namespaces for CLI options export type ApiNamespace = keyof Api; diff --git a/packages/api/src/beacon/routes/beacon/index.ts b/packages/api/src/beacon/routes/beacon/index.ts index 28937ab2b6c7..92fcc2093188 100644 --- a/packages/api/src/beacon/routes/beacon/index.ts +++ b/packages/api/src/beacon/routes/beacon/index.ts @@ -20,7 +20,7 @@ export * as rewards from "./rewards.js"; export {BroadcastValidation} from "./block.js"; export type {BlockId, BlockHeaderResponse} from "./block.js"; export type {AttestationFilters} from "./pool.js"; -export type {ProposerRewardsResponse as ProposerRewards} from "./rewards.js"; +export type {BlockRewards} from "./rewards.js"; // TODO: Review if re-exporting all these types is necessary export type { StateId, diff --git a/packages/api/src/beacon/routes/beacon/rewards.ts b/packages/api/src/beacon/routes/beacon/rewards.ts index e7455c00bd37..c4bd821a6bee 100644 --- a/packages/api/src/beacon/routes/beacon/rewards.ts +++ b/packages/api/src/beacon/routes/beacon/rewards.ts @@ -20,7 +20,7 @@ import {BlockId} from "./block.js"; */ export type ExecutionOptimistic = boolean; -export type ProposerRewardsResponse = { +export type BlockRewards = { proposerIndex: ValidatorIndex; total: number; attestations: number; @@ -37,11 +37,11 @@ export type Api = { * @param blockId Block identifier. * Can be one of: "head" (canonical head in node's view), "genesis", "finalized", \, \. */ - getProposerRewards( + getBlockRewards( blockId: BlockId ): Promise< ApiClientResponse< - {[HttpStatusCode.OK]: {data: ProposerRewardsResponse; executionOptimistic: ExecutionOptimistic}}, + {[HttpStatusCode.OK]: {data: BlockRewards; executionOptimistic: ExecutionOptimistic}}, HttpStatusCode.BAD_REQUEST | HttpStatusCode.NOT_FOUND > >; @@ -51,17 +51,17 @@ export type Api = { * Define javascript values for each route */ export const routesData: RoutesData = { - getProposerRewards: {url: "/eth/v1/beacon/rewards/blocks/{block_id}", method: "GET"}, + getBlockRewards: {url: "/eth/v1/beacon/rewards/blocks/{block_id}", method: "GET"}, }; export type ReqTypes = { /* eslint-disable @typescript-eslint/naming-convention */ - getProposerRewards: {params: {block_id: string}}; + getBlockRewards: {params: {block_id: string}}; }; export function getReqSerializers(): ReqSerializers { return { - getProposerRewards: { + getBlockRewards: { writeReq: (block_id) => ({params: {block_id: String(block_id)}}), parseReq: ({params}) => [params.block_id], schema: {params: {block_id: Schema.StringRequired}}, @@ -70,7 +70,7 @@ export function getReqSerializers(): ReqSerializers { } export function getReturnTypes(): ReturnTypes { - const ProposerRewardsResponse = new ContainerType( + const BlockRewardsResponse = new ContainerType( { proposerIndex: ssz.ValidatorIndex, total: ssz.UintNum64, @@ -83,6 +83,6 @@ export function getReturnTypes(): ReturnTypes { ); return { - getProposerRewards: ContainerDataExecutionOptimistic(ProposerRewardsResponse), + getBlockRewards: ContainerDataExecutionOptimistic(BlockRewardsResponse), }; } diff --git a/packages/api/src/beacon/routes/index.ts b/packages/api/src/beacon/routes/index.ts index 81eb0cd3276c..651ae5ce3f2e 100644 --- a/packages/api/src/beacon/routes/index.ts +++ b/packages/api/src/beacon/routes/index.ts @@ -30,6 +30,8 @@ export type Api = { validator: ValidatorApi; }; +export type {BlockRewards} from "./beacon/index.js"; + // Reasoning of the API definitions // ================================ // diff --git a/packages/api/test/unit/beacon/testData/beacon.ts b/packages/api/test/unit/beacon/testData/beacon.ts index cd62bcbdf705..6def8181b2fa 100644 --- a/packages/api/test/unit/beacon/testData/beacon.ts +++ b/packages/api/test/unit/beacon/testData/beacon.ts @@ -170,7 +170,7 @@ export const testData: GenericServerTestCases = { // reward - getProposerRewards: { + getBlockRewards: { args: ["head"], res: { executionOptimistic: true, diff --git a/packages/beacon-node/src/api/impl/beacon/rewards/index.ts b/packages/beacon-node/src/api/impl/beacon/rewards/index.ts index a19be8b5efbd..03a182359d90 100644 --- a/packages/beacon-node/src/api/impl/beacon/rewards/index.ts +++ b/packages/beacon-node/src/api/impl/beacon/rewards/index.ts @@ -4,7 +4,7 @@ import {resolveBlockId} from "../blocks/utils.js"; export function getBeaconRewardsApi({chain}: Pick): ServerApi { return { - async getProposerRewards(blockId) { + async getBlockRewards(blockId) { const {block, executionOptimistic} = await resolveBlockId(chain, blockId); const data = await chain.getBlockRewards(block.message); return {data, executionOptimistic}; diff --git a/packages/beacon-node/src/chain/chain.ts b/packages/beacon-node/src/chain/chain.ts index 016e5b5cb836..54de388da19d 100644 --- a/packages/beacon-node/src/chain/chain.ts +++ b/packages/beacon-node/src/chain/chain.ts @@ -33,6 +33,7 @@ import {ProcessShutdownCallback} from "@lodestar/validator"; import {Logger, gweiToWei, isErrorAborted, pruneSetToMax, sleep, toHex} from "@lodestar/utils"; import {ForkSeq, SLOTS_PER_EPOCH} from "@lodestar/params"; +import {type BlockRewards} from "@lodestar/api"; import {GENESIS_EPOCH, ZERO_HASH} from "../constants/index.js"; import {IBeaconDb} from "../db/index.js"; import {Metrics} from "../metrics/index.js"; @@ -76,7 +77,7 @@ import {BlockAttributes, produceBlockBody, produceCommonBlockBody} from "./produ import {computeNewStateRoot} from "./produceBlock/computeNewStateRoot.js"; import {BlockInput} from "./blocks/types.js"; import {SeenAttestationDatas} from "./seenCache/seenAttestationData.js"; -import {BlockRewards, computeBlockRewards} from "./rewards/blockRewards.js"; +import {computeBlockRewards} from "./rewards/blockRewards.js"; import {ShufflingCache} from "./shufflingCache.js"; import {StateContextCache} from "./stateCache/stateContextCache.js"; import {SeenGossipBlockInput} from "./seenCache/index.js"; diff --git a/packages/beacon-node/src/chain/interface.ts b/packages/beacon-node/src/chain/interface.ts index 99c1b7ea0c4a..0df79b4e284a 100644 --- a/packages/beacon-node/src/chain/interface.ts +++ b/packages/beacon-node/src/chain/interface.ts @@ -24,6 +24,7 @@ import {BeaconConfig} from "@lodestar/config"; import {Logger} from "@lodestar/utils"; import {IForkChoice, ProtoBlock} from "@lodestar/fork-choice"; +import {type BlockRewards} from "@lodestar/api"; import {IEth1ForBlockProduction} from "../eth1/index.js"; import {IExecutionEngine, IExecutionBuilder} from "../execution/index.js"; import {Metrics} from "../metrics/metrics.js"; @@ -52,7 +53,6 @@ import {AssembledBlockType, BlockAttributes, BlockType} from "./produceBlock/pro import {SeenAttestationDatas} from "./seenCache/seenAttestationData.js"; import {SeenGossipBlockInput} from "./seenCache/index.js"; import {ShufflingCache} from "./shufflingCache.js"; -import {BlockRewards} from "./rewards/blockRewards.js"; export {BlockType, type AssembledBlockType}; export {type ProposerPreparationData}; diff --git a/packages/beacon-node/src/chain/rewards/blockRewards.ts b/packages/beacon-node/src/chain/rewards/blockRewards.ts index 4820b2223631..32a9463fdaba 100644 --- a/packages/beacon-node/src/chain/rewards/blockRewards.ts +++ b/packages/beacon-node/src/chain/rewards/blockRewards.ts @@ -5,20 +5,12 @@ import { getAttesterSlashableIndices, processAttestationsAltair, } from "@lodestar/state-transition"; -import {ValidatorIndex, allForks, altair, phase0} from "@lodestar/types"; +import {allForks, altair, phase0} from "@lodestar/types"; import {ForkName, WHISTLEBLOWER_REWARD_QUOTIENT} from "@lodestar/params"; +import {type BlockRewards} from "@lodestar/api"; type SubRewardValue = number; // All reward values should be integer -export type BlockRewards = { - proposerIndex: ValidatorIndex; - total: SubRewardValue; - attestations: SubRewardValue; - syncAggregate: SubRewardValue; - proposerSlashings: SubRewardValue; - attesterSlashings: SubRewardValue; -}; - /** * Calculate total proposer block rewards given block and the beacon state of the same slot before the block is applied (preState) * postState can be passed in to read reward cache if available From 3f8d42c9a8dddedc2d797337253dca2e5f1c77cf Mon Sep 17 00:00:00 2001 From: navie Date: Mon, 22 Jan 2024 14:03:27 +0800 Subject: [PATCH 18/26] Remove getBlockRewards from api ignore list --- packages/api/test/unit/beacon/oapiSpec.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/api/test/unit/beacon/oapiSpec.test.ts b/packages/api/test/unit/beacon/oapiSpec.test.ts index 15a10bfeb6f7..08ccccf9caa7 100644 --- a/packages/api/test/unit/beacon/oapiSpec.test.ts +++ b/packages/api/test/unit/beacon/oapiSpec.test.ts @@ -88,7 +88,6 @@ const ignoredOperations = [ /* missing route */ /* https://github.com/ChainSafe/lodestar/issues/5694 */ "getSyncCommitteeRewards", - "getBlockRewards", "getAttestationsRewards", "getDepositSnapshot", // Won't fix for now, see https://github.com/ChainSafe/lodestar/issues/5697 "getBlindedBlock", // https://github.com/ChainSafe/lodestar/issues/5699 From f2bbe954159d8437cbc2141f17df036cd0a501a4 Mon Sep 17 00:00:00 2001 From: navie Date: Tue, 23 Jan 2024 16:13:12 +0800 Subject: [PATCH 19/26] Fix test --- packages/api/test/unit/beacon/oapiSpec.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/api/test/unit/beacon/oapiSpec.test.ts b/packages/api/test/unit/beacon/oapiSpec.test.ts index 08ccccf9caa7..ace1c94da0d9 100644 --- a/packages/api/test/unit/beacon/oapiSpec.test.ts +++ b/packages/api/test/unit/beacon/oapiSpec.test.ts @@ -122,6 +122,7 @@ const ignoredProperties: Record = { getBlockRoot: {response: ["finalized"]}, getBlockAttestations: {response: ["finalized"]}, getStateV2: {response: ["finalized"]}, + getBlockRewards: {response: ["finalized"]}, /* https://github.com/ChainSafe/lodestar/issues/6168 From cde10e9c48bcf0d17c8b1c13032119b12138a5d5 Mon Sep 17 00:00:00 2001 From: NC Date: Thu, 25 Jan 2024 15:16:22 +0800 Subject: [PATCH 20/26] Rename state to preState --- .../beacon-node/src/chain/rewards/blockRewards.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/beacon-node/src/chain/rewards/blockRewards.ts b/packages/beacon-node/src/chain/rewards/blockRewards.ts index 32a9463fdaba..4fddfeb22700 100644 --- a/packages/beacon-node/src/chain/rewards/blockRewards.ts +++ b/packages/beacon-node/src/chain/rewards/blockRewards.ts @@ -62,7 +62,7 @@ export async function computeBlockRewards( */ function computeBlockAttestationRewardPhase0( _block: phase0.BeaconBlock, - _state: CachedBeaconStatePhase0 + _preState: CachedBeaconStatePhase0 ): SubRewardValue { throw new Error("Unsupported fork! Block attestation reward calculation is not yet available in phase0"); } @@ -73,20 +73,20 @@ function computeBlockAttestationRewardPhase0( */ function computeBlockAttestationRewardAltair( block: altair.BeaconBlock, - state: CachedBeaconStateAltair + preState: CachedBeaconStateAltair ): SubRewardValue { - const fork = state.config.getForkSeq(block.slot); + const fork = preState.config.getForkSeq(block.slot); const {attestations} = block.body; - processAttestationsAltair(fork, state, attestations, false); + processAttestationsAltair(fork, preState, attestations, false); - return state.proposerRewards.attestations; + return preState.proposerRewards.attestations; } -function computeSyncAggregateReward(block: altair.BeaconBlock, state: CachedBeaconStateAltair): SubRewardValue { +function computeSyncAggregateReward(block: altair.BeaconBlock, preState: CachedBeaconStateAltair): SubRewardValue { if (block.body.syncAggregate !== undefined) { const {syncCommitteeBits} = block.body.syncAggregate; - const {syncProposerReward} = state.epochCtx; + const {syncProposerReward} = preState.epochCtx; return syncCommitteeBits.getTrueBitIndexes().length * Math.floor(syncProposerReward); // syncProposerReward should already be integer } else { From a6e2fc43d3a3700c2d8399c4404baf30a45e2d1a Mon Sep 17 00:00:00 2001 From: NC Date: Thu, 25 Jan 2024 15:33:07 +0800 Subject: [PATCH 21/26] Add description to fields in BlockRewards --- packages/api/src/beacon/routes/beacon/rewards.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/api/src/beacon/routes/beacon/rewards.ts b/packages/api/src/beacon/routes/beacon/rewards.ts index c4bd821a6bee..fbbfbb02530d 100644 --- a/packages/api/src/beacon/routes/beacon/rewards.ts +++ b/packages/api/src/beacon/routes/beacon/rewards.ts @@ -20,13 +20,16 @@ import {BlockId} from "./block.js"; */ export type ExecutionOptimistic = boolean; +/** + * Rewards info for a single block. Every reward value is in Gwei. + */ export type BlockRewards = { - proposerIndex: ValidatorIndex; - total: number; - attestations: number; - syncAggregate: number; - proposerSlashings: number; - attesterSlashings: number; + proposerIndex: ValidatorIndex; // proposer of the block, the proposer index who receives these rewards + total: number; // total block reward, equal to attestations + sync_aggregate + proposer_slashings + attester_slashings + attestations: number; // block reward component due to included attestations + syncAggregate: number; // block reward component due to included sync_aggregate + proposerSlashings: number; // block reward component due to included proposer_slashings + attesterSlashings: number; // block reward component due to included attester_slashings }; export type Api = { From ee42dded59d8701746c0398f11c3dedd48c2a89d Mon Sep 17 00:00:00 2001 From: NC Date: Thu, 25 Jan 2024 15:54:06 +0800 Subject: [PATCH 22/26] Clean up imports --- packages/api/src/beacon/index.ts | 1 - packages/api/src/beacon/routes/index.ts | 2 -- packages/beacon-node/src/chain/chain.ts | 3 +-- packages/beacon-node/src/chain/interface.ts | 2 +- packages/beacon-node/src/chain/rewards/blockRewards.ts | 3 ++- 5 files changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/api/src/beacon/index.ts b/packages/api/src/beacon/index.ts index 147e6ff86df2..d07f6f4bed53 100644 --- a/packages/api/src/beacon/index.ts +++ b/packages/api/src/beacon/index.ts @@ -5,7 +5,6 @@ import type {Api} from "./routes/index.js"; export * as routes from "./routes/index.js"; export {getClient} from "./client/index.js"; export type {Api}; -export type {BlockRewards} from "./routes/index.js"; // Declare namespaces for CLI options export type ApiNamespace = keyof Api; diff --git a/packages/api/src/beacon/routes/index.ts b/packages/api/src/beacon/routes/index.ts index 651ae5ce3f2e..81eb0cd3276c 100644 --- a/packages/api/src/beacon/routes/index.ts +++ b/packages/api/src/beacon/routes/index.ts @@ -30,8 +30,6 @@ export type Api = { validator: ValidatorApi; }; -export type {BlockRewards} from "./beacon/index.js"; - // Reasoning of the API definitions // ================================ // diff --git a/packages/beacon-node/src/chain/chain.ts b/packages/beacon-node/src/chain/chain.ts index 54de388da19d..016e5b5cb836 100644 --- a/packages/beacon-node/src/chain/chain.ts +++ b/packages/beacon-node/src/chain/chain.ts @@ -33,7 +33,6 @@ import {ProcessShutdownCallback} from "@lodestar/validator"; import {Logger, gweiToWei, isErrorAborted, pruneSetToMax, sleep, toHex} from "@lodestar/utils"; import {ForkSeq, SLOTS_PER_EPOCH} from "@lodestar/params"; -import {type BlockRewards} from "@lodestar/api"; import {GENESIS_EPOCH, ZERO_HASH} from "../constants/index.js"; import {IBeaconDb} from "../db/index.js"; import {Metrics} from "../metrics/index.js"; @@ -77,7 +76,7 @@ import {BlockAttributes, produceBlockBody, produceCommonBlockBody} from "./produ import {computeNewStateRoot} from "./produceBlock/computeNewStateRoot.js"; import {BlockInput} from "./blocks/types.js"; import {SeenAttestationDatas} from "./seenCache/seenAttestationData.js"; -import {computeBlockRewards} from "./rewards/blockRewards.js"; +import {BlockRewards, computeBlockRewards} from "./rewards/blockRewards.js"; import {ShufflingCache} from "./shufflingCache.js"; import {StateContextCache} from "./stateCache/stateContextCache.js"; import {SeenGossipBlockInput} from "./seenCache/index.js"; diff --git a/packages/beacon-node/src/chain/interface.ts b/packages/beacon-node/src/chain/interface.ts index 0df79b4e284a..99c1b7ea0c4a 100644 --- a/packages/beacon-node/src/chain/interface.ts +++ b/packages/beacon-node/src/chain/interface.ts @@ -24,7 +24,6 @@ import {BeaconConfig} from "@lodestar/config"; import {Logger} from "@lodestar/utils"; import {IForkChoice, ProtoBlock} from "@lodestar/fork-choice"; -import {type BlockRewards} from "@lodestar/api"; import {IEth1ForBlockProduction} from "../eth1/index.js"; import {IExecutionEngine, IExecutionBuilder} from "../execution/index.js"; import {Metrics} from "../metrics/metrics.js"; @@ -53,6 +52,7 @@ import {AssembledBlockType, BlockAttributes, BlockType} from "./produceBlock/pro import {SeenAttestationDatas} from "./seenCache/seenAttestationData.js"; import {SeenGossipBlockInput} from "./seenCache/index.js"; import {ShufflingCache} from "./shufflingCache.js"; +import {BlockRewards} from "./rewards/blockRewards.js"; export {BlockType, type AssembledBlockType}; export {type ProposerPreparationData}; diff --git a/packages/beacon-node/src/chain/rewards/blockRewards.ts b/packages/beacon-node/src/chain/rewards/blockRewards.ts index 4fddfeb22700..54b7ad63a91c 100644 --- a/packages/beacon-node/src/chain/rewards/blockRewards.ts +++ b/packages/beacon-node/src/chain/rewards/blockRewards.ts @@ -7,8 +7,9 @@ import { } from "@lodestar/state-transition"; import {allForks, altair, phase0} from "@lodestar/types"; import {ForkName, WHISTLEBLOWER_REWARD_QUOTIENT} from "@lodestar/params"; -import {type BlockRewards} from "@lodestar/api"; +import {routes} from "@lodestar/api"; +export type BlockRewards = routes.beacon.BlockRewards; type SubRewardValue = number; // All reward values should be integer /** From b6f68d038fbc0dd67c667bebacb1c3e77e709662 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Thu, 25 Jan 2024 16:37:44 +0100 Subject: [PATCH 23/26] Use jsdoc to document properties --- .../api/src/beacon/routes/beacon/rewards.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/api/src/beacon/routes/beacon/rewards.ts b/packages/api/src/beacon/routes/beacon/rewards.ts index fbbfbb02530d..42dced7d5c3f 100644 --- a/packages/api/src/beacon/routes/beacon/rewards.ts +++ b/packages/api/src/beacon/routes/beacon/rewards.ts @@ -24,12 +24,18 @@ export type ExecutionOptimistic = boolean; * Rewards info for a single block. Every reward value is in Gwei. */ export type BlockRewards = { - proposerIndex: ValidatorIndex; // proposer of the block, the proposer index who receives these rewards - total: number; // total block reward, equal to attestations + sync_aggregate + proposer_slashings + attester_slashings - attestations: number; // block reward component due to included attestations - syncAggregate: number; // block reward component due to included sync_aggregate - proposerSlashings: number; // block reward component due to included proposer_slashings - attesterSlashings: number; // block reward component due to included attester_slashings + /** Proposer of the block, the proposer index who receives these rewards */ + proposerIndex: ValidatorIndex; + /** Total block reward, equal to attestations + sync_aggregate + proposer_slashings + attester_slashings */ + total: number; + /** Block reward component due to included attestations */ + attestations: number; + /** Block reward component due to included sync_aggregate */ + syncAggregate: number; + /** Block reward component due to included proposer_slashings */ + proposerSlashings: number; + /** Block reward component due to included attester_slashings */ + attesterSlashings: number; }; export type Api = { From 5c10acfb7fc7443ea5bd99597b3ff15083c03a8c Mon Sep 17 00:00:00 2001 From: Cayman Date: Thu, 25 Jan 2024 12:10:45 -0500 Subject: [PATCH 24/26] Apply suggestions from code review --- packages/beacon-node/src/chain/rewards/blockRewards.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/beacon-node/src/chain/rewards/blockRewards.ts b/packages/beacon-node/src/chain/rewards/blockRewards.ts index 54b7ad63a91c..7fdfe126e122 100644 --- a/packages/beacon-node/src/chain/rewards/blockRewards.ts +++ b/packages/beacon-node/src/chain/rewards/blockRewards.ts @@ -27,7 +27,7 @@ export async function computeBlockRewards( ): Promise { const fork = preState.config.getForkName(block.slot); const {attestations: cachedAttestationsReward = 0, syncAggregate: cachedSyncAggregateReward = 0} = - postState?.proposerRewards || {}; + postState?.proposerRewards ?? {}; let blockAttestationReward = cachedAttestationsReward; let syncAggregateReward = cachedSyncAggregateReward; @@ -65,7 +65,7 @@ function computeBlockAttestationRewardPhase0( _block: phase0.BeaconBlock, _preState: CachedBeaconStatePhase0 ): SubRewardValue { - throw new Error("Unsupported fork! Block attestation reward calculation is not yet available in phase0"); + throw new Error("Unsupported fork! Block attestation reward calculation is not available in phase0"); } /** From 719ab1d6cefa9807ded9044b6130f03b1bae5038 Mon Sep 17 00:00:00 2001 From: NC Date: Tue, 30 Jan 2024 18:25:02 +0800 Subject: [PATCH 25/26] Add `getPreStateSync()` --- packages/beacon-node/src/chain/chain.ts | 10 ++- .../beacon-node/src/chain/regen/interface.ts | 1 + .../beacon-node/src/chain/regen/queued.ts | 64 +++++++++++-------- 3 files changed, 45 insertions(+), 30 deletions(-) diff --git a/packages/beacon-node/src/chain/chain.ts b/packages/beacon-node/src/chain/chain.ts index 016e5b5cb836..92da02ddab82 100644 --- a/packages/beacon-node/src/chain/chain.ts +++ b/packages/beacon-node/src/chain/chain.ts @@ -994,9 +994,13 @@ export class BeaconChain implements IBeaconChain { } async getBlockRewards(block: allForks.FullOrBlindedBeaconBlock): Promise { - const preState = (await this.regen.getPreState(block, {dontTransferCache: true}, RegenCaller.restApi)).clone(); + const preState = this.regen.getPreStateSync(block); const postState = this.regen.getStateSync(toHexString(block.stateRoot)) ?? undefined; - const result = computeBlockRewards(block, preState, postState); - return result; + + if (preState === null) { + throw Error(`Pre-state is unavailable given block's parent root ${toHexString(block.parentRoot)}`); + } + + return computeBlockRewards(block, preState, postState); } } diff --git a/packages/beacon-node/src/chain/regen/interface.ts b/packages/beacon-node/src/chain/regen/interface.ts index be481de9abc8..b861378ff440 100644 --- a/packages/beacon-node/src/chain/regen/interface.ts +++ b/packages/beacon-node/src/chain/regen/interface.ts @@ -35,6 +35,7 @@ export interface IStateRegenerator extends IStateRegeneratorInternal { dropCache(): void; dumpCacheSummary(): routes.lodestar.StateCacheItem[]; getStateSync(stateRoot: RootHex): CachedBeaconStateAllForks | null; + getPreStateSync(block: allForks.BeaconBlock): CachedBeaconStateAllForks | null; getCheckpointStateSync(cp: CheckpointHex): CachedBeaconStateAllForks | null; getClosestHeadState(head: ProtoBlock): CachedBeaconStateAllForks | null; pruneOnCheckpoint(finalizedEpoch: Epoch, justifiedEpoch: Epoch, headStateRoot: RootHex): void; diff --git a/packages/beacon-node/src/chain/regen/queued.ts b/packages/beacon-node/src/chain/regen/queued.ts index 928c2e399b9a..a65c462227f3 100644 --- a/packages/beacon-node/src/chain/regen/queued.ts +++ b/packages/beacon-node/src/chain/regen/queued.ts @@ -71,6 +71,40 @@ export class QueuedStateRegenerator implements IStateRegenerator { return this.stateCache.get(stateRoot); } + getPreStateSync(block: allForks.BeaconBlock): CachedBeaconStateAllForks | null { + const parentRoot = toHexString(block.parentRoot); + const parentBlock = this.forkChoice.getBlockHex(parentRoot); + if (!parentBlock) { + throw new RegenError({ + code: RegenErrorCode.BLOCK_NOT_IN_FORKCHOICE, + blockRoot: block.parentRoot, + }); + } + + const parentEpoch = computeEpochAtSlot(parentBlock.slot); + const blockEpoch = computeEpochAtSlot(block.slot); + + // Check the checkpoint cache (if the pre-state is a checkpoint state) + if (parentEpoch < blockEpoch) { + const checkpointState = this.checkpointStateCache.getLatest(parentRoot, blockEpoch); + if (checkpointState && computeEpochAtSlot(checkpointState.slot) === blockEpoch) { + return checkpointState; + } + } + + // Check the state cache, only if the state doesn't need to go through an epoch transition. + // Otherwise the state transition may not be cached and wasted. Queue for regen since the + // work required will still be significant. + if (parentEpoch === blockEpoch) { + const state = this.stateCache.get(parentBlock.stateRoot); + if (state) { + return state; + } + } + + return null; + } + getCheckpointStateSync(cp: CheckpointHex): CachedBeaconStateAllForks | null { return this.checkpointStateCache.get(cp); } @@ -137,34 +171,10 @@ export class QueuedStateRegenerator implements IStateRegenerator { this.metrics?.regenFnCallTotal.inc({caller: rCaller, entrypoint: RegenFnName.getPreState}); // First attempt to fetch the state from caches before queueing - const parentRoot = toHexString(block.parentRoot); - const parentBlock = this.forkChoice.getBlockHex(parentRoot); - if (!parentBlock) { - throw new RegenError({ - code: RegenErrorCode.BLOCK_NOT_IN_FORKCHOICE, - blockRoot: block.parentRoot, - }); - } + const cachedState = this.getPreStateSync(block); - const parentEpoch = computeEpochAtSlot(parentBlock.slot); - const blockEpoch = computeEpochAtSlot(block.slot); - - // Check the checkpoint cache (if the pre-state is a checkpoint state) - if (parentEpoch < blockEpoch) { - const checkpointState = this.checkpointStateCache.getLatest(parentRoot, blockEpoch); - if (checkpointState && computeEpochAtSlot(checkpointState.slot) === blockEpoch) { - return checkpointState; - } - } - - // Check the state cache, only if the state doesn't need to go through an epoch transition. - // Otherwise the state transition may not be cached and wasted. Queue for regen since the - // work required will still be significant. - if (parentEpoch === blockEpoch) { - const state = this.stateCache.get(parentBlock.stateRoot); - if (state) { - return state; - } + if (cachedState !== null) { + return cachedState; } // The state is not immediately available in the caches, enqueue the job From 51bbf5e02d04cfa565f7562029aaff794dcaf59f Mon Sep 17 00:00:00 2001 From: Tuyen Nguyen Date: Tue, 20 Feb 2024 09:11:42 +0700 Subject: [PATCH 26/26] fix: clone states to compute block rewards --- packages/beacon-node/src/chain/chain.ts | 2 +- packages/beacon-node/src/chain/rewards/blockRewards.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/beacon-node/src/chain/chain.ts b/packages/beacon-node/src/chain/chain.ts index 92da02ddab82..20a6ca343565 100644 --- a/packages/beacon-node/src/chain/chain.ts +++ b/packages/beacon-node/src/chain/chain.ts @@ -1001,6 +1001,6 @@ export class BeaconChain implements IBeaconChain { throw Error(`Pre-state is unavailable given block's parent root ${toHexString(block.parentRoot)}`); } - return computeBlockRewards(block, preState, postState); + return computeBlockRewards(block, preState.clone(), postState?.clone()); } } diff --git a/packages/beacon-node/src/chain/rewards/blockRewards.ts b/packages/beacon-node/src/chain/rewards/blockRewards.ts index 7fdfe126e122..bd8bf3537582 100644 --- a/packages/beacon-node/src/chain/rewards/blockRewards.ts +++ b/packages/beacon-node/src/chain/rewards/blockRewards.ts @@ -94,6 +94,7 @@ function computeSyncAggregateReward(block: altair.BeaconBlock, preState: CachedB return 0; // phase0 block does not have syncAggregate } } + /** * Calculate rewards received by block proposer for including proposer slashings. * All proposer slashing rewards go to block proposer and none to whistleblower as of Deneb