Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Merged by Bors] - Implement sync_committee_rewards API (per-validator reward) #3903

Closed
wants to merge 39 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
039173d
Added sync committee reward api blueprint
ensi321 Dec 1, 2022
e8ef118
Added sync committee reward api blueprint
ensi321 Dec 1, 2022
5bbdd46
lib.rs modification + add sync_committee_rewards
kevinbogner Dec 1, 2022
09705bd
change order
kevinbogner Dec 1, 2022
4cc09c6
add types and function to lib.rs
kevinbogner Dec 2, 2022
73ba07b
use declarations
kevinbogner Dec 2, 2022
6f3a7e6
change use declaration of BlockId
kevinbogner Dec 3, 2022
c3c4c33
get block, get state, convert slot, compute reward
kevinbogner Dec 3, 2022
a820451
Added sync_committee_attestation_rewards
ensi321 Dec 3, 2022
dda4c27
Partial implementation on sync committee reward api
ensi321 Dec 5, 2022
0b5d6ad
delete imports, change comments
kevinbogner Dec 7, 2022
4994c27
Updated logic to grab rewards
ensi321 Dec 8, 2022
29d1822
fix imports, add comment
kevinbogner Dec 9, 2022
afefce8
Changed fields to optional in sync_committee_attestation_rewards
ensi321 Dec 9, 2022
9cb4fb0
Fixed conflict
ensi321 Dec 9, 2022
552581b
Populate reward content in sync_committee_rewards
ensi321 Dec 10, 2022
7d5ae25
Handle case where validators is empty in sync_committee_rewards
ensi321 Dec 10, 2022
511f96b
Added debug messages
ensi321 Dec 10, 2022
46b3624
change data types + delete comment
kevinbogner Dec 12, 2022
37e8729
Handle case where participants are penalized
ensi321 Dec 14, 2022
aeaded7
add endpoint to common/eth2/src/lib.rs
kevinbogner Dec 15, 2022
9b29113
Rewrite logic for sync_committee_rewards
ensi321 Dec 15, 2022
04f9768
Rewrite logic for sync_committee_rewards
ensi321 Dec 15, 2022
93bdc09
Apply rewards to proposer balance
ensi321 Dec 16, 2022
662d723
Add sync committee rewards to beacon chain trait
ensi321 Dec 22, 2022
70a5cf3
Filter reward_payload by validators
ensi321 Dec 26, 2022
3a71579
Added initial ver of unit test for sync committee rewards
ensi321 Dec 27, 2022
3d3f71c
Use post state for sync committee rewards
ensi321 Dec 27, 2022
5627e5f
Added unit test for sync committee rewards
ensi321 Jan 7, 2023
992b56d
Clean up some of the code
ensi321 Jan 7, 2023
968fa24
Added log
ensi321 Jan 9, 2023
e29604b
Refactored naming
ensi321 Jan 9, 2023
0843f23
Polish code according to PR comments
ensi321 Jan 17, 2023
1f894aa
To be more specific with http status code it returns
ensi321 Jan 17, 2023
576fc80
Fix conflict
ensi321 Jan 17, 2023
fa4fe11
Format some of the code
ensi321 Jan 17, 2023
7a150e6
Fix lint error
ensi321 Jan 20, 2023
32bce39
Addressed comments
ensi321 Jan 23, 2023
1d746c0
Fix Clippy
michaelsproul Jan 24, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions beacon_node/beacon_chain/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ pub enum BeaconChainError {
BlockRewardSlotError,
BlockRewardAttestationError,
BlockRewardSyncError,
SyncCommitteeRewardsSyncError,
HeadMissingFromForkChoice(Hash256),
FinalizedBlockMissingFromForkChoice(Hash256),
HeadBlockMissingFromForkChoice(Hash256),
Expand Down
1 change: 1 addition & 0 deletions beacon_node/beacon_chain/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ pub mod schema_change;
mod shuffling_cache;
mod snapshot_cache;
pub mod state_advance_timer;
pub mod sync_committee_rewards;
pub mod sync_committee_verification;
pub mod test_utils;
mod timeout_rw_lock;
Expand Down
87 changes: 87 additions & 0 deletions beacon_node/beacon_chain/src/sync_committee_rewards.rs
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())
}
}
25 changes: 25 additions & 0 deletions beacon_node/beacon_chain/src/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ pub use crate::persisted_beacon_chain::PersistedBeaconChain;
pub use crate::{
beacon_chain::{BEACON_CHAIN_DB_KEY, ETH1_CACHE_DB_KEY, FORK_CHOICE_DB_KEY, OP_POOL_DB_KEY},
migrate::MigratorConfig,
sync_committee_verification::Error as SyncCommitteeError,
validator_monitor::DEFAULT_INDIVIDUAL_TRACKING_THRESHOLD,
BeaconChainError, NotifyExecutionLayer, ProduceBlockVerification,
};
Expand Down Expand Up @@ -1980,6 +1981,30 @@ where

(honest_head, faulty_head)
}

pub fn process_sync_contributions(
&self,
sync_contributions: HarnessSyncContributions<E>,
) -> Result<(), SyncCommitteeError> {
let mut verified_contributions = Vec::with_capacity(sync_contributions.len());

for (_, contribution_and_proof) in sync_contributions {
let signed_contribution_and_proof = contribution_and_proof.unwrap();

let verified_contribution = self
.chain
.verify_sync_contribution_for_gossip(signed_contribution_and_proof)?;

verified_contributions.push(verified_contribution);
}

for verified_contribution in verified_contributions {
self.chain
.add_contribution_to_block_inclusion_pool(verified_contribution)?;
}

Ok(())
}
}

// Junk `Debug` impl to satistfy certain trait bounds during testing.
Expand Down
1 change: 1 addition & 0 deletions beacon_node/beacon_chain/tests/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ mod block_verification;
mod merge;
mod op_verification;
mod payload_invalidation;
mod rewards;
mod store_tests;
mod sync_committee_verification;
mod tests;
121 changes: 121 additions & 0 deletions beacon_node/beacon_chain/tests/rewards.rs
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(",")
);
}
37 changes: 37 additions & 0 deletions beacon_node/http_api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ mod metrics;
mod proposer_duties;
mod publish_blocks;
mod state_id;
mod sync_committee_rewards;
mod sync_committees;
mod ui;
mod validator_inclusion;
Expand Down Expand Up @@ -1699,6 +1700,41 @@ pub fn serve<T: BeaconChainTypes>(
},
);

/*
* beacon/rewards
*/

let beacon_rewards_path = eth_v1
.and(warp::path("beacon"))
.and(warp::path("rewards"))
.and(chain_filter.clone());

// POST beacon/rewards/sync_committee/{block_id}
let post_beacon_rewards_sync_committee = beacon_rewards_path
.clone()
.and(warp::path("sync_committee"))
.and(block_id_or_err)
.and(warp::path::end())
.and(warp::body::json())
.and(log_filter.clone())
.and_then(
|chain: Arc<BeaconChain<T>>,
block_id: BlockId,
validators: Vec<ValidatorId>,
log: Logger| {
blocking_json_task(move || {
let (rewards, execution_optimistic) =
sync_committee_rewards::compute_sync_committee_rewards(
chain, block_id, validators, log,
)?;

Ok(rewards)
.map(api_types::GenericResponse::from)
.map(|resp| resp.add_execution_optimistic(execution_optimistic))
})
},
);

/*
* config
*/
Expand Down Expand Up @@ -3396,6 +3432,7 @@ pub fn serve<T: BeaconChainTypes>(
.or(post_beacon_pool_proposer_slashings.boxed())
.or(post_beacon_pool_voluntary_exits.boxed())
.or(post_beacon_pool_sync_committees.boxed())
.or(post_beacon_rewards_sync_committee.boxed())
.or(post_validator_duties_attester.boxed())
.or(post_validator_duties_sync.boxed())
.or(post_validator_aggregate_and_proofs.boxed())
Expand Down
Loading