forked from sigp/lighthouse
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement sync_committee_rewards API (per-validator reward) (sigp#3903)
[sigp#3661](sigp#3661) `/eth/v1/beacon/rewards/sync_committee/{block_id}` ``` { "execution_optimistic": false, "finalized": false, "data": [ { "validator_index": "0", "reward": "2000" } ] } ``` The issue contains the implementation of three per-validator reward APIs: * `sync_committee_rewards` * [`attestation_rewards`](sigp#3822) * `block_rewards` This PR only implements the `sync_committe_rewards `. The endpoints can be viewed in the Ethereum Beacon nodes API browser: [https://ethereum.github.io/beacon-APIs/?urls.primaryName=dev#/Rewards](https://ethereum.github.io/beacon-APIs/?urls.primaryName=dev#/Rewards) The implementation of [consensus client reward APIs](https://github.com/eth-protocol-fellows/cohort-three/blob/master/projects/project-ideas.md#consensus-client-reward-apis) is part of the [EPF](https://github.com/eth-protocol-fellows/cohort-three). Co-authored-by: navie <naviechan@gmail.com> Co-authored-by: kevinbogner <kevbogner@gmail.com>
- Loading branch information
1 parent
61f7943
commit 18d8d88
Showing
10 changed files
with
357 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
use crate::{BeaconChain, BeaconChainError, BeaconChainTypes}; | ||
|
||
use eth2::lighthouse::SyncCommitteeReward; | ||
use safe_arith::SafeArith; | ||
use slog::error; | ||
use state_processing::per_block_processing::altair::sync_committee::compute_sync_aggregate_rewards; | ||
use std::collections::HashMap; | ||
use store::RelativeEpoch; | ||
use types::{BeaconBlockRef, BeaconState, ExecPayload}; | ||
|
||
impl<T: BeaconChainTypes> BeaconChain<T> { | ||
pub fn compute_sync_committee_rewards<Payload: ExecPayload<T::EthSpec>>( | ||
&self, | ||
block: BeaconBlockRef<'_, T::EthSpec, Payload>, | ||
state: &mut BeaconState<T::EthSpec>, | ||
) -> Result<Vec<SyncCommitteeReward>, BeaconChainError> { | ||
if block.slot() != state.slot() { | ||
return Err(BeaconChainError::BlockRewardSlotError); | ||
} | ||
|
||
let spec = &self.spec; | ||
|
||
state.build_committee_cache(RelativeEpoch::Current, spec)?; | ||
|
||
let sync_aggregate = block.body().sync_aggregate()?; | ||
|
||
let sync_committee = state.current_sync_committee()?.clone(); | ||
|
||
let sync_committee_indices = state.get_sync_committee_indices(&sync_committee)?; | ||
|
||
let (participant_reward_value, proposer_reward_per_bit) = | ||
compute_sync_aggregate_rewards(state, spec).map_err(|e| { | ||
error!( | ||
self.log, "Error calculating sync aggregate rewards"; | ||
"error" => ?e | ||
); | ||
BeaconChainError::SyncCommitteeRewardsSyncError | ||
})?; | ||
|
||
let mut balances = HashMap::<usize, u64>::new(); | ||
|
||
let mut total_proposer_rewards = 0; | ||
let proposer_index = state.get_beacon_proposer_index(block.slot(), spec)?; | ||
|
||
// Apply rewards to participant balances. Keep track of proposer rewards | ||
for (validator_index, participant_bit) in sync_committee_indices | ||
.iter() | ||
.zip(sync_aggregate.sync_committee_bits.iter()) | ||
{ | ||
let participant_balance = balances | ||
.entry(*validator_index) | ||
.or_insert_with(|| state.balances()[*validator_index]); | ||
|
||
if participant_bit { | ||
participant_balance.safe_add_assign(participant_reward_value)?; | ||
|
||
balances | ||
.entry(proposer_index) | ||
.or_insert_with(|| state.balances()[proposer_index]) | ||
.safe_add_assign(proposer_reward_per_bit)?; | ||
|
||
total_proposer_rewards.safe_add_assign(proposer_reward_per_bit)?; | ||
} else { | ||
*participant_balance = participant_balance.saturating_sub(participant_reward_value); | ||
} | ||
} | ||
|
||
Ok(balances | ||
.iter() | ||
.filter_map(|(i, new_balance)| { | ||
let reward = if *i != proposer_index { | ||
*new_balance as i64 - state.balances()[*i] as i64 | ||
} else if sync_committee_indices.contains(i) { | ||
*new_balance as i64 | ||
- state.balances()[*i] as i64 | ||
- total_proposer_rewards as i64 | ||
} else { | ||
return None; | ||
}; | ||
Some(SyncCommitteeReward { | ||
validator_index: *i as u64, | ||
reward, | ||
}) | ||
}) | ||
.collect()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
#![cfg(test)] | ||
|
||
use std::collections::HashMap; | ||
|
||
use beacon_chain::test_utils::{ | ||
generate_deterministic_keypairs, BeaconChainHarness, EphemeralHarnessType, | ||
}; | ||
use beacon_chain::{ | ||
test_utils::{AttestationStrategy, BlockStrategy, RelativeSyncCommittee}, | ||
types::{Epoch, EthSpec, Keypair, MinimalEthSpec}, | ||
}; | ||
use lazy_static::lazy_static; | ||
|
||
pub const VALIDATOR_COUNT: usize = 64; | ||
|
||
lazy_static! { | ||
static ref KEYPAIRS: Vec<Keypair> = generate_deterministic_keypairs(VALIDATOR_COUNT); | ||
} | ||
|
||
fn get_harness<E: EthSpec>() -> BeaconChainHarness<EphemeralHarnessType<E>> { | ||
let mut spec = E::default_spec(); | ||
|
||
spec.altair_fork_epoch = Some(Epoch::new(0)); // We use altair for all tests | ||
|
||
let harness = BeaconChainHarness::builder(E::default()) | ||
.spec(spec) | ||
.keypairs(KEYPAIRS.to_vec()) | ||
.fresh_ephemeral_store() | ||
.build(); | ||
|
||
harness.advance_slot(); | ||
|
||
harness | ||
} | ||
|
||
#[tokio::test] | ||
async fn test_sync_committee_rewards() { | ||
let num_block_produced = MinimalEthSpec::slots_per_epoch(); | ||
let harness = get_harness::<MinimalEthSpec>(); | ||
|
||
let latest_block_root = harness | ||
.extend_chain( | ||
num_block_produced as usize, | ||
BlockStrategy::OnCanonicalHead, | ||
AttestationStrategy::AllValidators, | ||
) | ||
.await; | ||
|
||
// Create and add sync committee message to op_pool | ||
let sync_contributions = harness.make_sync_contributions( | ||
&harness.get_current_state(), | ||
latest_block_root, | ||
harness.get_current_slot(), | ||
RelativeSyncCommittee::Current, | ||
); | ||
|
||
harness | ||
.process_sync_contributions(sync_contributions) | ||
.unwrap(); | ||
|
||
// Add block | ||
let chain = &harness.chain; | ||
let (head_state, head_state_root) = harness.get_current_state_and_root(); | ||
let target_slot = harness.get_current_slot() + 1; | ||
|
||
let (block_root, mut state) = harness | ||
.add_attested_block_at_slot(target_slot, head_state, head_state_root, &[]) | ||
.await | ||
.unwrap(); | ||
|
||
let block = harness.get_block(block_root).unwrap(); | ||
let parent_block = chain | ||
.get_blinded_block(&block.parent_root()) | ||
.unwrap() | ||
.unwrap(); | ||
let parent_state = chain | ||
.get_state(&parent_block.state_root(), Some(parent_block.slot())) | ||
.unwrap() | ||
.unwrap(); | ||
|
||
let reward_payload = chain | ||
.compute_sync_committee_rewards(block.message(), &mut state) | ||
.unwrap(); | ||
|
||
let rewards = reward_payload | ||
.iter() | ||
.map(|reward| (reward.validator_index, reward.reward)) | ||
.collect::<HashMap<_, _>>(); | ||
|
||
let proposer_index = state | ||
.get_beacon_proposer_index(target_slot, &MinimalEthSpec::default_spec()) | ||
.unwrap(); | ||
|
||
let mut mismatches = vec![]; | ||
|
||
for validator in state.validators() { | ||
let validator_index = state | ||
.clone() | ||
.get_validator_index(&validator.pubkey) | ||
.unwrap() | ||
.unwrap(); | ||
let pre_state_balance = parent_state.balances()[validator_index]; | ||
let post_state_balance = state.balances()[validator_index]; | ||
let sync_committee_reward = rewards.get(&(validator_index as u64)).unwrap_or(&0); | ||
|
||
if validator_index == proposer_index { | ||
continue; // Ignore proposer | ||
} | ||
|
||
if pre_state_balance as i64 + *sync_committee_reward != post_state_balance as i64 { | ||
mismatches.push(validator_index.to_string()); | ||
} | ||
} | ||
|
||
assert_eq!( | ||
mismatches.len(), | ||
0, | ||
"Expect 0 mismatches, but these validators have mismatches on balance: {} ", | ||
mismatches.join(",") | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
use crate::{BlockId, ExecutionOptimistic}; | ||
use beacon_chain::{BeaconChain, BeaconChainError, BeaconChainTypes}; | ||
use eth2::lighthouse::SyncCommitteeReward; | ||
use eth2::types::ValidatorId; | ||
use slog::{debug, Logger}; | ||
use state_processing::BlockReplayer; | ||
use std::sync::Arc; | ||
use types::{BeaconState, SignedBlindedBeaconBlock}; | ||
use warp_utils::reject::{beacon_chain_error, custom_not_found}; | ||
|
||
pub fn compute_sync_committee_rewards<T: BeaconChainTypes>( | ||
chain: Arc<BeaconChain<T>>, | ||
block_id: BlockId, | ||
validators: Vec<ValidatorId>, | ||
log: Logger, | ||
) -> Result<(Option<Vec<SyncCommitteeReward>>, ExecutionOptimistic), warp::Rejection> { | ||
let (block, execution_optimistic) = block_id.blinded_block(&chain)?; | ||
|
||
let mut state = get_state_before_applying_block(chain.clone(), &block)?; | ||
|
||
let reward_payload = chain | ||
.compute_sync_committee_rewards(block.message(), &mut state) | ||
.map_err(beacon_chain_error)?; | ||
|
||
let data = if reward_payload.is_empty() { | ||
debug!(log, "compute_sync_committee_rewards returned empty"); | ||
None | ||
} else if validators.is_empty() { | ||
Some(reward_payload) | ||
} else { | ||
Some( | ||
reward_payload | ||
.into_iter() | ||
.filter(|reward| { | ||
validators.iter().any(|validator| match validator { | ||
ValidatorId::Index(i) => reward.validator_index == *i, | ||
ValidatorId::PublicKey(pubkey) => match state.get_validator_index(pubkey) { | ||
Ok(Some(i)) => reward.validator_index == i as u64, | ||
_ => false, | ||
}, | ||
}) | ||
}) | ||
.collect::<Vec<SyncCommitteeReward>>(), | ||
) | ||
}; | ||
|
||
Ok((data, execution_optimistic)) | ||
} | ||
|
||
fn get_state_before_applying_block<T: BeaconChainTypes>( | ||
chain: Arc<BeaconChain<T>>, | ||
block: &SignedBlindedBeaconBlock<T::EthSpec>, | ||
) -> Result<BeaconState<T::EthSpec>, warp::reject::Rejection> { | ||
let parent_block: SignedBlindedBeaconBlock<T::EthSpec> = chain | ||
.get_blinded_block(&block.parent_root()) | ||
.and_then(|maybe_block| { | ||
maybe_block.ok_or_else(|| BeaconChainError::MissingBeaconBlock(block.parent_root())) | ||
}) | ||
.map_err(|e| custom_not_found(format!("Parent block is not available! {:?}", e)))?; | ||
|
||
let parent_state = chain | ||
.get_state(&parent_block.state_root(), Some(parent_block.slot())) | ||
.and_then(|maybe_state| { | ||
maybe_state | ||
.ok_or_else(|| BeaconChainError::MissingBeaconState(parent_block.state_root())) | ||
}) | ||
.map_err(|e| custom_not_found(format!("Parent state is not available! {:?}", e)))?; | ||
|
||
let 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)?; | ||
|
||
Ok(replayer.into_state()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.