From e70daaa3b6ee70eb6a6f2c0a1759062943e08259 Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Thu, 27 Jan 2022 01:06:02 +0000 Subject: [PATCH] Implement API for block rewards (#2628) ## Proposed Changes Add an API endpoint for retrieving detailed information about block rewards. For information on usage see [the docs](https://github.com/sigp/lighthouse/blob/block-rewards-api/book/src/api-lighthouse.md#lighthouseblock_rewards), and the source. --- beacon_node/beacon_chain/src/block_reward.rs | 97 +++++++++++++++++++ .../beacon_chain/src/block_verification.rs | 13 +++ beacon_node/beacon_chain/src/errors.rs | 3 + beacon_node/beacon_chain/src/events.rs | 13 +++ beacon_node/beacon_chain/src/lib.rs | 1 + beacon_node/http_api/src/block_rewards.rs | 80 +++++++++++++++ beacon_node/http_api/src/lib.rs | 15 +++ beacon_node/operation_pool/src/lib.rs | 5 +- book/src/api-lighthouse.md | 42 +++++++- common/eth2/src/lib.rs | 2 + common/eth2/src/lighthouse.rs | 3 + common/eth2/src/lighthouse/block_rewards.rs | 54 +++++++++++ common/eth2/src/types.rs | 17 ++++ .../altair/sync_committee.rs | 37 ++++--- 14 files changed, 366 insertions(+), 16 deletions(-) create mode 100644 beacon_node/beacon_chain/src/block_reward.rs create mode 100644 beacon_node/http_api/src/block_rewards.rs create mode 100644 common/eth2/src/lighthouse/block_rewards.rs diff --git a/beacon_node/beacon_chain/src/block_reward.rs b/beacon_node/beacon_chain/src/block_reward.rs new file mode 100644 index 0000000000..83b204113f --- /dev/null +++ b/beacon_node/beacon_chain/src/block_reward.rs @@ -0,0 +1,97 @@ +use crate::{BeaconChain, BeaconChainError, BeaconChainTypes}; +use eth2::lighthouse::{AttestationRewards, BlockReward, BlockRewardMeta}; +use operation_pool::{AttMaxCover, MaxCover}; +use state_processing::per_block_processing::altair::sync_committee::compute_sync_aggregate_rewards; +use types::{BeaconBlockRef, BeaconState, EthSpec, Hash256, RelativeEpoch}; + +impl BeaconChain { + pub fn compute_block_reward( + &self, + block: BeaconBlockRef<'_, T::EthSpec>, + block_root: Hash256, + state: &BeaconState, + ) -> Result { + if block.slot() != state.slot() { + return Err(BeaconChainError::BlockRewardSlotError); + } + + let active_indices = state.get_cached_active_validator_indices(RelativeEpoch::Current)?; + let total_active_balance = state.get_total_balance(active_indices, &self.spec)?; + let mut per_attestation_rewards = block + .body() + .attestations() + .iter() + .map(|att| { + AttMaxCover::new(att, state, total_active_balance, &self.spec) + .ok_or(BeaconChainError::BlockRewardAttestationError) + }) + .collect::, _>>()?; + + // Update the attestation rewards for each previous attestation included. + // This is O(n^2) in the number of attestations n. + for i in 0..per_attestation_rewards.len() { + let (updated, to_update) = per_attestation_rewards.split_at_mut(i + 1); + let latest_att = &updated[i]; + + for att in to_update { + att.update_covering_set(latest_att.object(), latest_att.covering_set()); + } + } + + let mut prev_epoch_total = 0; + let mut curr_epoch_total = 0; + + for cover in &per_attestation_rewards { + for &reward in cover.fresh_validators_rewards.values() { + if cover.att.data.slot.epoch(T::EthSpec::slots_per_epoch()) == state.current_epoch() + { + curr_epoch_total += reward; + } else { + prev_epoch_total += reward; + } + } + } + + let attestation_total = prev_epoch_total + curr_epoch_total; + + // Drop the covers. + let per_attestation_rewards = per_attestation_rewards + .into_iter() + .map(|cover| cover.fresh_validators_rewards) + .collect(); + + let attestation_rewards = AttestationRewards { + total: attestation_total, + prev_epoch_total, + curr_epoch_total, + per_attestation_rewards, + }; + + // Sync committee rewards. + let sync_committee_rewards = if let Ok(sync_aggregate) = block.body().sync_aggregate() { + let (_, proposer_reward_per_bit) = compute_sync_aggregate_rewards(state, &self.spec) + .map_err(|_| BeaconChainError::BlockRewardSyncError)?; + sync_aggregate.sync_committee_bits.num_set_bits() as u64 * proposer_reward_per_bit + } else { + 0 + }; + + // Total, metadata + let total = attestation_total + sync_committee_rewards; + + let meta = BlockRewardMeta { + slot: block.slot(), + parent_slot: state.latest_block_header().slot, + proposer_index: block.proposer_index(), + graffiti: block.body().graffiti().as_utf8_lossy(), + }; + + Ok(BlockReward { + total, + block_root, + meta, + attestation_rewards, + sync_committee_rewards, + }) + } +} diff --git a/beacon_node/beacon_chain/src/block_verification.rs b/beacon_node/beacon_chain/src/block_verification.rs index c6d937c81e..a4a1dc31b9 100644 --- a/beacon_node/beacon_chain/src/block_verification.rs +++ b/beacon_node/beacon_chain/src/block_verification.rs @@ -53,6 +53,7 @@ use crate::{ }, metrics, BeaconChain, BeaconChainError, BeaconChainTypes, }; +use eth2::types::EventKind; use fork_choice::{ForkChoice, ForkChoiceStore, PayloadVerificationStatus}; use parking_lot::RwLockReadGuard; use proto_array::Block as ProtoBlock; @@ -1165,6 +1166,18 @@ impl<'a, T: BeaconChainTypes> FullyVerifiedBlock<'a, T> { metrics::stop_timer(committee_timer); + /* + * If we have block reward listeners, compute the block reward and push it to the + * event handler. + */ + if let Some(ref event_handler) = chain.event_handler { + if event_handler.has_block_reward_subscribers() { + let block_reward = + chain.compute_block_reward(block.message(), block_root, &state)?; + event_handler.register(EventKind::BlockReward(block_reward)); + } + } + /* * Perform `per_block_processing` on the block and state, returning early if the block is * invalid. diff --git a/beacon_node/beacon_chain/src/errors.rs b/beacon_node/beacon_chain/src/errors.rs index 70e288ec26..6920c06039 100644 --- a/beacon_node/beacon_chain/src/errors.rs +++ b/beacon_node/beacon_chain/src/errors.rs @@ -137,6 +137,9 @@ pub enum BeaconChainError { AltairForkDisabled, ExecutionLayerMissing, ExecutionForkChoiceUpdateFailed(execution_layer::Error), + BlockRewardSlotError, + BlockRewardAttestationError, + BlockRewardSyncError, HeadMissingFromForkChoice(Hash256), FinalizedBlockMissingFromForkChoice(Hash256), InvalidFinalizedPayloadShutdownError(TrySendError), diff --git a/beacon_node/beacon_chain/src/events.rs b/beacon_node/beacon_chain/src/events.rs index 459ccb457f..6f4415ef4f 100644 --- a/beacon_node/beacon_chain/src/events.rs +++ b/beacon_node/beacon_chain/src/events.rs @@ -15,6 +15,7 @@ pub struct ServerSentEventHandler { chain_reorg_tx: Sender>, contribution_tx: Sender>, late_head: Sender>, + block_reward_tx: Sender>, log: Logger, } @@ -32,6 +33,7 @@ impl ServerSentEventHandler { let (chain_reorg_tx, _) = broadcast::channel(capacity); let (contribution_tx, _) = broadcast::channel(capacity); let (late_head, _) = broadcast::channel(capacity); + let (block_reward_tx, _) = broadcast::channel(capacity); Self { attestation_tx, @@ -42,6 +44,7 @@ impl ServerSentEventHandler { chain_reorg_tx, contribution_tx, late_head, + block_reward_tx, log, } } @@ -67,6 +70,8 @@ impl ServerSentEventHandler { .map(|count| trace!(self.log, "Registering server-sent contribution and proof event"; "receiver_count" => count)), EventKind::LateHead(late_head) => self.late_head.send(EventKind::LateHead(late_head)) .map(|count| trace!(self.log, "Registering server-sent late head event"; "receiver_count" => count)), + EventKind::BlockReward(block_reward) => self.block_reward_tx.send(EventKind::BlockReward(block_reward)) + .map(|count| trace!(self.log, "Registering server-sent contribution and proof event"; "receiver_count" => count)), }; if let Err(SendError(event)) = result { trace!(self.log, "No receivers registered to listen for event"; "event" => ?event); @@ -105,6 +110,10 @@ impl ServerSentEventHandler { self.late_head.subscribe() } + pub fn subscribe_block_reward(&self) -> Receiver> { + self.block_reward_tx.subscribe() + } + pub fn has_attestation_subscribers(&self) -> bool { self.attestation_tx.receiver_count() > 0 } @@ -136,4 +145,8 @@ impl ServerSentEventHandler { pub fn has_late_head_subscribers(&self) -> bool { self.late_head.receiver_count() > 0 } + + pub fn has_block_reward_subscribers(&self) -> bool { + self.block_reward_tx.receiver_count() > 0 + } } diff --git a/beacon_node/beacon_chain/src/lib.rs b/beacon_node/beacon_chain/src/lib.rs index 768a869551..aff8657e86 100644 --- a/beacon_node/beacon_chain/src/lib.rs +++ b/beacon_node/beacon_chain/src/lib.rs @@ -5,6 +5,7 @@ mod beacon_chain; mod beacon_fork_choice_store; mod beacon_proposer_cache; mod beacon_snapshot; +pub mod block_reward; mod block_times_cache; mod block_verification; pub mod builder; diff --git a/beacon_node/http_api/src/block_rewards.rs b/beacon_node/http_api/src/block_rewards.rs new file mode 100644 index 0000000000..154773aa95 --- /dev/null +++ b/beacon_node/http_api/src/block_rewards.rs @@ -0,0 +1,80 @@ +use beacon_chain::{BeaconChain, BeaconChainError, BeaconChainTypes, WhenSlotSkipped}; +use eth2::lighthouse::{BlockReward, BlockRewardsQuery}; +use slog::{warn, Logger}; +use state_processing::BlockReplayer; +use std::sync::Arc; +use warp_utils::reject::{beacon_chain_error, beacon_state_error, custom_bad_request}; + +pub fn get_block_rewards( + query: BlockRewardsQuery, + chain: Arc>, + log: Logger, +) -> Result, warp::Rejection> { + let start_slot = query.start_slot; + let end_slot = query.end_slot; + let prior_slot = start_slot - 1; + + if start_slot > end_slot || start_slot == 0 { + return Err(custom_bad_request(format!( + "invalid start and end: {}, {}", + start_slot, end_slot + ))); + } + + let end_block_root = chain + .block_root_at_slot(end_slot, WhenSlotSkipped::Prev) + .map_err(beacon_chain_error)? + .ok_or_else(|| custom_bad_request(format!("block at end slot {} unknown", end_slot)))?; + + let blocks = chain + .store + .load_blocks_to_replay(start_slot, end_slot, end_block_root) + .map_err(|e| beacon_chain_error(e.into()))?; + + let state_root = chain + .state_root_at_slot(prior_slot) + .map_err(beacon_chain_error)? + .ok_or_else(|| custom_bad_request(format!("prior state at slot {} unknown", prior_slot)))?; + + let mut state = chain + .get_state(&state_root, Some(prior_slot)) + .and_then(|maybe_state| maybe_state.ok_or(BeaconChainError::MissingBeaconState(state_root))) + .map_err(beacon_chain_error)?; + + state + .build_all_caches(&chain.spec) + .map_err(beacon_state_error)?; + + let mut block_rewards = Vec::with_capacity(blocks.len()); + + let block_replayer = BlockReplayer::new(state, &chain.spec) + .pre_block_hook(Box::new(|state, block| { + // Compute block reward. + let block_reward = + chain.compute_block_reward(block.message(), block.canonical_root(), state)?; + block_rewards.push(block_reward); + Ok(()) + })) + .state_root_iter( + chain + .forwards_iter_state_roots_until(prior_slot, end_slot) + .map_err(beacon_chain_error)?, + ) + .no_signature_verification() + .minimal_block_root_verification() + .apply_blocks(blocks, None) + .map_err(beacon_chain_error)?; + + if block_replayer.state_root_miss() { + warn!( + log, + "Block reward state root miss"; + "start_slot" => start_slot, + "end_slot" => end_slot, + ); + } + + drop(block_replayer); + + Ok(block_rewards) +} diff --git a/beacon_node/http_api/src/lib.rs b/beacon_node/http_api/src/lib.rs index b0907a30c1..deadf68543 100644 --- a/beacon_node/http_api/src/lib.rs +++ b/beacon_node/http_api/src/lib.rs @@ -7,6 +7,7 @@ mod attester_duties; mod block_id; +mod block_rewards; mod database; mod metrics; mod proposer_duties; @@ -2540,6 +2541,16 @@ pub fn serve( }, ); + let get_lighthouse_block_rewards = warp::path("lighthouse") + .and(warp::path("block_rewards")) + .and(warp::query::()) + .and(warp::path::end()) + .and(chain_filter.clone()) + .and(log_filter.clone()) + .and_then(|query, chain, log| { + blocking_json_task(move || block_rewards::get_block_rewards(query, chain, log)) + }); + let get_events = eth1_v1 .and(warp::path("events")) .and(warp::path::end()) @@ -2576,6 +2587,9 @@ pub fn serve( api_types::EventTopic::LateHead => { event_handler.subscribe_late_head() } + api_types::EventTopic::BlockReward => { + event_handler.subscribe_block_reward() + } }; receivers.push(BroadcastStream::new(receiver).map(|msg| { @@ -2661,6 +2675,7 @@ pub fn serve( .or(get_lighthouse_beacon_states_ssz.boxed()) .or(get_lighthouse_staking.boxed()) .or(get_lighthouse_database_info.boxed()) + .or(get_lighthouse_block_rewards.boxed()) .or(get_events.boxed()), ) .or(warp::post().and( diff --git a/beacon_node/operation_pool/src/lib.rs b/beacon_node/operation_pool/src/lib.rs index 2cc3ffaf6b..c9b252ca11 100644 --- a/beacon_node/operation_pool/src/lib.rs +++ b/beacon_node/operation_pool/src/lib.rs @@ -6,15 +6,16 @@ mod metrics; mod persistence; mod sync_aggregate_id; +pub use attestation::AttMaxCover; +pub use max_cover::MaxCover; pub use persistence::{ PersistedOperationPool, PersistedOperationPoolAltair, PersistedOperationPoolBase, }; use crate::sync_aggregate_id::SyncAggregateId; -use attestation::AttMaxCover; use attestation_id::AttestationId; use attester_slashing::AttesterSlashingMaxCover; -use max_cover::{maximum_cover, MaxCover}; +use max_cover::maximum_cover; use parking_lot::RwLock; use state_processing::per_block_processing::errors::AttestationValidationError; use state_processing::per_block_processing::{ diff --git a/book/src/api-lighthouse.md b/book/src/api-lighthouse.md index 8ea35f7348..7836ac14a4 100644 --- a/book/src/api-lighthouse.md +++ b/book/src/api-lighthouse.md @@ -407,4 +407,44 @@ The endpoint will return immediately. See the beacon node logs for an indication ### `/lighthouse/database/historical_blocks` Manually provide `SignedBeaconBlock`s to backfill the database. This is intended -for use by Lighthouse developers during testing only. \ No newline at end of file +for use by Lighthouse developers during testing only. + +### `/lighthouse/block_rewards` + +Fetch information about the block rewards paid to proposers for a range of consecutive blocks. + +Two query parameters are required: + +* `start_slot` (inclusive): the slot of the first block to compute rewards for. +* `end_slot` (inclusive): the slot of the last block to compute rewards for. + +Example: + +```bash +curl "http://localhost:5052/lighthouse/block_rewards?start_slot=1&end_slot=32" | jq +``` + +```json +[ + { + "block_root": "0x51576c2fcf0ab68d7d93c65e6828e620efbb391730511ffa35584d6c30e51410", + "attestation_rewards": { + "total": 4941156, + }, + .. + }, + .. +] +``` + +Caveats: + +* Presently only attestation rewards are computed. +* The output format is verbose and subject to change. Please see [`BlockReward`][block_reward_src] + in the source. +* For maximum efficiency the `start_slot` should satisfy `start_slot % slots_per_restore_point == 1`. + This is because the state _prior_ to the `start_slot` needs to be loaded from the database, and + loading a state on a boundary is most efficient. + +[block_reward_src]: +https://github.com/sigp/lighthouse/tree/unstable/common/eth2/src/lighthouse/block_reward.rs \ No newline at end of file diff --git a/common/eth2/src/lib.rs b/common/eth2/src/lib.rs index 153667d7e9..8dc808c265 100644 --- a/common/eth2/src/lib.rs +++ b/common/eth2/src/lib.rs @@ -9,6 +9,7 @@ #[cfg(feature = "lighthouse")] pub mod lighthouse; +#[cfg(feature = "lighthouse")] pub mod lighthouse_vc; pub mod mixin; pub mod types; @@ -245,6 +246,7 @@ impl BeaconNodeHttpClient { } /// Perform a HTTP POST request, returning a JSON response. + #[cfg(feature = "lighthouse")] async fn post_with_response( &self, url: U, diff --git a/common/eth2/src/lighthouse.rs b/common/eth2/src/lighthouse.rs index a8993a39c5..10601556fa 100644 --- a/common/eth2/src/lighthouse.rs +++ b/common/eth2/src/lighthouse.rs @@ -1,5 +1,7 @@ //! This module contains endpoints that are non-standard and only available on Lighthouse servers. +mod block_rewards; + use crate::{ ok_or_error, types::{BeaconState, ChainSpec, Epoch, EthSpec, GenericResponse, ValidatorId}, @@ -12,6 +14,7 @@ use ssz::four_byte_option_impl; use ssz_derive::{Decode, Encode}; use store::{AnchorInfo, Split}; +pub use block_rewards::{AttestationRewards, BlockReward, BlockRewardMeta, BlockRewardsQuery}; pub use lighthouse_network::{types::SyncState, PeerInfo}; // Define "legacy" implementations of `Option` which use four bytes for encoding the union diff --git a/common/eth2/src/lighthouse/block_rewards.rs b/common/eth2/src/lighthouse/block_rewards.rs new file mode 100644 index 0000000000..186cbd888c --- /dev/null +++ b/common/eth2/src/lighthouse/block_rewards.rs @@ -0,0 +1,54 @@ +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use types::{Hash256, Slot}; + +/// Details about the rewards paid to a block proposer for proposing a block. +/// +/// All rewards in GWei. +/// +/// Presently this only counts attestation rewards, but in future should be expanded +/// to include information on slashings and sync committee aggregates too. +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +pub struct BlockReward { + /// Sum of all reward components. + pub total: u64, + /// Block root of the block that these rewards are for. + pub block_root: Hash256, + /// Metadata about the block, particularly reward-relevant metadata. + pub meta: BlockRewardMeta, + /// Rewards due to attestations. + pub attestation_rewards: AttestationRewards, + /// Sum of rewards due to sync committee signatures. + pub sync_committee_rewards: u64, +} + +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +pub struct BlockRewardMeta { + pub slot: Slot, + pub parent_slot: Slot, + pub proposer_index: u64, + pub graffiti: String, +} + +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +pub struct AttestationRewards { + /// Total block reward from attestations included. + pub total: u64, + /// Total rewards from previous epoch attestations. + pub prev_epoch_total: u64, + /// Total rewards from current epoch attestations. + pub curr_epoch_total: u64, + /// Vec of attestation rewards for each attestation included. + /// + /// Each element of the vec is a map from validator index to reward. + pub per_attestation_rewards: Vec>, +} + +/// Query parameters for the `/lighthouse/block_rewards` endpoint. +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +pub struct BlockRewardsQuery { + /// Lower slot limit for block rewards returned (inclusive). + pub start_slot: Slot, + /// Upper slot limit for block rewards returned (inclusive). + pub end_slot: Slot, +} diff --git a/common/eth2/src/types.rs b/common/eth2/src/types.rs index a761b9ed12..78567ad83c 100644 --- a/common/eth2/src/types.rs +++ b/common/eth2/src/types.rs @@ -10,6 +10,9 @@ use std::str::{from_utf8, FromStr}; use std::time::Duration; pub use types::*; +#[cfg(feature = "lighthouse")] +use crate::lighthouse::BlockReward; + /// An API error serializable to JSON. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(untagged)] @@ -839,6 +842,8 @@ pub enum EventKind { ChainReorg(SseChainReorg), ContributionAndProof(Box>), LateHead(SseLateHead), + #[cfg(feature = "lighthouse")] + BlockReward(BlockReward), } impl EventKind { @@ -852,6 +857,8 @@ impl EventKind { EventKind::ChainReorg(_) => "chain_reorg", EventKind::ContributionAndProof(_) => "contribution_and_proof", EventKind::LateHead(_) => "late_head", + #[cfg(feature = "lighthouse")] + EventKind::BlockReward(_) => "block_reward", } } @@ -904,6 +911,10 @@ impl EventKind { ServerError::InvalidServerSentEvent(format!("Contribution and Proof: {:?}", e)) })?, ))), + #[cfg(feature = "lighthouse")] + "block_reward" => Ok(EventKind::BlockReward(serde_json::from_str(data).map_err( + |e| ServerError::InvalidServerSentEvent(format!("Block Reward: {:?}", e)), + )?)), _ => Err(ServerError::InvalidServerSentEvent( "Could not parse event tag".to_string(), )), @@ -929,6 +940,8 @@ pub enum EventTopic { ChainReorg, ContributionAndProof, LateHead, + #[cfg(feature = "lighthouse")] + BlockReward, } impl FromStr for EventTopic { @@ -944,6 +957,8 @@ impl FromStr for EventTopic { "chain_reorg" => Ok(EventTopic::ChainReorg), "contribution_and_proof" => Ok(EventTopic::ContributionAndProof), "late_head" => Ok(EventTopic::LateHead), + #[cfg(feature = "lighthouse")] + "block_reward" => Ok(EventTopic::BlockReward), _ => Err("event topic cannot be parsed.".to_string()), } } @@ -960,6 +975,8 @@ impl fmt::Display for EventTopic { EventTopic::ChainReorg => write!(f, "chain_reorg"), EventTopic::ContributionAndProof => write!(f, "contribution_and_proof"), EventTopic::LateHead => write!(f, "late_head"), + #[cfg(feature = "lighthouse")] + EventTopic::BlockReward => write!(f, "block_reward"), } } } diff --git a/consensus/state_processing/src/per_block_processing/altair/sync_committee.rs b/consensus/state_processing/src/per_block_processing/altair/sync_committee.rs index 31386a8fb1..8358003e4b 100644 --- a/consensus/state_processing/src/per_block_processing/altair/sync_committee.rs +++ b/consensus/state_processing/src/per_block_processing/altair/sync_committee.rs @@ -42,19 +42,7 @@ pub fn process_sync_aggregate( } // Compute participant and proposer rewards - let total_active_balance = state.get_total_active_balance()?; - let total_active_increments = - total_active_balance.safe_div(spec.effective_balance_increment)?; - let total_base_rewards = get_base_reward_per_increment(total_active_balance, spec)? - .safe_mul(total_active_increments)?; - let max_participant_rewards = total_base_rewards - .safe_mul(SYNC_REWARD_WEIGHT)? - .safe_div(WEIGHT_DENOMINATOR)? - .safe_div(T::slots_per_epoch())?; - let participant_reward = max_participant_rewards.safe_div(T::SyncCommitteeSize::to_u64())?; - let proposer_reward = participant_reward - .safe_mul(PROPOSER_WEIGHT)? - .safe_div(WEIGHT_DENOMINATOR.safe_sub(PROPOSER_WEIGHT)?)?; + let (participant_reward, proposer_reward) = compute_sync_aggregate_rewards(state, spec)?; // Apply participant and proposer rewards let committee_indices = state.get_sync_committee_indices(¤t_sync_committee)?; @@ -73,3 +61,26 @@ pub fn process_sync_aggregate( Ok(()) } + +/// Compute the `(participant_reward, proposer_reward)` for a sync aggregate. +/// +/// The `state` should be the pre-state from the same slot as the block containing the aggregate. +pub fn compute_sync_aggregate_rewards( + state: &BeaconState, + spec: &ChainSpec, +) -> Result<(u64, u64), BlockProcessingError> { + let total_active_balance = state.get_total_active_balance()?; + let total_active_increments = + total_active_balance.safe_div(spec.effective_balance_increment)?; + let total_base_rewards = get_base_reward_per_increment(total_active_balance, spec)? + .safe_mul(total_active_increments)?; + let max_participant_rewards = total_base_rewards + .safe_mul(SYNC_REWARD_WEIGHT)? + .safe_div(WEIGHT_DENOMINATOR)? + .safe_div(T::slots_per_epoch())?; + let participant_reward = max_participant_rewards.safe_div(T::SyncCommitteeSize::to_u64())?; + let proposer_reward = participant_reward + .safe_mul(PROPOSER_WEIGHT)? + .safe_div(WEIGHT_DENOMINATOR.safe_sub(PROPOSER_WEIGHT)?)?; + Ok((participant_reward, proposer_reward)) +}