diff --git a/Cargo.lock b/Cargo.lock index 1554ff5564..d461027e77 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2521,6 +2521,7 @@ dependencies = [ "lighthouse_network", "lighthouse_version", "logging", + "lru", "network", "parking_lot 0.12.1", "safe_arith", diff --git a/beacon_node/beacon_chain/src/block_reward.rs b/beacon_node/beacon_chain/src/block_reward.rs index 74a27d5f75..4b8b809d3f 100644 --- a/beacon_node/beacon_chain/src/block_reward.rs +++ b/beacon_node/beacon_chain/src/block_reward.rs @@ -2,7 +2,7 @@ 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, ExecPayload, Hash256, RelativeEpoch}; +use types::{BeaconBlockRef, BeaconState, EthSpec, ExecPayload, Hash256}; impl BeaconChain { pub fn compute_block_reward>( @@ -10,13 +10,13 @@ impl BeaconChain { block: BeaconBlockRef<'_, T::EthSpec, Payload>, block_root: Hash256, state: &BeaconState, + include_attestations: bool, ) -> 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 total_active_balance = state.get_total_active_balance()?; let mut per_attestation_rewards = block .body() .attestations() @@ -60,11 +60,24 @@ impl BeaconChain { .map(|cover| cover.fresh_validators_rewards) .collect(); + // Add the attestation data if desired. + let attestations = if include_attestations { + block + .body() + .attestations() + .iter() + .map(|a| a.data.clone()) + .collect() + } else { + vec![] + }; + let attestation_rewards = AttestationRewards { total: attestation_total, prev_epoch_total, curr_epoch_total, per_attestation_rewards, + attestations, }; // Sync committee rewards. diff --git a/beacon_node/beacon_chain/src/block_verification.rs b/beacon_node/beacon_chain/src/block_verification.rs index afdbaf13ee..c791a35f68 100644 --- a/beacon_node/beacon_chain/src/block_verification.rs +++ b/beacon_node/beacon_chain/src/block_verification.rs @@ -1235,7 +1235,7 @@ impl<'a, T: BeaconChainTypes> FullyVerifiedBlock<'a, T> { 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)?; + chain.compute_block_reward(block.message(), block_root, &state, true)?; event_handler.register(EventKind::BlockReward(block_reward)); } } diff --git a/beacon_node/http_api/Cargo.toml b/beacon_node/http_api/Cargo.toml index a34618c2ef..9dd2af7d17 100644 --- a/beacon_node/http_api/Cargo.toml +++ b/beacon_node/http_api/Cargo.toml @@ -31,7 +31,7 @@ execution_layer = {path = "../execution_layer"} parking_lot = "0.12.0" safe_arith = {path = "../../consensus/safe_arith"} task_executor = { path = "../../common/task_executor" } - +lru = "0.7.7" [dev-dependencies] store = { path = "../store" } diff --git a/beacon_node/http_api/src/block_rewards.rs b/beacon_node/http_api/src/block_rewards.rs index 154773aa95..0555037210 100644 --- a/beacon_node/http_api/src/block_rewards.rs +++ b/beacon_node/http_api/src/block_rewards.rs @@ -1,10 +1,17 @@ use beacon_chain::{BeaconChain, BeaconChainError, BeaconChainTypes, WhenSlotSkipped}; use eth2::lighthouse::{BlockReward, BlockRewardsQuery}; -use slog::{warn, Logger}; +use lru::LruCache; +use slog::{debug, warn, Logger}; use state_processing::BlockReplayer; use std::sync::Arc; -use warp_utils::reject::{beacon_chain_error, beacon_state_error, custom_bad_request}; +use types::BeaconBlock; +use warp_utils::reject::{ + beacon_chain_error, beacon_state_error, custom_bad_request, custom_server_error, +}; +const STATE_CACHE_SIZE: usize = 2; + +/// Fetch block rewards for blocks from the canonical chain. pub fn get_block_rewards( query: BlockRewardsQuery, chain: Arc>, @@ -50,8 +57,12 @@ pub fn get_block_rewards( 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)?; + let block_reward = chain.compute_block_reward( + block.message(), + block.canonical_root(), + state, + query.include_attestations, + )?; block_rewards.push(block_reward); Ok(()) })) @@ -78,3 +89,84 @@ pub fn get_block_rewards( Ok(block_rewards) } + +/// Compute block rewards for blocks passed in as input. +pub fn compute_block_rewards( + blocks: Vec>, + chain: Arc>, + log: Logger, +) -> Result, warp::Rejection> { + let mut block_rewards = Vec::with_capacity(blocks.len()); + let mut state_cache = LruCache::new(STATE_CACHE_SIZE); + + for block in blocks { + let parent_root = block.parent_root(); + + // Check LRU cache for a constructed state from a previous iteration. + let state = if let Some(state) = state_cache.get(&(parent_root, block.slot())) { + debug!( + log, + "Re-using cached state for block rewards"; + "parent_root" => ?parent_root, + "slot" => block.slot(), + ); + state + } else { + debug!( + log, + "Fetching state for block rewards"; + "parent_root" => ?parent_root, + "slot" => block.slot() + ); + let parent_block = chain + .get_blinded_block(&parent_root) + .map_err(beacon_chain_error)? + .ok_or_else(|| { + custom_bad_request(format!( + "parent block not known or not canonical: {:?}", + parent_root + )) + })?; + + let parent_state = chain + .get_state(&parent_block.state_root(), Some(parent_block.slot())) + .map_err(beacon_chain_error)? + .ok_or_else(|| { + custom_bad_request(format!( + "no state known for parent block: {:?}", + parent_root + )) + })?; + + let block_replayer = BlockReplayer::new(parent_state, &chain.spec) + .no_signature_verification() + .state_root_iter([Ok((parent_block.state_root(), parent_block.slot()))].into_iter()) + .minimal_block_root_verification() + .apply_blocks(vec![], Some(block.slot())) + .map_err(beacon_chain_error)?; + + if block_replayer.state_root_miss() { + warn!( + log, + "Block reward state root miss"; + "parent_slot" => parent_block.slot(), + "slot" => block.slot(), + ); + } + + state_cache + .get_or_insert((parent_root, block.slot()), || block_replayer.into_state()) + .ok_or_else(|| { + custom_server_error("LRU cache insert should always succeed".into()) + })? + }; + + // Compute block reward. + let block_reward = chain + .compute_block_reward(block.to_ref(), block.canonical_root(), state, true) + .map_err(beacon_chain_error)?; + block_rewards.push(block_reward); + } + + Ok(block_rewards) +} diff --git a/beacon_node/http_api/src/lib.rs b/beacon_node/http_api/src/lib.rs index be08b3f737..379033a113 100644 --- a/beacon_node/http_api/src/lib.rs +++ b/beacon_node/http_api/src/lib.rs @@ -2823,6 +2823,18 @@ pub fn serve( blocking_json_task(move || block_rewards::get_block_rewards(query, chain, log)) }); + // POST lighthouse/analysis/block_rewards + let post_lighthouse_block_rewards = warp::path("lighthouse") + .and(warp::path("analysis")) + .and(warp::path("block_rewards")) + .and(warp::body::json()) + .and(warp::path::end()) + .and(chain_filter.clone()) + .and(log_filter.clone()) + .and_then(|blocks, chain, log| { + blocking_json_task(move || block_rewards::compute_block_rewards(blocks, chain, log)) + }); + // GET lighthouse/analysis/attestation_performance/{index} let get_lighthouse_attestation_performance = warp::path("lighthouse") .and(warp::path("analysis")) @@ -2998,7 +3010,8 @@ pub fn serve( .or(post_validator_prepare_beacon_proposer.boxed()) .or(post_lighthouse_liveness.boxed()) .or(post_lighthouse_database_reconstruct.boxed()) - .or(post_lighthouse_database_historical_blocks.boxed()), + .or(post_lighthouse_database_historical_blocks.boxed()) + .or(post_lighthouse_block_rewards.boxed()), )) .recover(warp_utils::reject::handle_rejection) .with(slog_logging(log.clone())) diff --git a/common/eth2/src/lighthouse/block_rewards.rs b/common/eth2/src/lighthouse/block_rewards.rs index 186cbd888c..38070f3539 100644 --- a/common/eth2/src/lighthouse/block_rewards.rs +++ b/common/eth2/src/lighthouse/block_rewards.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use types::{Hash256, Slot}; +use types::{AttestationData, Hash256, Slot}; /// Details about the rewards paid to a block proposer for proposing a block. /// @@ -42,6 +42,9 @@ pub struct AttestationRewards { /// /// Each element of the vec is a map from validator index to reward. pub per_attestation_rewards: Vec>, + /// The attestations themselves (optional). + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub attestations: Vec, } /// Query parameters for the `/lighthouse/block_rewards` endpoint. @@ -51,4 +54,7 @@ pub struct BlockRewardsQuery { pub start_slot: Slot, /// Upper slot limit for block rewards returned (inclusive). pub end_slot: Slot, + /// Include the full attestations themselves? + #[serde(default)] + pub include_attestations: bool, }