From fb2eb85565ee6775162e229c50980be4ff94adc1 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 3c86a7aaa2d4..7c42d3176534 100644 --- a/packages/beacon-node/src/chain/chain.ts +++ b/packages/beacon-node/src/chain/chain.ts @@ -75,6 +75,7 @@ import {BlockAttributes, produceBlockBody} from "./produceBlock/produceBlockBody 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 {CheckpointStateCache} from "./stateCache/stateContextCheckpointsCache.js"; @@ -967,4 +968,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 7fa60fd76ace..3fc70618779e 100644 --- a/packages/beacon-node/src/chain/interface.ts +++ b/packages/beacon-node/src/chain/interface.ts @@ -38,6 +38,7 @@ import {IChainOptions} from "./options.js"; import {AssembledBlockType, BlockAttributes, BlockType} from "./produceBlock/produceBlockBody.js"; import {SeenAttestationDatas} from "./seenCache/seenAttestationData.js"; import {ShufflingCache} from "./shufflingCache.js"; +import { BlockRewards } from "./rewards/blockRewards.js"; export {BlockType, type AssembledBlockType}; export {type ProposerPreparationData}; @@ -173,6 +174,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 e72b6fa0581c..dae7de5b3349 100644 --- a/packages/state-transition/src/index.ts +++ b/packages/state-transition/src/index.ts @@ -60,3 +60,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 7b460e2844cb235dc93e93baef7fb15e9b3d2341 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 54b2537648cb..8511af2eea8e 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 cea591c8dd1b6fc2bdbfc63f87b15078ec173de4 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 ca4535254ff1744fac144ebeadead8a8a1d7cb7d 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 8511af2eea8e..55f1f6a3010a 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 f592160836c9..fe5d0da4ccd4 100644 --- a/packages/beacon-node/src/chain/interface.ts +++ b/packages/beacon-node/src/chain/interface.ts @@ -50,7 +50,7 @@ import {IChainOptions} from "./options.js"; import {AssembledBlockType, BlockAttributes, BlockType} from "./produceBlock/produceBlockBody.js"; import {SeenAttestationDatas} from "./seenCache/seenAttestationData.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 dae7de5b3349..12d1ecb0f807 100644 --- a/packages/state-transition/src/index.ts +++ b/packages/state-transition/src/index.ts @@ -61,4 +61,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 68b99d13d33d095fe6b0a229801a7d19647ca83f 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 c3bd1eea8075..badc688bb678 100644 --- a/packages/beacon-node/src/chain/chain.ts +++ b/packages/beacon-node/src/chain/chain.ts @@ -983,7 +983,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 05c5cd7c6b7f482ffcfea87821ed09654f0222a1 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 12d1ecb0f807..3cb93803447f 100644 --- a/packages/state-transition/src/index.ts +++ b/packages/state-transition/src/index.ts @@ -61,4 +61,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 61db8ab5bcbe0bab343c998d8582df355d517a21 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 c2eae823a28a93433a657a78d552c5bf887f5e48 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 03b249adfefff065802b69adba555733dc377d98 Mon Sep 17 00:00:00 2001 From: NC Date: Thu, 28 Dec 2023 14:36:15 +0800 Subject: [PATCH 09/26] Add endpoint definition for sync rewards --- .../api/src/beacon/routes/beacon/rewards.ts | 27 +++++++++++++++++++ .../api/test/unit/beacon/testData/beacon.ts | 5 ++++ .../src/api/impl/beacon/rewards/index.ts | 5 ++++ packages/beacon-node/src/chain/chain.ts | 8 ++++++ packages/beacon-node/src/chain/interface.ts | 2 ++ .../src/chain/rewards/syncCommitteeRewards.ts | 24 +++++++++++++++++ 6 files changed, 71 insertions(+) create mode 100644 packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts diff --git a/packages/api/src/beacon/routes/beacon/rewards.ts b/packages/api/src/beacon/routes/beacon/rewards.ts index e7455c00bd37..09e4f73da500 100644 --- a/packages/api/src/beacon/routes/beacon/rewards.ts +++ b/packages/api/src/beacon/routes/beacon/rewards.ts @@ -7,10 +7,12 @@ import { Schema, ReqSerializers, ContainerDataExecutionOptimistic, + ArrayOf, } from "../../../utils/index.js"; import {HttpStatusCode} from "../../../utils/client/httpStatusCode.js"; import {ApiClientResponse} from "../../../interfaces.js"; import {BlockId} from "./block.js"; +import { ValidatorId } from "./state.js"; // See /packages/api/src/routes/index.ts for reasoning and instructions to add new routes @@ -29,6 +31,8 @@ export type ProposerRewardsResponse = { attesterSlashings: number; }; +export type SyncCommitteeRewardsResponse = {validatorIndex: ValidatorIndex, reward: number}[]; + export type Api = { /** * Get block rewards @@ -45,6 +49,10 @@ export type Api = { HttpStatusCode.BAD_REQUEST | HttpStatusCode.NOT_FOUND > >; + getSyncCommitteeRewards(blockId: BlockId, filters?: ValidatorId[]): Promise>; }; /** @@ -52,11 +60,13 @@ export type Api = { */ export const routesData: RoutesData = { getProposerRewards: {url: "/eth/v1/beacon/rewards/blocks/{block_id}", method: "GET"}, + getSyncCommitteeRewards: {url: "/eth/v1/beacon/rewards/sync_committee/{block_id}", method: "POST"}, }; export type ReqTypes = { /* eslint-disable @typescript-eslint/naming-convention */ getProposerRewards: {params: {block_id: string}}; + getSyncCommitteeRewards: {params: {block_id: string}; body: ValidatorId[]}; }; export function getReqSerializers(): ReqSerializers { @@ -66,6 +76,14 @@ export function getReqSerializers(): ReqSerializers { parseReq: ({params}) => [params.block_id], schema: {params: {block_id: Schema.StringRequired}}, }, + getSyncCommitteeRewards: { + writeReq: (block_id, filters) => ({params: {block_id: String(block_id)}, body: filters || []}), + parseReq: ({params, body}) => [params.block_id, body], + schema: { + params: {block_id: Schema.StringRequired}, + body: Schema.UintOrStringArray, + } + } }; } @@ -82,7 +100,16 @@ export function getReturnTypes(): ReturnTypes { {jsonCase: "eth2"} ); + const SyncCommitteeRewards = new ContainerType( + { + validatorIndex: ssz.ValidatorIndex, + reward: ssz.UintNum64, + }, + {jsonCase: "eth2"} + ); + return { getProposerRewards: ContainerDataExecutionOptimistic(ProposerRewardsResponse), + getSyncCommitteeRewards: ContainerDataExecutionOptimistic(ArrayOf(SyncCommitteeRewards)), }; } diff --git a/packages/api/test/unit/beacon/testData/beacon.ts b/packages/api/test/unit/beacon/testData/beacon.ts index cd62bcbdf705..3908f77e8beb 100644 --- a/packages/api/test/unit/beacon/testData/beacon.ts +++ b/packages/api/test/unit/beacon/testData/beacon.ts @@ -12,6 +12,7 @@ import {GenericServerTestCases} from "../../../utils/genericServerTest.js"; const root = new Uint8Array(32).fill(1); const randao = new Uint8Array(32).fill(1); const balance = 32e9; +const reward = 32e9; const pubkeyHex = toHexString(Buffer.alloc(48, 1)); const blockHeaderResponse: BlockHeaderResponse = { @@ -184,6 +185,10 @@ export const testData: GenericServerTestCases = { }, }, }, + getSyncCommitteeRewards: { + args: ["head", ["1300"]], + res: {executionOptimistic: true, data: [{validatorIndex: 1300, reward}]}, + }, // - 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..89529288ed46 100644 --- a/packages/beacon-node/src/api/impl/beacon/rewards/index.ts +++ b/packages/beacon-node/src/api/impl/beacon/rewards/index.ts @@ -9,5 +9,10 @@ export function getBeaconRewardsApi({chain}: Pick): ServerA const data = await chain.getBlockRewards(block.message); return {data, executionOptimistic}; }, + async getSyncCommitteeRewards(blockId, filters = []) { + const {block, executionOptimistic} = await resolveBlockId(chain, blockId); + const data = await chain.getSyncCommitteeRewards(block.message, filters); + return {data, executionOptimistic}; + }, }; } diff --git a/packages/beacon-node/src/chain/chain.ts b/packages/beacon-node/src/chain/chain.ts index badc688bb678..f3a9390b0d5e 100644 --- a/packages/beacon-node/src/chain/chain.ts +++ b/packages/beacon-node/src/chain/chain.ts @@ -81,6 +81,7 @@ import {BlockRewards, computeBlockRewards} from "./rewards/blockRewards.js"; import {ShufflingCache} from "./shufflingCache.js"; import {StateContextCache} from "./stateCache/stateContextCache.js"; import {CheckpointStateCache} from "./stateCache/stateContextCheckpointsCache.js"; +import { SyncCommitteeRewards, computeSyncCommitteeRewards } from "./rewards/syncCommitteeRewards.js"; /** * Arbitrary constants, blobs and payloads should be consumed immediately in the same slot @@ -988,4 +989,11 @@ export class BeaconChain implements IBeaconChain { const result = computeBlockRewards(block, preState); return result; } + + async getSyncCommitteeRewards(block: allForks.FullOrBlindedBeaconBlock, filter?: (ValidatorIndex | string)[]): 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 = computeSyncCommitteeRewards(block, preState); + return result; + } } diff --git a/packages/beacon-node/src/chain/interface.ts b/packages/beacon-node/src/chain/interface.ts index fe5d0da4ccd4..f3ba42e87a9f 100644 --- a/packages/beacon-node/src/chain/interface.ts +++ b/packages/beacon-node/src/chain/interface.ts @@ -51,6 +51,7 @@ import {AssembledBlockType, BlockAttributes, BlockType} from "./produceBlock/pro import {SeenAttestationDatas} from "./seenCache/seenAttestationData.js"; import {ShufflingCache} from "./shufflingCache.js"; import {BlockRewards} from "./rewards/blockRewards.js"; +import { SyncCommitteeRewards } from "./rewards/syncCommitteeRewards.js"; export {BlockType, type AssembledBlockType}; export {type ProposerPreparationData}; @@ -191,6 +192,7 @@ export interface IBeaconChain { blsThreadPoolCanAcceptWork(): boolean; getBlockRewards(blockRef: allForks.FullOrBlindedBeaconBlock): Promise; + getSyncCommitteeRewards(blockRef: allForks.FullOrBlindedBeaconBlock, filter?: (ValidatorIndex | string)[]): Promise; } export type SSZObjectType = diff --git a/packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts b/packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts new file mode 100644 index 000000000000..64565be895c7 --- /dev/null +++ b/packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts @@ -0,0 +1,24 @@ +import { + CachedBeaconStateAllForks, + CachedBeaconStateAltair, + CachedBeaconStatePhase0, + getAttesterSlashableIndices, + processAttestationsAltair, +} from "@lodestar/state-transition"; +import {ValidatorIndex, allForks, altair, phase0} from "@lodestar/types"; +import {ForkName, WHISTLEBLOWER_REWARD_QUOTIENT} from "@lodestar/params"; + + +export type SyncCommitteeRewards = { + validatorIndex: ValidatorIndex; + reward: number; +}[]; + +export async function computeSyncCommitteeRewards(block: allForks.BeaconBlock, state: CachedBeaconStateAllForks, filters?: (ValidatorIndex | string)[]): Promise { + const fork = state.config.getForkName(block.slot); + if (fork === ForkName.phase0) { + throw Error("Cannot get sync rewards as phase0 block does not have sync committee!"); + } + + return []; +} \ No newline at end of file From 503ca6052ed86ab87437231f69747397700de36b Mon Sep 17 00:00:00 2001 From: navie Date: Thu, 25 Jan 2024 19:24:41 +0800 Subject: [PATCH 10/26] Add calculation logic --- .../src/chain/rewards/syncCommitteeRewards.ts | 45 +++++++++++++++---- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts b/packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts index 64565be895c7..ee2742e0e27b 100644 --- a/packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts +++ b/packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts @@ -1,24 +1,53 @@ import { CachedBeaconStateAllForks, CachedBeaconStateAltair, - CachedBeaconStatePhase0, - getAttesterSlashableIndices, - processAttestationsAltair, } from "@lodestar/state-transition"; import {ValidatorIndex, allForks, altair, phase0} from "@lodestar/types"; -import {ForkName, WHISTLEBLOWER_REWARD_QUOTIENT} from "@lodestar/params"; +import {ForkName, SYNC_COMMITTEE_SIZE} from "@lodestar/params"; +// Note: This excludes sync aggregate reward. The reward for proposer here only reflect the sync committee participation portion export type SyncCommitteeRewards = { validatorIndex: ValidatorIndex; reward: number; }[]; -export async function computeSyncCommitteeRewards(block: allForks.BeaconBlock, state: CachedBeaconStateAllForks, filters?: (ValidatorIndex | string)[]): Promise { - const fork = state.config.getForkName(block.slot); +export async function computeSyncCommitteeRewards(block: allForks.BeaconBlock, preState: CachedBeaconStateAllForks, filters?: (ValidatorIndex | string)[]): Promise { + const fork = preState.config.getForkName(block.slot); if (fork === ForkName.phase0) { throw Error("Cannot get sync rewards as phase0 block does not have sync committee!"); } - return []; -} \ No newline at end of file + const altairBlock = block as altair.BeaconBlock; + const preStateAltair = preState as CachedBeaconStateAltair; + const {index2pubkey} = preStateAltair.epochCtx; + + + // Bound committeeIndices in case it goes beyond SYNC_COMMITTEE_SIZE just to be safe + const committeeIndices = preStateAltair.epochCtx.currentSyncCommitteeIndexed.validatorIndices.slice(0, SYNC_COMMITTEE_SIZE); + const {syncParticipantReward} = preStateAltair.epochCtx; + const {syncCommitteeBits} = altairBlock.body.syncAggregate; + + // Use balance of each committee as starting point such that we cap the penalty to avoid balance dropping below 0 + const balances: Map = new Map(committeeIndices.map(i => [i, {val: preStateAltair.balances.get(i)}])); // Use val for convenient way to increment/decrement balance + + for (const i of committeeIndices) { + const balanceRecord = balances.get(i)!; // We are certain i is in balances + if (syncCommitteeBits.get(i)) { + // Positive rewards for participants + balanceRecord.val += syncParticipantReward; + } else { + // Negative rewards for non participants + balanceRecord.val = Math.max(0, balanceRecord.val - syncParticipantReward); + } + } + + const rewards = Array.from(balances, ([validatorIndex, v]) => ({validatorIndex, reward: v.val})); + + if (filters !== undefined) { + // Might be a bit slow. But this is only called by rewards api which is insensitive to performance + return rewards.filter(reward => filters.includes(reward.validatorIndex) || filters.includes(index2pubkey[reward.validatorIndex].toHex())); + } else { + return Array.from(balances, ([validatorIndex, v]) => ({validatorIndex, reward: v.val})); + } +} From 4470cd5d30308f631e96202853368249eab483b9 Mon Sep 17 00:00:00 2001 From: NC Date: Thu, 25 Jan 2024 19:40:37 +0800 Subject: [PATCH 11/26] Lint --- .../api/src/beacon/routes/beacon/rewards.ts | 17 +++++++---- packages/beacon-node/src/chain/chain.ts | 9 ++++-- packages/beacon-node/src/chain/interface.ts | 7 +++-- .../src/chain/rewards/syncCommitteeRewards.ts | 30 ++++++++++++------- 4 files changed, 41 insertions(+), 22 deletions(-) diff --git a/packages/api/src/beacon/routes/beacon/rewards.ts b/packages/api/src/beacon/routes/beacon/rewards.ts index 09e4f73da500..cf3a46ae3bb6 100644 --- a/packages/api/src/beacon/routes/beacon/rewards.ts +++ b/packages/api/src/beacon/routes/beacon/rewards.ts @@ -12,7 +12,7 @@ import { import {HttpStatusCode} from "../../../utils/client/httpStatusCode.js"; import {ApiClientResponse} from "../../../interfaces.js"; import {BlockId} from "./block.js"; -import { ValidatorId } from "./state.js"; +import {ValidatorId} from "./state.js"; // See /packages/api/src/routes/index.ts for reasoning and instructions to add new routes @@ -31,7 +31,7 @@ export type ProposerRewardsResponse = { attesterSlashings: number; }; -export type SyncCommitteeRewardsResponse = {validatorIndex: ValidatorIndex, reward: number}[]; +export type SyncCommitteeRewardsResponse = {validatorIndex: ValidatorIndex; reward: number}[]; export type Api = { /** @@ -49,10 +49,15 @@ export type Api = { HttpStatusCode.BAD_REQUEST | HttpStatusCode.NOT_FOUND > >; - getSyncCommitteeRewards(blockId: BlockId, filters?: ValidatorId[]): Promise>; + > + >; }; /** @@ -82,8 +87,8 @@ export function getReqSerializers(): ReqSerializers { schema: { params: {block_id: Schema.StringRequired}, body: Schema.UintOrStringArray, - } - } + }, + }, }; } diff --git a/packages/beacon-node/src/chain/chain.ts b/packages/beacon-node/src/chain/chain.ts index 0febeab4cb7e..a47d8f4c4260 100644 --- a/packages/beacon-node/src/chain/chain.ts +++ b/packages/beacon-node/src/chain/chain.ts @@ -81,7 +81,7 @@ import {ShufflingCache} from "./shufflingCache.js"; import {StateContextCache} from "./stateCache/stateContextCache.js"; import {SeenGossipBlockInput} from "./seenCache/index.js"; import {CheckpointStateCache} from "./stateCache/stateContextCheckpointsCache.js"; -import { SyncCommitteeRewards, computeSyncCommitteeRewards } from "./rewards/syncCommitteeRewards.js"; +import {SyncCommitteeRewards, computeSyncCommitteeRewards} from "./rewards/syncCommitteeRewards.js"; /** * Arbitrary constants, blobs and payloads should be consumed immediately in the same slot @@ -1001,10 +1001,13 @@ export class BeaconChain implements IBeaconChain { return result; } - async getSyncCommitteeRewards(block: allForks.FullOrBlindedBeaconBlock, filter?: (ValidatorIndex | string)[]): Promise{ + async getSyncCommitteeRewards( + block: allForks.FullOrBlindedBeaconBlock, + filter?: (ValidatorIndex | string)[] + ): 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 = computeSyncCommitteeRewards(block, preState); + const result = computeSyncCommitteeRewards(block, preState, filter); return result; } } diff --git a/packages/beacon-node/src/chain/interface.ts b/packages/beacon-node/src/chain/interface.ts index dd1b3a02e0ab..af80b1538ea0 100644 --- a/packages/beacon-node/src/chain/interface.ts +++ b/packages/beacon-node/src/chain/interface.ts @@ -53,7 +53,7 @@ import {SeenAttestationDatas} from "./seenCache/seenAttestationData.js"; import {SeenGossipBlockInput} from "./seenCache/index.js"; import {ShufflingCache} from "./shufflingCache.js"; import {BlockRewards} from "./rewards/blockRewards.js"; -import { SyncCommitteeRewards } from "./rewards/syncCommitteeRewards.js"; +import {SyncCommitteeRewards} from "./rewards/syncCommitteeRewards.js"; export {BlockType, type AssembledBlockType}; export {type ProposerPreparationData}; @@ -202,7 +202,10 @@ export interface IBeaconChain { blsThreadPoolCanAcceptWork(): boolean; getBlockRewards(blockRef: allForks.FullOrBlindedBeaconBlock): Promise; - getSyncCommitteeRewards(blockRef: allForks.FullOrBlindedBeaconBlock, filter?: (ValidatorIndex | string)[]): Promise; + getSyncCommitteeRewards( + blockRef: allForks.FullOrBlindedBeaconBlock, + filter?: (ValidatorIndex | string)[] + ): Promise; } export type SSZObjectType = diff --git a/packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts b/packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts index ee2742e0e27b..41e8b7ae9f33 100644 --- a/packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts +++ b/packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts @@ -1,18 +1,18 @@ -import { - CachedBeaconStateAllForks, - CachedBeaconStateAltair, -} from "@lodestar/state-transition"; -import {ValidatorIndex, allForks, altair, phase0} from "@lodestar/types"; +import {CachedBeaconStateAllForks, CachedBeaconStateAltair} from "@lodestar/state-transition"; +import {ValidatorIndex, allForks, altair} from "@lodestar/types"; import {ForkName, SYNC_COMMITTEE_SIZE} from "@lodestar/params"; - // Note: This excludes sync aggregate reward. The reward for proposer here only reflect the sync committee participation portion export type SyncCommitteeRewards = { validatorIndex: ValidatorIndex; reward: number; }[]; -export async function computeSyncCommitteeRewards(block: allForks.BeaconBlock, preState: CachedBeaconStateAllForks, filters?: (ValidatorIndex | string)[]): Promise { +export async function computeSyncCommitteeRewards( + block: allForks.BeaconBlock, + preState: CachedBeaconStateAllForks, + filters?: (ValidatorIndex | string)[] +): Promise { const fork = preState.config.getForkName(block.slot); if (fork === ForkName.phase0) { throw Error("Cannot get sync rewards as phase0 block does not have sync committee!"); @@ -22,16 +22,21 @@ export async function computeSyncCommitteeRewards(block: allForks.BeaconBlock, const preStateAltair = preState as CachedBeaconStateAltair; const {index2pubkey} = preStateAltair.epochCtx; - // Bound committeeIndices in case it goes beyond SYNC_COMMITTEE_SIZE just to be safe - const committeeIndices = preStateAltair.epochCtx.currentSyncCommitteeIndexed.validatorIndices.slice(0, SYNC_COMMITTEE_SIZE); + const committeeIndices = preStateAltair.epochCtx.currentSyncCommitteeIndexed.validatorIndices.slice( + 0, + SYNC_COMMITTEE_SIZE + ); const {syncParticipantReward} = preStateAltair.epochCtx; const {syncCommitteeBits} = altairBlock.body.syncAggregate; // Use balance of each committee as starting point such that we cap the penalty to avoid balance dropping below 0 - const balances: Map = new Map(committeeIndices.map(i => [i, {val: preStateAltair.balances.get(i)}])); // Use val for convenient way to increment/decrement balance + const balances: Map = new Map( + committeeIndices.map((i) => [i, {val: preStateAltair.balances.get(i)}]) + ); // Use val for convenient way to increment/decrement balance for (const i of committeeIndices) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const balanceRecord = balances.get(i)!; // We are certain i is in balances if (syncCommitteeBits.get(i)) { // Positive rewards for participants @@ -46,7 +51,10 @@ export async function computeSyncCommitteeRewards(block: allForks.BeaconBlock, if (filters !== undefined) { // Might be a bit slow. But this is only called by rewards api which is insensitive to performance - return rewards.filter(reward => filters.includes(reward.validatorIndex) || filters.includes(index2pubkey[reward.validatorIndex].toHex())); + return rewards.filter( + (reward) => + filters.includes(reward.validatorIndex) || filters.includes(index2pubkey[reward.validatorIndex].toHex()) + ); } else { return Array.from(balances, ([validatorIndex, v]) => ({validatorIndex, reward: v.val})); } From 5302c9372233b2c29d8fcb0da82c274035ef2d15 Mon Sep 17 00:00:00 2001 From: NC Date: Fri, 23 Feb 2024 13:26:11 +0800 Subject: [PATCH 12/26] Follow convention from block rewards --- packages/api/src/beacon/routes/beacon/index.ts | 2 +- packages/api/src/beacon/routes/beacon/rewards.ts | 12 ++++++++---- packages/beacon-node/src/chain/chain.ts | 11 +++++++---- .../src/chain/rewards/syncCommitteeRewards.ts | 7 ++----- 4 files changed, 18 insertions(+), 14 deletions(-) diff --git a/packages/api/src/beacon/routes/beacon/index.ts b/packages/api/src/beacon/routes/beacon/index.ts index 92fcc2093188..af1fcdaecfe0 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 {BlockRewards} from "./rewards.js"; +export type {BlockRewards, SyncCommitteeRewards} 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 24f4ea5e5384..44416fe4b77e 100644 --- a/packages/api/src/beacon/routes/beacon/rewards.ts +++ b/packages/api/src/beacon/routes/beacon/rewards.ts @@ -40,7 +40,11 @@ export type BlockRewards = { attesterSlashings: number; }; -export type SyncCommitteeRewardsResponse = {validatorIndex: ValidatorIndex; reward: number}[]; +/** + * Rewards info for sync committee participation. Every reward value is in Gwei. + * Note: This excludes sync aggregate reward. The reward for proposer here only reflect the sync committee participation portion + */ +export type SyncCommitteeRewards = {validatorIndex: ValidatorIndex; reward: number}[]; export type Api = { /** @@ -63,7 +67,7 @@ export type Api = { filters?: ValidatorId[] ): Promise< ApiClientResponse< - {[HttpStatusCode.OK]: {data: SyncCommitteeRewardsResponse; executionOptimistic: ExecutionOptimistic}}, + {[HttpStatusCode.OK]: {data: SyncCommitteeRewards; executionOptimistic: ExecutionOptimistic}}, HttpStatusCode.BAD_REQUEST | HttpStatusCode.NOT_FOUND > >; @@ -114,7 +118,7 @@ export function getReturnTypes(): ReturnTypes { {jsonCase: "eth2"} ); - const SyncCommitteeRewards = new ContainerType( + const SyncCommitteeRewardsResponse = new ContainerType( { validatorIndex: ssz.ValidatorIndex, reward: ssz.UintNum64, @@ -124,6 +128,6 @@ export function getReturnTypes(): ReturnTypes { return { getBlockRewards: ContainerDataExecutionOptimistic(BlockRewardsResponse), - getSyncCommitteeRewards: ContainerDataExecutionOptimistic(ArrayOf(SyncCommitteeRewards)), + getSyncCommitteeRewards: ContainerDataExecutionOptimistic(ArrayOf(SyncCommitteeRewardsResponse)), }; } diff --git a/packages/beacon-node/src/chain/chain.ts b/packages/beacon-node/src/chain/chain.ts index fe0c59503e3e..1b3cc43981a9 100644 --- a/packages/beacon-node/src/chain/chain.ts +++ b/packages/beacon-node/src/chain/chain.ts @@ -1009,9 +1009,12 @@ export class BeaconChain implements IBeaconChain { block: allForks.FullOrBlindedBeaconBlock, filter?: (ValidatorIndex | string)[] ): 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 = computeSyncCommitteeRewards(block, preState, filter); - return result; + const preState = this.regen.getPreStateSync(block); + + if (preState === null) { + throw Error(`Pre-state is unavailable given block's parent root ${toHexString(block.parentRoot)}`); + } + + return computeSyncCommitteeRewards(block, preState.clone(), filter); } } diff --git a/packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts b/packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts index 41e8b7ae9f33..09cde588e99a 100644 --- a/packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts +++ b/packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts @@ -1,12 +1,9 @@ import {CachedBeaconStateAllForks, CachedBeaconStateAltair} from "@lodestar/state-transition"; import {ValidatorIndex, allForks, altair} from "@lodestar/types"; import {ForkName, SYNC_COMMITTEE_SIZE} from "@lodestar/params"; +import {routes} from "@lodestar/api"; -// Note: This excludes sync aggregate reward. The reward for proposer here only reflect the sync committee participation portion -export type SyncCommitteeRewards = { - validatorIndex: ValidatorIndex; - reward: number; -}[]; +export type SyncCommitteeRewards = routes.beacon.SyncCommitteeRewards; export async function computeSyncCommitteeRewards( block: allForks.BeaconBlock, From b7447aa1c36645c75cf54713c8aaefec47878c4c Mon Sep 17 00:00:00 2001 From: NC Date: Fri, 23 Feb 2024 13:26:30 +0800 Subject: [PATCH 13/26] Include getSyncCommitteeRewards in unit test --- packages/api/test/unit/beacon/oapiSpec.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/api/test/unit/beacon/oapiSpec.test.ts b/packages/api/test/unit/beacon/oapiSpec.test.ts index 4d036fb2dd8d..500b524ccf05 100644 --- a/packages/api/test/unit/beacon/oapiSpec.test.ts +++ b/packages/api/test/unit/beacon/oapiSpec.test.ts @@ -87,7 +87,6 @@ const testDatas = { const ignoredOperations = [ /* missing route */ /* https://github.com/ChainSafe/lodestar/issues/5694 */ - "getSyncCommitteeRewards", "getAttestationsRewards", "getDepositSnapshot", // Won't fix for now, see https://github.com/ChainSafe/lodestar/issues/5697 "getBlindedBlock", // https://github.com/ChainSafe/lodestar/issues/5699 @@ -123,6 +122,7 @@ const ignoredProperties: Record = { getBlockAttestations: {response: ["finalized"]}, getStateV2: {response: ["finalized"]}, getBlockRewards: {response: ["finalized"]}, + getSyncCommitteeRewards: {response: ["finalized"]}, /* https://github.com/ChainSafe/lodestar/issues/6168 From ac6c4279f71702c8e8991a0f6d3d5e0886bd5626 Mon Sep 17 00:00:00 2001 From: NC Date: Mon, 26 Feb 2024 17:20:31 +0800 Subject: [PATCH 14/26] Update packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts Co-authored-by: Nico Flaig --- packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts b/packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts index 09cde588e99a..79136056c1a0 100644 --- a/packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts +++ b/packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts @@ -12,7 +12,7 @@ export async function computeSyncCommitteeRewards( ): Promise { const fork = preState.config.getForkName(block.slot); if (fork === ForkName.phase0) { - throw Error("Cannot get sync rewards as phase0 block does not have sync committee!"); + throw Error("Cannot get sync rewards as phase0 block does not have sync committee"); } const altairBlock = block as altair.BeaconBlock; From 2034dcc31a62138dfbe34ff22add4ffebd383002 Mon Sep 17 00:00:00 2001 From: NC Date: Mon, 26 Feb 2024 17:21:49 +0800 Subject: [PATCH 15/26] Update packages/beacon-node/src/api/impl/beacon/rewards/index.ts Co-authored-by: Nico Flaig --- packages/beacon-node/src/api/impl/beacon/rewards/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 406e03e490ba..2fdae0299747 100644 --- a/packages/beacon-node/src/api/impl/beacon/rewards/index.ts +++ b/packages/beacon-node/src/api/impl/beacon/rewards/index.ts @@ -9,7 +9,7 @@ export function getBeaconRewardsApi({chain}: Pick): ServerA const data = await chain.getBlockRewards(block.message); return {data, executionOptimistic}; }, - async getSyncCommitteeRewards(blockId, filters = []) { + async getSyncCommitteeRewards(blockId, filters) { const {block, executionOptimistic} = await resolveBlockId(chain, blockId); const data = await chain.getSyncCommitteeRewards(block.message, filters); return {data, executionOptimistic}; From d889c407b6b86eddc0e1d07e48fea359847f253a Mon Sep 17 00:00:00 2001 From: NC Date: Mon, 26 Feb 2024 17:37:07 +0800 Subject: [PATCH 16/26] Improve filtering logic --- .../beacon-node/src/chain/rewards/syncCommitteeRewards.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts b/packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts index 79136056c1a0..a5f469e18e17 100644 --- a/packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts +++ b/packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts @@ -45,12 +45,12 @@ export async function computeSyncCommitteeRewards( } const rewards = Array.from(balances, ([validatorIndex, v]) => ({validatorIndex, reward: v.val})); + const filtersSet = new Set(filters); if (filters !== undefined) { - // Might be a bit slow. But this is only called by rewards api which is insensitive to performance return rewards.filter( (reward) => - filters.includes(reward.validatorIndex) || filters.includes(index2pubkey[reward.validatorIndex].toHex()) + filtersSet.has(reward.validatorIndex) || filtersSet.has(index2pubkey[reward.validatorIndex].toHex()) ); } else { return Array.from(balances, ([validatorIndex, v]) => ({validatorIndex, reward: v.val})); From 39e057a79c5f8dc0d5d792a13a2fadf75258f319 Mon Sep 17 00:00:00 2001 From: NC Date: Mon, 26 Feb 2024 17:41:16 +0800 Subject: [PATCH 17/26] Early throw on empty preState in getBlockRewards --- packages/beacon-node/src/chain/chain.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/beacon-node/src/chain/chain.ts b/packages/beacon-node/src/chain/chain.ts index 1b3cc43981a9..006cc3e8244c 100644 --- a/packages/beacon-node/src/chain/chain.ts +++ b/packages/beacon-node/src/chain/chain.ts @@ -996,12 +996,13 @@ export class BeaconChain implements IBeaconChain { async getBlockRewards(block: allForks.FullOrBlindedBeaconBlock): Promise { const preState = this.regen.getPreStateSync(block); - const postState = this.regen.getStateSync(toHexString(block.stateRoot)) ?? undefined; if (preState === null) { throw Error(`Pre-state is unavailable given block's parent root ${toHexString(block.parentRoot)}`); } + const postState = this.regen.getStateSync(toHexString(block.stateRoot)) ?? undefined; + return computeBlockRewards(block, preState.clone(), postState?.clone()); } From 56a7941c44c7aac42e295ca09e5524394249b0a4 Mon Sep 17 00:00:00 2001 From: NC Date: Mon, 26 Feb 2024 17:53:53 +0800 Subject: [PATCH 18/26] Add jsdoc --- packages/api/src/beacon/routes/beacon/rewards.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/api/src/beacon/routes/beacon/rewards.ts b/packages/api/src/beacon/routes/beacon/rewards.ts index 44416fe4b77e..29c66bc575e7 100644 --- a/packages/api/src/beacon/routes/beacon/rewards.ts +++ b/packages/api/src/beacon/routes/beacon/rewards.ts @@ -62,6 +62,16 @@ export type Api = { HttpStatusCode.BAD_REQUEST | HttpStatusCode.NOT_FOUND > >; + + /** + * Get sync committee rewards + * Returns participant reward value for each sync committee member at the given block. Optional filters can be + * passed in to filter the result + * + * @param blockId Block identifier. + * Can be one of: "head" (canonical head in node's view), "genesis", "finalized", \, \. + * @param filters List of validator indices or pubkeys to filter in + */ getSyncCommitteeRewards( blockId: BlockId, filters?: ValidatorId[] From 45a158bb5b11c5409d5363546fd8cc59f1570be9 Mon Sep 17 00:00:00 2001 From: NC Date: Mon, 26 Feb 2024 18:02:04 +0800 Subject: [PATCH 19/26] Address comment --- .../src/chain/rewards/syncCommitteeRewards.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts b/packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts index a5f469e18e17..fd8ab6706e82 100644 --- a/packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts +++ b/packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts @@ -4,6 +4,7 @@ import {ForkName, SYNC_COMMITTEE_SIZE} from "@lodestar/params"; import {routes} from "@lodestar/api"; export type SyncCommitteeRewards = routes.beacon.SyncCommitteeRewards; +type BalanceRecord = {val: number}; // Use val for convenient way to increment/decrement balance export async function computeSyncCommitteeRewards( block: allForks.BeaconBlock, @@ -28,13 +29,12 @@ export async function computeSyncCommitteeRewards( const {syncCommitteeBits} = altairBlock.body.syncAggregate; // Use balance of each committee as starting point such that we cap the penalty to avoid balance dropping below 0 - const balances: Map = new Map( + const balances: Map = new Map( committeeIndices.map((i) => [i, {val: preStateAltair.balances.get(i)}]) - ); // Use val for convenient way to increment/decrement balance + ); for (const i of committeeIndices) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const balanceRecord = balances.get(i)!; // We are certain i is in balances + const balanceRecord = balances.get(i) as BalanceRecord; if (syncCommitteeBits.get(i)) { // Positive rewards for participants balanceRecord.val += syncParticipantReward; @@ -49,8 +49,7 @@ export async function computeSyncCommitteeRewards( if (filters !== undefined) { return rewards.filter( - (reward) => - filtersSet.has(reward.validatorIndex) || filtersSet.has(index2pubkey[reward.validatorIndex].toHex()) + (reward) => filtersSet.has(reward.validatorIndex) || filtersSet.has(index2pubkey[reward.validatorIndex].toHex()) ); } else { return Array.from(balances, ([validatorIndex, v]) => ({validatorIndex, reward: v.val})); From 41ca8875399c5691dfd2d1c25be0295d0fd8b159 Mon Sep 17 00:00:00 2001 From: NC Date: Mon, 26 Feb 2024 19:08:49 +0800 Subject: [PATCH 20/26] Clarify comment --- packages/api/src/beacon/routes/beacon/rewards.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/api/src/beacon/routes/beacon/rewards.ts b/packages/api/src/beacon/routes/beacon/rewards.ts index 29c66bc575e7..ac87d1d2ab03 100644 --- a/packages/api/src/beacon/routes/beacon/rewards.ts +++ b/packages/api/src/beacon/routes/beacon/rewards.ts @@ -42,7 +42,9 @@ export type BlockRewards = { /** * Rewards info for sync committee participation. Every reward value is in Gwei. - * Note: This excludes sync aggregate reward. The reward for proposer here only reflect the sync committee participation portion + * Note: In the case that block proposer is present in `SyncCommitteeRewards`, the reward value only reflects rewards for + * participating in sync committee. Please refer to `BlockRewards.syncAggregate` for rewards of proposer including sync committee + * outputs into their block */ export type SyncCommitteeRewards = {validatorIndex: ValidatorIndex; reward: number}[]; From 945d9b0471a76ece4beeaffffa4577c9230e1df9 Mon Sep 17 00:00:00 2001 From: NC Date: Thu, 29 Feb 2024 20:32:37 +0800 Subject: [PATCH 21/26] Address comment --- packages/api/src/beacon/routes/beacon/rewards.ts | 3 +-- packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/api/src/beacon/routes/beacon/rewards.ts b/packages/api/src/beacon/routes/beacon/rewards.ts index ac87d1d2ab03..779ae189e70f 100644 --- a/packages/api/src/beacon/routes/beacon/rewards.ts +++ b/packages/api/src/beacon/routes/beacon/rewards.ts @@ -67,8 +67,7 @@ export type Api = { /** * Get sync committee rewards - * Returns participant reward value for each sync committee member at the given block. Optional filters can be - * passed in to filter the result + * Returns participant reward value for each sync committee member at the given block. * * @param blockId Block identifier. * Can be one of: "head" (canonical head in node's view), "genesis", "finalized", \, \. diff --git a/packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts b/packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts index fd8ab6706e82..c960cc7eabc1 100644 --- a/packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts +++ b/packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts @@ -45,9 +45,9 @@ export async function computeSyncCommitteeRewards( } const rewards = Array.from(balances, ([validatorIndex, v]) => ({validatorIndex, reward: v.val})); - const filtersSet = new Set(filters); if (filters !== undefined) { + const filtersSet = new Set(filters); return rewards.filter( (reward) => filtersSet.has(reward.validatorIndex) || filtersSet.has(index2pubkey[reward.validatorIndex].toHex()) ); From f96efc4c364290be5b1f5e58ee972d6c5bacd2fd Mon Sep 17 00:00:00 2001 From: NC Date: Thu, 29 Feb 2024 20:33:04 +0800 Subject: [PATCH 22/26] Update packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts Co-authored-by: Nico Flaig --- packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts b/packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts index c960cc7eabc1..a0bd2ca220de 100644 --- a/packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts +++ b/packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts @@ -52,6 +52,6 @@ export async function computeSyncCommitteeRewards( (reward) => filtersSet.has(reward.validatorIndex) || filtersSet.has(index2pubkey[reward.validatorIndex].toHex()) ); } else { - return Array.from(balances, ([validatorIndex, v]) => ({validatorIndex, reward: v.val})); + return rewards; } } From 7ab065b5590fc2a2d52bef3a8d93ba8709f351bc Mon Sep 17 00:00:00 2001 From: NC Date: Thu, 29 Feb 2024 21:40:22 +0800 Subject: [PATCH 23/26] Improve naming of filters --- packages/api/src/beacon/routes/beacon/rewards.ts | 6 +++--- packages/beacon-node/src/api/impl/beacon/rewards/index.ts | 4 ++-- packages/beacon-node/src/chain/chain.ts | 4 ++-- packages/beacon-node/src/chain/interface.ts | 2 +- .../beacon-node/src/chain/rewards/syncCommitteeRewards.ts | 8 ++++---- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/api/src/beacon/routes/beacon/rewards.ts b/packages/api/src/beacon/routes/beacon/rewards.ts index 779ae189e70f..c7595794ecd2 100644 --- a/packages/api/src/beacon/routes/beacon/rewards.ts +++ b/packages/api/src/beacon/routes/beacon/rewards.ts @@ -71,11 +71,11 @@ export type Api = { * * @param blockId Block identifier. * Can be one of: "head" (canonical head in node's view), "genesis", "finalized", \, \. - * @param filters List of validator indices or pubkeys to filter in + * @param ids List of validator indices or pubkeys to filter in */ getSyncCommitteeRewards( blockId: BlockId, - filters?: ValidatorId[] + ids?: ValidatorId[] ): Promise< ApiClientResponse< {[HttpStatusCode.OK]: {data: SyncCommitteeRewards; executionOptimistic: ExecutionOptimistic}}, @@ -106,7 +106,7 @@ export function getReqSerializers(): ReqSerializers { schema: {params: {block_id: Schema.StringRequired}}, }, getSyncCommitteeRewards: { - writeReq: (block_id, filters) => ({params: {block_id: String(block_id)}, body: filters || []}), + writeReq: (block_id, ids) => ({params: {block_id: String(block_id)}, body: ids || []}), parseReq: ({params, body}) => [params.block_id, body], schema: { params: {block_id: Schema.StringRequired}, 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 2fdae0299747..780068ebd518 100644 --- a/packages/beacon-node/src/api/impl/beacon/rewards/index.ts +++ b/packages/beacon-node/src/api/impl/beacon/rewards/index.ts @@ -9,9 +9,9 @@ export function getBeaconRewardsApi({chain}: Pick): ServerA const data = await chain.getBlockRewards(block.message); return {data, executionOptimistic}; }, - async getSyncCommitteeRewards(blockId, filters) { + async getSyncCommitteeRewards(blockId, validatorIds) { const {block, executionOptimistic} = await resolveBlockId(chain, blockId); - const data = await chain.getSyncCommitteeRewards(block.message, filters); + const data = await chain.getSyncCommitteeRewards(block.message, validatorIds); return {data, executionOptimistic}; }, }; diff --git a/packages/beacon-node/src/chain/chain.ts b/packages/beacon-node/src/chain/chain.ts index 006cc3e8244c..08743165cd05 100644 --- a/packages/beacon-node/src/chain/chain.ts +++ b/packages/beacon-node/src/chain/chain.ts @@ -1008,7 +1008,7 @@ export class BeaconChain implements IBeaconChain { async getSyncCommitteeRewards( block: allForks.FullOrBlindedBeaconBlock, - filter?: (ValidatorIndex | string)[] + validatorIds?: (ValidatorIndex | string)[] ): Promise { const preState = this.regen.getPreStateSync(block); @@ -1016,6 +1016,6 @@ export class BeaconChain implements IBeaconChain { throw Error(`Pre-state is unavailable given block's parent root ${toHexString(block.parentRoot)}`); } - return computeSyncCommitteeRewards(block, preState.clone(), filter); + return computeSyncCommitteeRewards(block, preState.clone(), validatorIds); } } diff --git a/packages/beacon-node/src/chain/interface.ts b/packages/beacon-node/src/chain/interface.ts index af80b1538ea0..55f5ebf485a2 100644 --- a/packages/beacon-node/src/chain/interface.ts +++ b/packages/beacon-node/src/chain/interface.ts @@ -204,7 +204,7 @@ export interface IBeaconChain { getBlockRewards(blockRef: allForks.FullOrBlindedBeaconBlock): Promise; getSyncCommitteeRewards( blockRef: allForks.FullOrBlindedBeaconBlock, - filter?: (ValidatorIndex | string)[] + validatorIds?: (ValidatorIndex | string)[] ): Promise; } diff --git a/packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts b/packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts index a0bd2ca220de..df1515c83126 100644 --- a/packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts +++ b/packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts @@ -9,7 +9,7 @@ type BalanceRecord = {val: number}; // Use val for convenient way to increment/d export async function computeSyncCommitteeRewards( block: allForks.BeaconBlock, preState: CachedBeaconStateAllForks, - filters?: (ValidatorIndex | string)[] + validatorIds?: (ValidatorIndex | string)[] ): Promise { const fork = preState.config.getForkName(block.slot); if (fork === ForkName.phase0) { @@ -46,12 +46,12 @@ export async function computeSyncCommitteeRewards( const rewards = Array.from(balances, ([validatorIndex, v]) => ({validatorIndex, reward: v.val})); - if (filters !== undefined) { - const filtersSet = new Set(filters); + if (validatorIds !== undefined) { + const filtersSet = new Set(validatorIds); return rewards.filter( (reward) => filtersSet.has(reward.validatorIndex) || filtersSet.has(index2pubkey[reward.validatorIndex].toHex()) ); } else { - return rewards; + return Array.from(balances, ([validatorIndex, v]) => ({validatorIndex, reward: v.val})); } } From 2f10a9a511f8bdc811924c4c232e500f03f9eac8 Mon Sep 17 00:00:00 2001 From: NC Date: Thu, 29 Feb 2024 21:41:02 +0800 Subject: [PATCH 24/26] Lint --- packages/api/src/beacon/routes/beacon/rewards.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/api/src/beacon/routes/beacon/rewards.ts b/packages/api/src/beacon/routes/beacon/rewards.ts index c7595794ecd2..b57f93242cc5 100644 --- a/packages/api/src/beacon/routes/beacon/rewards.ts +++ b/packages/api/src/beacon/routes/beacon/rewards.ts @@ -67,7 +67,7 @@ export type Api = { /** * Get sync committee rewards - * Returns participant reward value for each sync committee member at the given block. + * Returns participant reward value for each sync committee member at the given block. * * @param blockId Block identifier. * Can be one of: "head" (canonical head in node's view), "genesis", "finalized", \, \. From 387cee7a5fe5eaff48c61930b801a18963b90e5b Mon Sep 17 00:00:00 2001 From: NC Date: Fri, 1 Mar 2024 13:04:15 +0800 Subject: [PATCH 25/26] Update packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts Co-authored-by: Nico Flaig --- packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts b/packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts index df1515c83126..ba45d03adbab 100644 --- a/packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts +++ b/packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts @@ -52,6 +52,6 @@ export async function computeSyncCommitteeRewards( (reward) => filtersSet.has(reward.validatorIndex) || filtersSet.has(index2pubkey[reward.validatorIndex].toHex()) ); } else { - return Array.from(balances, ([validatorIndex, v]) => ({validatorIndex, reward: v.val})); + return rewards; } } From 0fde64eacdc4fb1f00d9261e53129a0305a18423 Mon Sep 17 00:00:00 2001 From: NC Date: Fri, 1 Mar 2024 13:08:42 +0800 Subject: [PATCH 26/26] ids -> validatorIds --- packages/api/src/beacon/routes/beacon/rewards.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/api/src/beacon/routes/beacon/rewards.ts b/packages/api/src/beacon/routes/beacon/rewards.ts index b57f93242cc5..926cb3033f06 100644 --- a/packages/api/src/beacon/routes/beacon/rewards.ts +++ b/packages/api/src/beacon/routes/beacon/rewards.ts @@ -71,11 +71,11 @@ export type Api = { * * @param blockId Block identifier. * Can be one of: "head" (canonical head in node's view), "genesis", "finalized", \, \. - * @param ids List of validator indices or pubkeys to filter in + * @param validatorIds List of validator indices or pubkeys to filter in */ getSyncCommitteeRewards( blockId: BlockId, - ids?: ValidatorId[] + validatorIds?: ValidatorId[] ): Promise< ApiClientResponse< {[HttpStatusCode.OK]: {data: SyncCommitteeRewards; executionOptimistic: ExecutionOptimistic}}, @@ -106,7 +106,7 @@ export function getReqSerializers(): ReqSerializers { schema: {params: {block_id: Schema.StringRequired}}, }, getSyncCommitteeRewards: { - writeReq: (block_id, ids) => ({params: {block_id: String(block_id)}, body: ids || []}), + writeReq: (block_id, validatorIds) => ({params: {block_id: String(block_id)}, body: validatorIds || []}), parseReq: ({params, body}) => [params.block_id, body], schema: { params: {block_id: Schema.StringRequired},