From 2b93c0eb0d02f381c22c6a8eadd122f98018a061 Mon Sep 17 00:00:00 2001 From: realbigsean Date: Fri, 14 Jul 2023 15:59:15 -0400 Subject: [PATCH 1/9] remove spec minimal feature gating in tests (#4468) * remove spec minimal feature gating in tests * do merge transition in overflow cache test --- .github/workflows/test-suite.yml | 8 +++--- .gitignore | 3 +- Makefile | 13 +++++---- beacon_node/Cargo.toml | 1 - beacon_node/beacon_chain/Cargo.toml | 1 - .../overflow_lru_cache.rs | 28 ++++++------------- .../execution_layer/src/test_utils/mod.rs | 4 +-- beacon_node/network/Cargo.toml | 1 - .../network/src/sync/block_lookups/tests.rs | 9 +++--- lighthouse/Cargo.toml | 2 +- 10 files changed, 28 insertions(+), 42 deletions(-) diff --git a/.github/workflows/test-suite.yml b/.github/workflows/test-suite.yml index 0285d63e50..939d548b30 100644 --- a/.github/workflows/test-suite.yml +++ b/.github/workflows/test-suite.yml @@ -119,8 +119,8 @@ jobs: repo-token: ${{ secrets.GITHUB_TOKEN }} - name: Run operation_pool tests for all known forks run: make test-op-pool - network-minimal-tests: - name: network-minimal-tests + network-tests: + name: network-tests runs-on: ubuntu-latest needs: cargo-fmt steps: @@ -131,8 +131,8 @@ jobs: uses: arduino/setup-protoc@e52d9eb8f7b63115df1ac544a1376fdbf5a39612 with: repo-token: ${{ secrets.GITHUB_TOKEN }} - - name: Run network tests for all known forks using the minimal spec - run: make test-network-minimal + - name: Run network tests for all known forks + run: make test-network slasher-tests: name: slasher-tests runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index 1e943347d4..bbae314541 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ genesis.ssz # IntelliJ /*.iml +.idea + # VSCode /.vscode -.idea diff --git a/Makefile b/Makefile index ad125f3244..bd49a9f90e 100644 --- a/Makefile +++ b/Makefile @@ -106,12 +106,12 @@ build-release-tarballs: # Runs the full workspace tests in **release**, without downloading any additional # test vectors. test-release: - cargo test --workspace --release --exclude ef_tests --exclude beacon_chain --exclude slasher + cargo test --workspace --release --exclude ef_tests --exclude beacon_chain --exclude slasher --exclude network # Runs the full workspace tests in **debug**, without downloading any additional test # vectors. test-debug: - cargo test --workspace --exclude ef_tests --exclude beacon_chain + cargo test --workspace --exclude ef_tests --exclude beacon_chain --exclude network # Runs cargo-fmt (linter). cargo-fmt: @@ -143,13 +143,14 @@ test-op-pool-%: --features 'beacon_chain/fork_from_env'\ -p operation_pool -test-network-minimal: $(patsubst %,test-network-minimal-%,$(FORKS)) +# Run the tests in the `network` crate for all known forks. +test-network: $(patsubst %,test-network-%,$(FORKS)) -test-network-minimal-%: +test-network-%: env FORK_NAME=$* cargo test --release \ - --features 'fork_from_env,spec-minimal'\ + --features 'fork_from_env' \ -p network - + # Run the tests in the `slasher` crate for all supported database backends. test-slasher: cargo test --release -p slasher --features lmdb diff --git a/beacon_node/Cargo.toml b/beacon_node/Cargo.toml index d3f43fad70..e25fad89ac 100644 --- a/beacon_node/Cargo.toml +++ b/beacon_node/Cargo.toml @@ -13,7 +13,6 @@ node_test_rig = { path = "../testing/node_test_rig" } [features] write_ssz_files = ["beacon_chain/write_ssz_files"] # Writes debugging .ssz files to /tmp during block processing. -spec-minimal = ["beacon_chain/spec-minimal"] [dependencies] eth2_config = { path = "../common/eth2_config" } diff --git a/beacon_node/beacon_chain/Cargo.toml b/beacon_node/beacon_chain/Cargo.toml index 77bb514cd8..f9c8be20f2 100644 --- a/beacon_node/beacon_chain/Cargo.toml +++ b/beacon_node/beacon_chain/Cargo.toml @@ -10,7 +10,6 @@ default = ["participation_metrics"] write_ssz_files = [] # Writes debugging .ssz files to /tmp during block processing. participation_metrics = [] # Exposes validator participation metrics to Prometheus. fork_from_env = [] # Initialise the harness chain spec from the FORK_NAME env variable -spec-minimal = ["kzg/minimal-spec"] [dev-dependencies] maplit = "1.0.2" diff --git a/beacon_node/beacon_chain/src/data_availability_checker/overflow_lru_cache.rs b/beacon_node/beacon_chain/src/data_availability_checker/overflow_lru_cache.rs index e969c283da..ad00dc32be 100644 --- a/beacon_node/beacon_chain/src/data_availability_checker/overflow_lru_cache.rs +++ b/beacon_node/beacon_chain/src/data_availability_checker/overflow_lru_cache.rs @@ -849,7 +849,6 @@ impl ssz::Decode for OverflowKey { #[cfg(test)] mod test { use super::*; - #[cfg(feature = "spec-minimal")] use crate::{ blob_verification::{ validate_blob_sidecar_for_gossip, verify_kzg_for_blob, GossipVerifiedBlob, @@ -859,31 +858,20 @@ mod test { eth1_finalization_cache::Eth1FinalizationData, test_utils::{BaseHarnessType, BeaconChainHarness, DiskHarnessType}, }; - #[cfg(feature = "spec-minimal")] + use execution_layer::test_utils::DEFAULT_TERMINAL_BLOCK; use fork_choice::PayloadVerificationStatus; - #[cfg(feature = "spec-minimal")] use logging::test_logger; - #[cfg(feature = "spec-minimal")] use slog::{info, Logger}; - #[cfg(feature = "spec-minimal")] use state_processing::ConsensusContext; - #[cfg(feature = "spec-minimal")] use std::collections::{BTreeMap, HashMap, VecDeque}; - #[cfg(feature = "spec-minimal")] use std::ops::AddAssign; - #[cfg(feature = "spec-minimal")] use store::{HotColdDB, ItemStore, LevelDB, StoreConfig}; - #[cfg(feature = "spec-minimal")] use tempfile::{tempdir, TempDir}; - #[cfg(feature = "spec-minimal")] use types::beacon_state::ssz_tagged_beacon_state; - #[cfg(feature = "spec-minimal")] use types::{ChainSpec, ExecPayload, MinimalEthSpec}; - #[cfg(feature = "spec-minimal")] const LOW_VALIDATOR_COUNT: usize = 32; - #[cfg(feature = "spec-minimal")] fn get_store_with_spec( db_path: &TempDir, spec: ChainSpec, @@ -906,7 +894,6 @@ mod test { } // get a beacon chain harness advanced to just before deneb fork - #[cfg(feature = "spec-minimal")] async fn get_deneb_chain( log: Logger, db_path: &TempDir, @@ -994,7 +981,6 @@ mod test { } #[tokio::test] - #[cfg(feature = "spec-minimal")] async fn ssz_tagged_beacon_state_encode_decode_equality() { type E = MinimalEthSpec; let altair_fork_epoch = Epoch::new(1); @@ -1011,6 +997,13 @@ mod test { spec.bellatrix_fork_epoch = Some(bellatrix_fork_epoch); spec.capella_fork_epoch = Some(capella_fork_epoch); spec.deneb_fork_epoch = Some(deneb_fork_epoch); + let genesis_block = execution_layer::test_utils::generate_genesis_block( + spec.terminal_total_difficulty, + DEFAULT_TERMINAL_BLOCK, + ) + .unwrap(); + spec.terminal_block_hash = genesis_block.block_hash; + spec.terminal_block_hash_activation_epoch = bellatrix_fork_epoch; let harness = BeaconChainHarness::builder(E::default()) .spec(spec) @@ -1069,7 +1062,6 @@ mod test { assert_eq!(state, decoded, "Encoded and decoded states should be equal"); } - #[cfg(feature = "spec-minimal")] async fn availability_pending_block( harness: &BeaconChainHarness>, log: Logger, @@ -1166,7 +1158,6 @@ mod test { } #[tokio::test] - #[cfg(feature = "spec-minimal")] async fn overflow_cache_test_insert_components() { type E = MinimalEthSpec; type T = DiskHarnessType; @@ -1287,7 +1278,6 @@ mod test { } #[tokio::test] - #[cfg(feature = "spec-minimal")] async fn overflow_cache_test_overflow() { type E = MinimalEthSpec; type T = DiskHarnessType; @@ -1447,7 +1437,6 @@ mod test { } #[tokio::test] - #[cfg(feature = "spec-minimal")] async fn overflow_cache_test_maintenance() { type E = MinimalEthSpec; type T = DiskHarnessType; @@ -1599,7 +1588,6 @@ mod test { } #[tokio::test] - #[cfg(feature = "spec-minimal")] async fn overflow_cache_test_persist_recover() { type E = MinimalEthSpec; type T = DiskHarnessType; diff --git a/beacon_node/execution_layer/src/test_utils/mod.rs b/beacon_node/execution_layer/src/test_utils/mod.rs index 98a385c6ca..7940cf9f71 100644 --- a/beacon_node/execution_layer/src/test_utils/mod.rs +++ b/beacon_node/execution_layer/src/test_utils/mod.rs @@ -25,8 +25,8 @@ use warp::{http::StatusCode, Filter, Rejection}; use crate::EngineCapabilities; pub use execution_block_generator::{ - generate_genesis_header, generate_pow_block, generate_random_blobs, Block, - ExecutionBlockGenerator, + generate_genesis_block, generate_genesis_header, generate_pow_block, generate_random_blobs, + Block, ExecutionBlockGenerator, }; pub use hook::Hook; pub use mock_builder::{Context as MockBuilderContext, MockBuilder, Operation, TestingBuilder}; diff --git a/beacon_node/network/Cargo.toml b/beacon_node/network/Cargo.toml index 3420e64684..fb0f7281d3 100644 --- a/beacon_node/network/Cargo.toml +++ b/beacon_node/network/Cargo.toml @@ -49,5 +49,4 @@ operation_pool = { path = "../operation_pool" } execution_layer = { path = "../execution_layer" } [features] -spec-minimal = ["beacon_chain/spec-minimal"] fork_from_env = ["beacon_chain/fork_from_env"] diff --git a/beacon_node/network/src/sync/block_lookups/tests.rs b/beacon_node/network/src/sync/block_lookups/tests.rs index ea6fee0af5..a061a98d32 100644 --- a/beacon_node/network/src/sync/block_lookups/tests.rs +++ b/beacon_node/network/src/sync/block_lookups/tests.rs @@ -1,4 +1,3 @@ -#![cfg(feature = "spec-minimal")] use std::sync::Arc; use crate::service::RequestId; @@ -117,7 +116,7 @@ impl TestRig { }; let (bundle, transactions) = execution_layer::test_utils::generate_random_blobs::( num_blobs, - &self.harness.chain.kzg.as_ref().unwrap(), + self.harness.chain.kzg.as_ref().unwrap(), ) .unwrap(); @@ -148,8 +147,8 @@ impl TestRig { block_parent_root: block.parent_root(), proposer_index: block.message().proposer_index(), blob: blob.clone(), - kzg_commitment: kzg_commitment.clone(), - kzg_proof: kzg_proof.clone(), + kzg_commitment, + kzg_proof, }); } } @@ -1393,7 +1392,7 @@ mod deneb_only { fn blobs_response_was_valid(mut self) -> Self { self.rig.expect_empty_network(); - if self.blobs.len() > 0 { + if !self.blobs.is_empty() { self.rig.expect_block_process(ResponseType::Blob); } self diff --git a/lighthouse/Cargo.toml b/lighthouse/Cargo.toml index ae79972e96..bbde006efc 100644 --- a/lighthouse/Cargo.toml +++ b/lighthouse/Cargo.toml @@ -17,7 +17,7 @@ modern = ["bls/supranational-force-adx"] # Uses the slower Milagro BLS library, which is written in native Rust. milagro = ["bls/milagro"] # Support minimal spec (used for testing only). -spec-minimal = ["beacon_node/spec-minimal"] +spec-minimal = [] # Support Gnosis spec and Gnosis Beacon Chain. gnosis = [] # Support slasher MDBX backend. From 18760822fe57868bde24b9e155814be2ce797af8 Mon Sep 17 00:00:00 2001 From: Pawan Dhananjay Date: Fri, 14 Jul 2023 13:16:48 -0700 Subject: [PATCH 2/9] Fix beta compiler warnings --- beacon_node/execution_layer/src/lib.rs | 8 ++++---- beacon_node/store/src/lib.rs | 1 - 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/beacon_node/execution_layer/src/lib.rs b/beacon_node/execution_layer/src/lib.rs index 7de5321412..5d5a27eb85 100644 --- a/beacon_node/execution_layer/src/lib.rs +++ b/beacon_node/execution_layer/src/lib.rs @@ -42,13 +42,13 @@ use tokio_stream::wrappers::WatchStream; use tree_hash::TreeHash; use types::beacon_block_body::KzgCommitments; use types::blob_sidecar::Blobs; +use types::KzgProofs; use types::{AbstractExecPayload, BeaconStateError, ExecPayload, VersionedHash}; use types::{ - BlindedPayload, BlockType, ChainSpec, Epoch, ExecutionBlockHash, ExecutionPayload, - ExecutionPayloadCapella, ExecutionPayloadDeneb, ExecutionPayloadMerge, ForkName, + BlindedPayload, BlockType, ChainSpec, Epoch, ExecutionPayloadCapella, ExecutionPayloadDeneb, + ExecutionPayloadMerge, }; -use types::{KzgProofs, Withdrawals}; -use types::{ProposerPreparationData, PublicKeyBytes, Signature, Slot, Transaction, Uint256}; +use types::{ProposerPreparationData, PublicKeyBytes, Signature, Slot, Transaction}; mod block_hash; mod engine_api; diff --git a/beacon_node/store/src/lib.rs b/beacon_node/store/src/lib.rs index cd2f2da2b9..e6b6e730e4 100644 --- a/beacon_node/store/src/lib.rs +++ b/beacon_node/store/src/lib.rs @@ -43,7 +43,6 @@ pub use metrics::scrape_for_metrics; use parking_lot::MutexGuard; use std::sync::Arc; use strum::{EnumString, IntoStaticStr}; -use types::blob_sidecar::BlobSidecarList; pub use types::*; pub type ColumnIter<'a> = Box), Error>> + 'a>; From e1d0724abf1789849fe22ecb0a6f641f2b3487f7 Mon Sep 17 00:00:00 2001 From: Pawan Dhananjay Date: Mon, 17 Jul 2023 13:29:12 -0700 Subject: [PATCH 3/9] Fix more beta compiler warnings --- beacon_node/execution_layer/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/beacon_node/execution_layer/src/lib.rs b/beacon_node/execution_layer/src/lib.rs index 2a1f319173..3c754a76c5 100644 --- a/beacon_node/execution_layer/src/lib.rs +++ b/beacon_node/execution_layer/src/lib.rs @@ -48,8 +48,8 @@ use types::{ use types::{ BlindedPayload, BlockType, ChainSpec, Epoch, ExecutionPayloadCapella, ExecutionPayloadMerge, }; -use types::{KzgProofs, Withdrawals}; -use types::{ProposerPreparationData, PublicKeyBytes, Signature, Slot, Transaction, Uint256}; +use types::{KzgProofs}; +use types::{ProposerPreparationData, PublicKeyBytes, Signature, Slot, Transaction}; mod block_hash; mod engine_api; From cffa56238491ea49808189ea6d9c1309526c4a81 Mon Sep 17 00:00:00 2001 From: realbigsean Date: Mon, 17 Jul 2023 16:32:09 -0400 Subject: [PATCH 4/9] cargo fmt --- beacon_node/execution_layer/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beacon_node/execution_layer/src/lib.rs b/beacon_node/execution_layer/src/lib.rs index 3c754a76c5..1f7023270f 100644 --- a/beacon_node/execution_layer/src/lib.rs +++ b/beacon_node/execution_layer/src/lib.rs @@ -42,13 +42,13 @@ use tokio_stream::wrappers::WatchStream; use tree_hash::TreeHash; use types::beacon_block_body::KzgCommitments; use types::blob_sidecar::Blobs; +use types::KzgProofs; use types::{ AbstractExecPayload, BeaconStateError, ExecPayload, ExecutionPayloadDeneb, VersionedHash, }; use types::{ BlindedPayload, BlockType, ChainSpec, Epoch, ExecutionPayloadCapella, ExecutionPayloadMerge, }; -use types::{KzgProofs}; use types::{ProposerPreparationData, PublicKeyBytes, Signature, Slot, Transaction}; mod block_hash; From fc7f1ba6b94ac3300338aad4d5168aaac5df3fac Mon Sep 17 00:00:00 2001 From: Jimmy Chen Date: Tue, 18 Jul 2023 01:48:40 +0000 Subject: [PATCH 5/9] Phase 0 attestation rewards via Beacon API (#4474) ## Issue Addressed Addresses #4026. Beacon-API spec [here](https://ethereum.github.io/beacon-APIs/?urls.primaryName=dev#/Beacon/getAttestationsRewards). Endpoint: `POST /eth/v1/beacon/rewards/attestations/{epoch}` This endpoint already supports post-Altair epochs. This PR adds support for phase 0 rewards calculation. ## Proposed Changes - [x] Attestation rewards API to support phase 0 rewards calculation, re-using logic from `state_processing`. Refactored `get_attestation_deltas` slightly to support computing deltas for a subset of validators. - [x] Add `inclusion_delay` to `ideal_rewards` (`beacon-API` spec update to follow) - [x] Add `inactivity` penalties to both `ideal_rewards` and `total_rewards` (`beacon-API` spec update to follow) - [x] Add tests to compute attestation rewards and compare results with beacon states ## Additional Notes - The extra penalty for missing attestations or being slashed during an inactivity leak is currently not included in the API response (for both phase 0 and Altair) in the spec. - I went with adding `inactivity` as a separate component rather than combining them with the 4 rewards, because this is how it was grouped in [the phase 0 spec](https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#get_attestation_deltas). During inactivity leak, all rewards include the optimal reward, and inactivity penalties are calculated separately (see below code snippet from the spec), so it would be quite confusing if we merge them. This would also work better with Altair, because there's no "cancelling" of rewards and inactivity penalties are more separate. - Altair calculation logic (to include inactivity penalties) to be updated in a follow-up PR. ```python def get_attestation_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: """ Return attestation reward/penalty deltas for each validator. """ source_rewards, source_penalties = get_source_deltas(state) target_rewards, target_penalties = get_target_deltas(state) head_rewards, head_penalties = get_head_deltas(state) inclusion_delay_rewards, _ = get_inclusion_delay_deltas(state) _, inactivity_penalties = get_inactivity_penalty_deltas(state) rewards = [ source_rewards[i] + target_rewards[i] + head_rewards[i] + inclusion_delay_rewards[i] for i in range(len(state.validators)) ] penalties = [ source_penalties[i] + target_penalties[i] + head_penalties[i] + inactivity_penalties[i] for i in range(len(state.validators)) ] return rewards, penalties ``` ## Example API Response
Click me ```json { "ideal_rewards": [ { "effective_balance": "1000000000", "head": "6638", "target": "6638", "source": "6638", "inclusion_delay": "9783", "inactivity": "0" }, { "effective_balance": "2000000000", "head": "13276", "target": "13276", "source": "13276", "inclusion_delay": "19565", "inactivity": "0" }, { "effective_balance": "3000000000", "head": "19914", "target": "19914", "source": "19914", "inclusion_delay": "29349", "inactivity": "0" }, { "effective_balance": "4000000000", "head": "26553", "target": "26553", "source": "26553", "inclusion_delay": "39131", "inactivity": "0" }, { "effective_balance": "5000000000", "head": "33191", "target": "33191", "source": "33191", "inclusion_delay": "48914", "inactivity": "0" }, { "effective_balance": "6000000000", "head": "39829", "target": "39829", "source": "39829", "inclusion_delay": "58697", "inactivity": "0" }, { "effective_balance": "7000000000", "head": "46468", "target": "46468", "source": "46468", "inclusion_delay": "68480", "inactivity": "0" }, { "effective_balance": "8000000000", "head": "53106", "target": "53106", "source": "53106", "inclusion_delay": "78262", "inactivity": "0" }, { "effective_balance": "9000000000", "head": "59744", "target": "59744", "source": "59744", "inclusion_delay": "88046", "inactivity": "0" }, { "effective_balance": "10000000000", "head": "66383", "target": "66383", "source": "66383", "inclusion_delay": "97828", "inactivity": "0" }, { "effective_balance": "11000000000", "head": "73021", "target": "73021", "source": "73021", "inclusion_delay": "107611", "inactivity": "0" }, { "effective_balance": "12000000000", "head": "79659", "target": "79659", "source": "79659", "inclusion_delay": "117394", "inactivity": "0" }, { "effective_balance": "13000000000", "head": "86298", "target": "86298", "source": "86298", "inclusion_delay": "127176", "inactivity": "0" }, { "effective_balance": "14000000000", "head": "92936", "target": "92936", "source": "92936", "inclusion_delay": "136959", "inactivity": "0" }, { "effective_balance": "15000000000", "head": "99574", "target": "99574", "source": "99574", "inclusion_delay": "146742", "inactivity": "0" }, { "effective_balance": "16000000000", "head": "106212", "target": "106212", "source": "106212", "inclusion_delay": "156525", "inactivity": "0" }, { "effective_balance": "17000000000", "head": "112851", "target": "112851", "source": "112851", "inclusion_delay": "166307", "inactivity": "0" }, { "effective_balance": "18000000000", "head": "119489", "target": "119489", "source": "119489", "inclusion_delay": "176091", "inactivity": "0" }, { "effective_balance": "19000000000", "head": "126127", "target": "126127", "source": "126127", "inclusion_delay": "185873", "inactivity": "0" }, { "effective_balance": "20000000000", "head": "132766", "target": "132766", "source": "132766", "inclusion_delay": "195656", "inactivity": "0" }, { "effective_balance": "21000000000", "head": "139404", "target": "139404", "source": "139404", "inclusion_delay": "205439", "inactivity": "0" }, { "effective_balance": "22000000000", "head": "146042", "target": "146042", "source": "146042", "inclusion_delay": "215222", "inactivity": "0" }, { "effective_balance": "23000000000", "head": "152681", "target": "152681", "source": "152681", "inclusion_delay": "225004", "inactivity": "0" }, { "effective_balance": "24000000000", "head": "159319", "target": "159319", "source": "159319", "inclusion_delay": "234787", "inactivity": "0" }, { "effective_balance": "25000000000", "head": "165957", "target": "165957", "source": "165957", "inclusion_delay": "244570", "inactivity": "0" }, { "effective_balance": "26000000000", "head": "172596", "target": "172596", "source": "172596", "inclusion_delay": "254352", "inactivity": "0" }, { "effective_balance": "27000000000", "head": "179234", "target": "179234", "source": "179234", "inclusion_delay": "264136", "inactivity": "0" }, { "effective_balance": "28000000000", "head": "185872", "target": "185872", "source": "185872", "inclusion_delay": "273918", "inactivity": "0" }, { "effective_balance": "29000000000", "head": "192510", "target": "192510", "source": "192510", "inclusion_delay": "283701", "inactivity": "0" }, { "effective_balance": "30000000000", "head": "199149", "target": "199149", "source": "199149", "inclusion_delay": "293484", "inactivity": "0" }, { "effective_balance": "31000000000", "head": "205787", "target": "205787", "source": "205787", "inclusion_delay": "303267", "inactivity": "0" }, { "effective_balance": "32000000000", "head": "212426", "target": "212426", "source": "212426", "inclusion_delay": "313050", "inactivity": "0" } ], "total_rewards": [ { "validator_index": "0", "head": "212426", "target": "212426", "source": "212426", "inclusion_delay": "313050", "inactivity": "0" }, { "validator_index": "32", "head": "212426", "target": "212426", "source": "212426", "inclusion_delay": "313050", "inactivity": "0" }, { "validator_index": "63", "head": "-357771", "target": "-357771", "source": "-357771", "inclusion_delay": "0", "inactivity": "0" } ] } ```
--- Cargo.lock | 1 + beacon_node/beacon_chain/Cargo.toml | 1 + .../beacon_chain/src/attestation_rewards.rs | 238 ++++++++++++++++-- beacon_node/beacon_chain/src/errors.rs | 4 +- beacon_node/beacon_chain/tests/rewards.rs | 192 +++++++++++++- beacon_node/http_api/src/lib.rs | 8 +- .../src/lighthouse/attestation_rewards.rs | 19 +- consensus/state_processing/src/common/base.rs | 12 + .../src/per_epoch_processing/base.rs | 2 +- .../base/rewards_and_penalties.rs | 101 +++++--- .../ef_tests/src/cases/epoch_processing.rs | 2 +- testing/ef_tests/src/cases/rewards.rs | 2 +- 12 files changed, 513 insertions(+), 69 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4bb3ab99e2..fda8cd761f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -630,6 +630,7 @@ dependencies = [ "eth1", "eth2", "ethereum_hashing", + "ethereum_serde_utils", "ethereum_ssz", "ethereum_ssz_derive", "execution_layer", diff --git a/beacon_node/beacon_chain/Cargo.toml b/beacon_node/beacon_chain/Cargo.toml index 7f884f561b..b537327fb3 100644 --- a/beacon_node/beacon_chain/Cargo.toml +++ b/beacon_node/beacon_chain/Cargo.toml @@ -27,6 +27,7 @@ operation_pool = { path = "../operation_pool" } rayon = "1.4.1" serde = "1.0.116" serde_derive = "1.0.116" +ethereum_serde_utils = "0.5.0" slog = { version = "2.5.2", features = ["max_level_trace"] } sloggers = { version = "2.1.1", features = ["json"] } slot_clock = { path = "../../common/slot_clock" } diff --git a/beacon_node/beacon_chain/src/attestation_rewards.rs b/beacon_node/beacon_chain/src/attestation_rewards.rs index 460bf18bcf..9fc21b668f 100644 --- a/beacon_node/beacon_chain/src/attestation_rewards.rs +++ b/beacon_node/beacon_chain/src/attestation_rewards.rs @@ -3,7 +3,8 @@ use eth2::lighthouse::attestation_rewards::{IdealAttestationRewards, TotalAttest use eth2::lighthouse::StandardAttestationRewards; use participation_cache::ParticipationCache; use safe_arith::SafeArith; -use slog::{debug, Logger}; +use serde_utils::quoted_u64::Quoted; +use slog::debug; use state_processing::{ common::altair::BaseRewardPerIncrement, per_epoch_processing::altair::{participation_cache, rewards_and_penalties::get_flag_weight}, @@ -15,32 +16,111 @@ use store::consts::altair::{ }; use types::consts::altair::WEIGHT_DENOMINATOR; -use types::{Epoch, EthSpec}; +use types::{BeaconState, Epoch, EthSpec}; use eth2::types::ValidatorId; +use state_processing::common::base::get_base_reward_from_effective_balance; +use state_processing::per_epoch_processing::base::rewards_and_penalties::{ + get_attestation_component_delta, get_attestation_deltas_all, get_attestation_deltas_subset, + get_inactivity_penalty_delta, get_inclusion_delay_delta, +}; +use state_processing::per_epoch_processing::base::validator_statuses::InclusionInfo; +use state_processing::per_epoch_processing::base::{ + TotalBalances, ValidatorStatus, ValidatorStatuses, +}; impl BeaconChain { pub fn compute_attestation_rewards( &self, epoch: Epoch, validators: Vec, - log: Logger, ) -> Result { - debug!(log, "computing attestation rewards"; "epoch" => epoch, "validator_count" => validators.len()); + debug!(self.log, "computing attestation rewards"; "epoch" => epoch, "validator_count" => validators.len()); // Get state - let spec = &self.spec; - let state_slot = (epoch + 1).end_slot(T::EthSpec::slots_per_epoch()); let state_root = self .state_root_at_slot(state_slot)? .ok_or(BeaconChainError::NoStateForSlot(state_slot))?; - let mut state = self + let state = self .get_state(&state_root, Some(state_slot))? .ok_or(BeaconChainError::MissingBeaconState(state_root))?; + match state { + BeaconState::Base(_) => self.compute_attestation_rewards_base(state, validators), + BeaconState::Altair(_) | BeaconState::Merge(_) | BeaconState::Capella(_) => { + self.compute_attestation_rewards_altair(state, validators) + } + } + } + + fn compute_attestation_rewards_base( + &self, + mut state: BeaconState, + validators: Vec, + ) -> Result { + let spec = &self.spec; + let mut validator_statuses = ValidatorStatuses::new(&state, spec)?; + validator_statuses.process_attestations(&state)?; + + let ideal_rewards = + self.compute_ideal_rewards_base(&state, &validator_statuses.total_balances)?; + + let indices_to_attestation_delta = if validators.is_empty() { + get_attestation_deltas_all(&state, &validator_statuses, spec)? + .into_iter() + .enumerate() + .collect() + } else { + let validator_indices = Self::validators_ids_to_indices(&mut state, validators)?; + get_attestation_deltas_subset(&state, &validator_statuses, &validator_indices, spec)? + }; + + let mut total_rewards = vec![]; + + for (index, delta) in indices_to_attestation_delta.into_iter() { + let head_delta = delta.head_delta; + let head = (head_delta.rewards as i64).safe_sub(head_delta.penalties as i64)?; + + let target_delta = delta.target_delta; + let target = (target_delta.rewards as i64).safe_sub(target_delta.penalties as i64)?; + + let source_delta = delta.source_delta; + let source = (source_delta.rewards as i64).safe_sub(source_delta.penalties as i64)?; + + // No penalties associated with inclusion delay + let inclusion_delay = delta.inclusion_delay_delta.rewards; + let inactivity = delta.inactivity_penalty_delta.penalties.wrapping_neg() as i64; + + let rewards = TotalAttestationRewards { + validator_index: index as u64, + head, + target, + source, + inclusion_delay: Some(Quoted { + value: inclusion_delay, + }), + inactivity, + }; + + total_rewards.push(rewards); + } + + Ok(StandardAttestationRewards { + ideal_rewards, + total_rewards, + }) + } + + fn compute_attestation_rewards_altair( + &self, + mut state: BeaconState, + validators: Vec, + ) -> Result { + let spec = &self.spec; + // Calculate ideal_rewards let participation_cache = ParticipationCache::new(&state, spec)?; @@ -71,7 +151,7 @@ impl BeaconChain { let base_reward_per_increment = BaseRewardPerIncrement::new(total_active_balance, spec)?; - for effective_balance_eth in 0..=32 { + for effective_balance_eth in 1..=self.max_effective_balance_increment_steps()? { let effective_balance = effective_balance_eth.safe_mul(spec.effective_balance_increment)?; let base_reward = @@ -101,20 +181,12 @@ impl BeaconChain { let validators = if validators.is_empty() { participation_cache.eligible_validator_indices().to_vec() } else { - validators - .into_iter() - .map(|validator| match validator { - ValidatorId::Index(i) => Ok(i as usize), - ValidatorId::PublicKey(pubkey) => state - .get_validator_index(&pubkey)? - .ok_or(BeaconChainError::ValidatorPubkeyUnknown(pubkey)), - }) - .collect::, _>>()? + Self::validators_ids_to_indices(&mut state, validators)? }; for validator_index in &validators { let eligible = state.is_eligible_validator(previous_epoch, *validator_index)?; - let mut head_reward = 0u64; + let mut head_reward = 0i64; let mut target_reward = 0i64; let mut source_reward = 0i64; @@ -132,7 +204,7 @@ impl BeaconChain { .map_err(|_| BeaconChainError::AttestationRewardsError)?; if voted_correctly { if flag_index == TIMELY_HEAD_FLAG_INDEX { - head_reward += ideal_reward; + head_reward += *ideal_reward as i64; } else if flag_index == TIMELY_TARGET_FLAG_INDEX { target_reward += *ideal_reward as i64; } else if flag_index == TIMELY_SOURCE_FLAG_INDEX { @@ -152,6 +224,9 @@ impl BeaconChain { head: head_reward, target: target_reward, source: source_reward, + inclusion_delay: None, + // TODO: altair calculation logic needs to be updated to include inactivity penalty + inactivity: 0, }); } @@ -173,6 +248,9 @@ impl BeaconChain { head: 0, target: 0, source: 0, + inclusion_delay: None, + // TODO: altair calculation logic needs to be updated to include inactivity penalty + inactivity: 0, }); match *flag_index { TIMELY_SOURCE_FLAG_INDEX => entry.source += ideal_reward, @@ -192,4 +270,126 @@ impl BeaconChain { total_rewards, }) } + + fn max_effective_balance_increment_steps(&self) -> Result { + let spec = &self.spec; + let max_steps = spec + .max_effective_balance + .safe_div(spec.effective_balance_increment)?; + Ok(max_steps) + } + + fn validators_ids_to_indices( + state: &mut BeaconState, + validators: Vec, + ) -> Result, BeaconChainError> { + let indices = validators + .into_iter() + .map(|validator| match validator { + ValidatorId::Index(i) => Ok(i as usize), + ValidatorId::PublicKey(pubkey) => state + .get_validator_index(&pubkey)? + .ok_or(BeaconChainError::ValidatorPubkeyUnknown(pubkey)), + }) + .collect::, _>>()?; + Ok(indices) + } + + fn compute_ideal_rewards_base( + &self, + state: &BeaconState, + total_balances: &TotalBalances, + ) -> Result, BeaconChainError> { + let spec = &self.spec; + let previous_epoch = state.previous_epoch(); + let finality_delay = previous_epoch + .safe_sub(state.finalized_checkpoint().epoch)? + .as_u64(); + + let ideal_validator_status = ValidatorStatus { + is_previous_epoch_attester: true, + is_slashed: false, + inclusion_info: Some(InclusionInfo { + delay: 1, + ..Default::default() + }), + ..Default::default() + }; + + let mut ideal_attestation_rewards_list = Vec::new(); + + for effective_balance_step in 1..=self.max_effective_balance_increment_steps()? { + let effective_balance = + effective_balance_step.safe_mul(spec.effective_balance_increment)?; + let base_reward = get_base_reward_from_effective_balance::( + effective_balance, + total_balances.current_epoch(), + spec, + )?; + + // compute ideal head rewards + let head = get_attestation_component_delta( + true, + total_balances.previous_epoch_attesters(), + total_balances, + base_reward, + finality_delay, + spec, + )? + .rewards; + + // compute ideal target rewards + let target = get_attestation_component_delta( + true, + total_balances.previous_epoch_target_attesters(), + total_balances, + base_reward, + finality_delay, + spec, + )? + .rewards; + + // compute ideal source rewards + let source = get_attestation_component_delta( + true, + total_balances.previous_epoch_head_attesters(), + total_balances, + base_reward, + finality_delay, + spec, + )? + .rewards; + + // compute ideal inclusion delay rewards + let inclusion_delay = + get_inclusion_delay_delta(&ideal_validator_status, base_reward, spec)? + .0 + .rewards; + + // compute inactivity penalty + let inactivity = get_inactivity_penalty_delta( + &ideal_validator_status, + base_reward, + finality_delay, + spec, + )? + .penalties + .wrapping_neg() as i64; + + let ideal_attestation_rewards = IdealAttestationRewards { + effective_balance, + head, + target, + source, + inclusion_delay: Some(Quoted { + value: inclusion_delay, + }), + inactivity, + }; + + ideal_attestation_rewards_list.push(ideal_attestation_rewards); + } + + Ok(ideal_attestation_rewards_list) + } } diff --git a/beacon_node/beacon_chain/src/errors.rs b/beacon_node/beacon_chain/src/errors.rs index 50bcf42653..8714131150 100644 --- a/beacon_node/beacon_chain/src/errors.rs +++ b/beacon_node/beacon_chain/src/errors.rs @@ -24,7 +24,7 @@ use state_processing::{ }, signature_sets::Error as SignatureSetError, state_advance::Error as StateAdvanceError, - BlockProcessingError, BlockReplayError, SlotProcessingError, + BlockProcessingError, BlockReplayError, EpochProcessingError, SlotProcessingError, }; use std::time::Duration; use task_executor::ShutdownReason; @@ -60,6 +60,7 @@ pub enum BeaconChainError { MissingBeaconBlock(Hash256), MissingBeaconState(Hash256), SlotProcessingError(SlotProcessingError), + EpochProcessingError(EpochProcessingError), StateAdvanceError(StateAdvanceError), UnableToAdvanceState(String), NoStateForAttestation { @@ -217,6 +218,7 @@ pub enum BeaconChainError { } easy_from_to!(SlotProcessingError, BeaconChainError); +easy_from_to!(EpochProcessingError, BeaconChainError); easy_from_to!(AttestationValidationError, BeaconChainError); easy_from_to!(SyncCommitteeMessageValidationError, BeaconChainError); easy_from_to!(ExitValidationError, BeaconChainError); diff --git a/beacon_node/beacon_chain/tests/rewards.rs b/beacon_node/beacon_chain/tests/rewards.rs index b61bea1242..be271804b9 100644 --- a/beacon_node/beacon_chain/tests/rewards.rs +++ b/beacon_node/beacon_chain/tests/rewards.rs @@ -9,19 +9,22 @@ use beacon_chain::{ test_utils::{AttestationStrategy, BlockStrategy, RelativeSyncCommittee}, types::{Epoch, EthSpec, Keypair, MinimalEthSpec}, }; +use eth2::lighthouse::attestation_rewards::TotalAttestationRewards; +use eth2::lighthouse::StandardAttestationRewards; +use eth2::types::ValidatorId; use lazy_static::lazy_static; +use types::beacon_state::Error as BeaconStateError; +use types::{BeaconState, ChainSpec}; pub const VALIDATOR_COUNT: usize = 64; +type E = MinimalEthSpec; + lazy_static! { static ref KEYPAIRS: Vec = generate_deterministic_keypairs(VALIDATOR_COUNT); } -fn get_harness() -> BeaconChainHarness> { - let mut spec = E::default_spec(); - - spec.altair_fork_epoch = Some(Epoch::new(0)); // We use altair for all tests - +fn get_harness(spec: ChainSpec) -> BeaconChainHarness> { let harness = BeaconChainHarness::builder(E::default()) .spec(spec) .keypairs(KEYPAIRS.to_vec()) @@ -35,8 +38,11 @@ fn get_harness() -> BeaconChainHarness> { #[tokio::test] async fn test_sync_committee_rewards() { - let num_block_produced = MinimalEthSpec::slots_per_epoch(); - let harness = get_harness::(); + let mut spec = E::default_spec(); + spec.altair_fork_epoch = Some(Epoch::new(0)); + + let harness = get_harness(spec); + let num_block_produced = E::slots_per_epoch(); let latest_block_root = harness .extend_chain( @@ -119,3 +125,175 @@ async fn test_sync_committee_rewards() { mismatches.join(",") ); } + +#[tokio::test] +async fn test_verify_attestation_rewards_base() { + let harness = get_harness(E::default_spec()); + + // epoch 0 (N), only two thirds of validators vote. + let two_thirds = (VALIDATOR_COUNT / 3) * 2; + let two_thirds_validators: Vec = (0..two_thirds).collect(); + harness + .extend_chain( + E::slots_per_epoch() as usize, + BlockStrategy::OnCanonicalHead, + AttestationStrategy::SomeValidators(two_thirds_validators), + ) + .await; + + let initial_balances: Vec = harness.get_current_state().balances().clone().into(); + + // extend slots to beginning of epoch N + 2 + harness.extend_slots(E::slots_per_epoch() as usize).await; + + // compute reward deltas for all validators in epoch N + let StandardAttestationRewards { + ideal_rewards, + total_rewards, + } = harness + .chain + .compute_attestation_rewards(Epoch::new(0), vec![]) + .unwrap(); + + // assert no inactivity penalty for both ideal rewards and individual validators + assert!(ideal_rewards.iter().all(|reward| reward.inactivity == 0)); + assert!(total_rewards.iter().all(|reward| reward.inactivity == 0)); + + // apply attestation rewards to initial balances + let expected_balances = apply_attestation_rewards(&initial_balances, total_rewards); + + // verify expected balances against actual balances + let balances: Vec = harness.get_current_state().balances().clone().into(); + assert_eq!(expected_balances, balances); +} + +#[tokio::test] +async fn test_verify_attestation_rewards_base_inactivity_leak() { + let spec = E::default_spec(); + let harness = get_harness(spec.clone()); + + let half = VALIDATOR_COUNT / 2; + let half_validators: Vec = (0..half).collect(); + // target epoch is the epoch where the chain enters inactivity leak + let target_epoch = &spec.min_epochs_to_inactivity_penalty + 1; + + // advance until beginning of epoch N + 1 and get balances + harness + .extend_chain( + (E::slots_per_epoch() * (target_epoch + 1)) as usize, + BlockStrategy::OnCanonicalHead, + AttestationStrategy::SomeValidators(half_validators.clone()), + ) + .await; + let initial_balances: Vec = harness.get_current_state().balances().clone().into(); + + // extend slots to beginning of epoch N + 2 + harness.advance_slot(); + harness + .extend_chain( + E::slots_per_epoch() as usize, + BlockStrategy::OnCanonicalHead, + AttestationStrategy::SomeValidators(half_validators), + ) + .await; + let _slot = harness.get_current_slot(); + + // compute reward deltas for all validators in epoch N + let StandardAttestationRewards { + ideal_rewards, + total_rewards, + } = harness + .chain + .compute_attestation_rewards(Epoch::new(target_epoch), vec![]) + .unwrap(); + + // assert inactivity penalty for both ideal rewards and individual validators + assert!(ideal_rewards.iter().all(|reward| reward.inactivity < 0)); + assert!(total_rewards.iter().all(|reward| reward.inactivity < 0)); + + // apply attestation rewards to initial balances + let expected_balances = apply_attestation_rewards(&initial_balances, total_rewards); + + // verify expected balances against actual balances + let balances: Vec = harness.get_current_state().balances().clone().into(); + assert_eq!(expected_balances, balances); +} + +#[tokio::test] +async fn test_verify_attestation_rewards_base_subset_only() { + let harness = get_harness(E::default_spec()); + + // epoch 0 (N), only two thirds of validators vote. + let two_thirds = (VALIDATOR_COUNT / 3) * 2; + let two_thirds_validators: Vec = (0..two_thirds).collect(); + harness + .extend_chain( + E::slots_per_epoch() as usize, + BlockStrategy::OnCanonicalHead, + AttestationStrategy::SomeValidators(two_thirds_validators), + ) + .await; + + // a small subset of validators to compute attestation rewards for + let validators_subset = [0, VALIDATOR_COUNT / 2, VALIDATOR_COUNT - 1]; + + // capture balances before transitioning to N + 2 + let initial_balances = get_validator_balances(harness.get_current_state(), &validators_subset); + + // extend slots to beginning of epoch N + 2 + harness.extend_slots(E::slots_per_epoch() as usize).await; + + let validators_subset_ids: Vec = validators_subset + .into_iter() + .map(|idx| ValidatorId::Index(idx as u64)) + .collect(); + + // compute reward deltas for the subset of validators in epoch N + let StandardAttestationRewards { + ideal_rewards: _, + total_rewards, + } = harness + .chain + .compute_attestation_rewards(Epoch::new(0), validators_subset_ids) + .unwrap(); + + // apply attestation rewards to initial balances + let expected_balances = apply_attestation_rewards(&initial_balances, total_rewards); + + // verify expected balances against actual balances + let balances = get_validator_balances(harness.get_current_state(), &validators_subset); + assert_eq!(expected_balances, balances); +} + +/// Apply a vec of `TotalAttestationRewards` to initial balances, and return +fn apply_attestation_rewards( + initial_balances: &[u64], + attestation_rewards: Vec, +) -> Vec { + initial_balances + .iter() + .zip(attestation_rewards) + .map(|(&initial_balance, rewards)| { + let expected_balance = initial_balance as i64 + + rewards.head + + rewards.source + + rewards.target + + rewards.inclusion_delay.map(|q| q.value).unwrap_or(0) as i64 + + rewards.inactivity; + expected_balance as u64 + }) + .collect::>() +} + +fn get_validator_balances(state: BeaconState, validators: &[usize]) -> Vec { + validators + .iter() + .flat_map(|&id| { + state + .balances() + .get(id) + .cloned() + .ok_or(BeaconStateError::BalancesOutOfBounds(id)) + }) + .collect() +} diff --git a/beacon_node/http_api/src/lib.rs b/beacon_node/http_api/src/lib.rs index 27bcc4d8a1..1f93f46110 100644 --- a/beacon_node/http_api/src/lib.rs +++ b/beacon_node/http_api/src/lib.rs @@ -2034,15 +2034,11 @@ pub fn serve( .and(warp::path::param::()) .and(warp::path::end()) .and(warp::body::json()) - .and(log_filter.clone()) .and_then( - |chain: Arc>, - epoch: Epoch, - validators: Vec, - log: Logger| { + |chain: Arc>, epoch: Epoch, validators: Vec| { blocking_json_task(move || { let attestation_rewards = chain - .compute_attestation_rewards(epoch, validators, log) + .compute_attestation_rewards(epoch, validators) .map_err(|e| match e { BeaconChainError::MissingBeaconState(root) => { warp_utils::reject::custom_not_found(format!( diff --git a/common/eth2/src/lighthouse/attestation_rewards.rs b/common/eth2/src/lighthouse/attestation_rewards.rs index bebd1c661b..fa3f93d06f 100644 --- a/common/eth2/src/lighthouse/attestation_rewards.rs +++ b/common/eth2/src/lighthouse/attestation_rewards.rs @@ -1,4 +1,5 @@ use serde::{Deserialize, Serialize}; +use serde_utils::quoted_u64::Quoted; // Details about the rewards paid for attestations // All rewards in GWei @@ -17,6 +18,12 @@ pub struct IdealAttestationRewards { // Ideal attester's reward for source vote in gwei #[serde(with = "serde_utils::quoted_u64")] pub source: u64, + // Ideal attester's inclusion_delay reward in gwei (phase0 only) + #[serde(skip_serializing_if = "Option::is_none")] + pub inclusion_delay: Option>, + // Ideal attester's inactivity penalty in gwei + #[serde(with = "serde_utils::quoted_i64")] + pub inactivity: i64, } #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] @@ -25,16 +32,20 @@ pub struct TotalAttestationRewards { #[serde(with = "serde_utils::quoted_u64")] pub validator_index: u64, // attester's reward for head vote in gwei - #[serde(with = "serde_utils::quoted_u64")] - pub head: u64, + #[serde(with = "serde_utils::quoted_i64")] + pub head: i64, // attester's reward for target vote in gwei #[serde(with = "serde_utils::quoted_i64")] pub target: i64, // attester's reward for source vote in gwei #[serde(with = "serde_utils::quoted_i64")] pub source: i64, - // TBD attester's inclusion_delay reward in gwei (phase0 only) - // pub inclusion_delay: u64, + // attester's inclusion_delay reward in gwei (phase0 only) + #[serde(skip_serializing_if = "Option::is_none")] + pub inclusion_delay: Option>, + // attester's inactivity penalty in gwei + #[serde(with = "serde_utils::quoted_i64")] + pub inactivity: i64, } #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] diff --git a/consensus/state_processing/src/common/base.rs b/consensus/state_processing/src/common/base.rs index b5cb382721..a8d04ad6cd 100644 --- a/consensus/state_processing/src/common/base.rs +++ b/consensus/state_processing/src/common/base.rs @@ -17,3 +17,15 @@ pub fn get_base_reward( .safe_div(spec.base_rewards_per_epoch) .map_err(Into::into) } + +pub fn get_base_reward_from_effective_balance( + effective_balance: u64, + total_active_balance: u64, + spec: &ChainSpec, +) -> Result { + effective_balance + .safe_mul(spec.base_reward_factor)? + .safe_div(total_active_balance.integer_sqrt())? + .safe_div(spec.base_rewards_per_epoch) + .map_err(Into::into) +} diff --git a/consensus/state_processing/src/per_epoch_processing/base.rs b/consensus/state_processing/src/per_epoch_processing/base.rs index 680563ce74..c5864bd1ef 100644 --- a/consensus/state_processing/src/per_epoch_processing/base.rs +++ b/consensus/state_processing/src/per_epoch_processing/base.rs @@ -36,7 +36,7 @@ pub fn process_epoch( justification_and_finalization_state.apply_changes_to_state(state); // Rewards and Penalties. - process_rewards_and_penalties(state, &mut validator_statuses, spec)?; + process_rewards_and_penalties(state, &validator_statuses, spec)?; // Registry Updates. process_registry_updates(state, spec)?; diff --git a/consensus/state_processing/src/per_epoch_processing/base/rewards_and_penalties.rs b/consensus/state_processing/src/per_epoch_processing/base/rewards_and_penalties.rs index e7a4d9c4dc..74c96d8aee 100644 --- a/consensus/state_processing/src/per_epoch_processing/base/rewards_and_penalties.rs +++ b/consensus/state_processing/src/per_epoch_processing/base/rewards_and_penalties.rs @@ -45,7 +45,7 @@ impl AttestationDelta { /// Apply attester and proposer rewards. pub fn process_rewards_and_penalties( state: &mut BeaconState, - validator_statuses: &mut ValidatorStatuses, + validator_statuses: &ValidatorStatuses, spec: &ChainSpec, ) -> Result<(), Error> { if state.current_epoch() == T::genesis_epoch() { @@ -59,7 +59,7 @@ pub fn process_rewards_and_penalties( return Err(Error::ValidatorStatusesInconsistent); } - let deltas = get_attestation_deltas(state, validator_statuses, spec)?; + let deltas = get_attestation_deltas_all(state, validator_statuses, spec)?; // Apply the deltas, erroring on overflow above but not on overflow below (saturating at 0 // instead). @@ -73,10 +73,41 @@ pub fn process_rewards_and_penalties( } /// Apply rewards for participation in attestations during the previous epoch. -pub fn get_attestation_deltas( +pub fn get_attestation_deltas_all( state: &BeaconState, validator_statuses: &ValidatorStatuses, spec: &ChainSpec, +) -> Result, Error> { + get_attestation_deltas(state, validator_statuses, None, spec) +} + +/// Apply rewards for participation in attestations during the previous epoch, and only compute +/// rewards for a subset of validators. +pub fn get_attestation_deltas_subset( + state: &BeaconState, + validator_statuses: &ValidatorStatuses, + validators_subset: &Vec, + spec: &ChainSpec, +) -> Result, Error> { + get_attestation_deltas(state, validator_statuses, Some(validators_subset), spec).map(|deltas| { + deltas + .into_iter() + .enumerate() + .filter(|(index, _)| validators_subset.contains(index)) + .collect() + }) +} + +/// Apply rewards for participation in attestations during the previous epoch. +/// If `maybe_validators_subset` specified, only the deltas for the specified validator subset is +/// returned, otherwise deltas for all validators are returned. +/// +/// Returns a vec of validator indices to `AttestationDelta`. +fn get_attestation_deltas( + state: &BeaconState, + validator_statuses: &ValidatorStatuses, + maybe_validators_subset: Option<&Vec>, + spec: &ChainSpec, ) -> Result, Error> { let previous_epoch = state.previous_epoch(); let finality_delay = state @@ -88,6 +119,13 @@ pub fn get_attestation_deltas( let total_balances = &validator_statuses.total_balances; + // Ignore validator if a subset is specified and validator is not in the subset + let include_validator_delta = |idx| match maybe_validators_subset.as_ref() { + None => true, + Some(validators_subset) if validators_subset.contains(&idx) => true, + Some(_) => false, + }; + for (index, validator) in validator_statuses.statuses.iter().enumerate() { // Ignore ineligible validators. All sub-functions of the spec do this except for // `get_inclusion_delay_deltas`. It's safe to do so here because any validator that is in @@ -99,41 +137,46 @@ pub fn get_attestation_deltas( let base_reward = get_base_reward(state, index, total_balances.current_epoch(), spec)?; - let source_delta = - get_source_delta(validator, base_reward, total_balances, finality_delay, spec)?; - let target_delta = - get_target_delta(validator, base_reward, total_balances, finality_delay, spec)?; - let head_delta = - get_head_delta(validator, base_reward, total_balances, finality_delay, spec)?; let (inclusion_delay_delta, proposer_delta) = get_inclusion_delay_delta(validator, base_reward, spec)?; - let inactivity_penalty_delta = - get_inactivity_penalty_delta(validator, base_reward, finality_delay, spec)?; - let delta = deltas - .get_mut(index) - .ok_or(Error::DeltaOutOfBounds(index))?; - delta.source_delta.combine(source_delta)?; - delta.target_delta.combine(target_delta)?; - delta.head_delta.combine(head_delta)?; - delta.inclusion_delay_delta.combine(inclusion_delay_delta)?; - delta - .inactivity_penalty_delta - .combine(inactivity_penalty_delta)?; + if include_validator_delta(index) { + let source_delta = + get_source_delta(validator, base_reward, total_balances, finality_delay, spec)?; + let target_delta = + get_target_delta(validator, base_reward, total_balances, finality_delay, spec)?; + let head_delta = + get_head_delta(validator, base_reward, total_balances, finality_delay, spec)?; + let inactivity_penalty_delta = + get_inactivity_penalty_delta(validator, base_reward, finality_delay, spec)?; + + let delta = deltas + .get_mut(index) + .ok_or(Error::DeltaOutOfBounds(index))?; + delta.source_delta.combine(source_delta)?; + delta.target_delta.combine(target_delta)?; + delta.head_delta.combine(head_delta)?; + delta.inclusion_delay_delta.combine(inclusion_delay_delta)?; + delta + .inactivity_penalty_delta + .combine(inactivity_penalty_delta)?; + } if let Some((proposer_index, proposer_delta)) = proposer_delta { - deltas - .get_mut(proposer_index) - .ok_or(Error::ValidatorStatusesInconsistent)? - .inclusion_delay_delta - .combine(proposer_delta)?; + if include_validator_delta(proposer_index) { + deltas + .get_mut(proposer_index) + .ok_or(Error::ValidatorStatusesInconsistent)? + .inclusion_delay_delta + .combine(proposer_delta)?; + } } } Ok(deltas) } -fn get_attestation_component_delta( +pub fn get_attestation_component_delta( index_in_unslashed_attesting_indices: bool, attesting_balance: u64, total_balances: &TotalBalances, @@ -216,7 +259,7 @@ fn get_head_delta( ) } -fn get_inclusion_delay_delta( +pub fn get_inclusion_delay_delta( validator: &ValidatorStatus, base_reward: u64, spec: &ChainSpec, @@ -242,7 +285,7 @@ fn get_inclusion_delay_delta( } } -fn get_inactivity_penalty_delta( +pub fn get_inactivity_penalty_delta( validator: &ValidatorStatus, base_reward: u64, finality_delay: u64, diff --git a/testing/ef_tests/src/cases/epoch_processing.rs b/testing/ef_tests/src/cases/epoch_processing.rs index 31542ba447..5e71187156 100644 --- a/testing/ef_tests/src/cases/epoch_processing.rs +++ b/testing/ef_tests/src/cases/epoch_processing.rs @@ -120,7 +120,7 @@ impl EpochTransition for RewardsAndPenalties { BeaconState::Base(_) => { let mut validator_statuses = base::ValidatorStatuses::new(state, spec)?; validator_statuses.process_attestations(state)?; - base::process_rewards_and_penalties(state, &mut validator_statuses, spec) + base::process_rewards_and_penalties(state, &validator_statuses, spec) } BeaconState::Altair(_) | BeaconState::Merge(_) | BeaconState::Capella(_) => { altair::process_rewards_and_penalties( diff --git a/testing/ef_tests/src/cases/rewards.rs b/testing/ef_tests/src/cases/rewards.rs index c59ceabe0b..ee0fc265e1 100644 --- a/testing/ef_tests/src/cases/rewards.rs +++ b/testing/ef_tests/src/cases/rewards.rs @@ -118,7 +118,7 @@ impl Case for RewardsTest { let mut validator_statuses = ValidatorStatuses::new(&state, spec)?; validator_statuses.process_attestations(&state)?; - let deltas = base::rewards_and_penalties::get_attestation_deltas( + let deltas = base::rewards_and_penalties::get_attestation_deltas_all( &state, &validator_statuses, spec, From 071dd4cd9c0ba4ef62f37d52895b83b15adbb36c Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Fri, 21 Jul 2023 04:46:52 +0000 Subject: [PATCH 6/9] Add self-hosted runners v2 (#4506) ## Issue Addressed NA ## Proposed Changes Carries on from #4115, with the following modifications: 1. Self-hosted runners are only enabled if `github.repository == sigp/lighthouse`. - This allows forks to still have Github-hosted CI. - This gives us a method to switch back to Github-runners if we have extended downtime on self-hosted. 1. Does not remove any existing dependency builds for Github-hosted runners (e.g., installing the latest Rust). 1. Adds the `WATCH_HOST` environment variable which defines where we expect to find the postgres db in the `watch` tests. This should be set to `host.docker.internal` for the tests to pass on self-hosted runners. ## Additional Info NA Co-authored-by: antondlr --- .github/workflows/test-suite.yml | 24 +++++++++++++++++++----- watch/tests/tests.rs | 11 +++++++++++ 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test-suite.yml b/.github/workflows/test-suite.yml index e3342ac370..ff7a9cf2f1 100644 --- a/.github/workflows/test-suite.yml +++ b/.github/workflows/test-suite.yml @@ -17,6 +17,10 @@ env: PINNED_NIGHTLY: nightly-2023-04-16 # Prevent Github API rate limiting. LIGHTHOUSE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # Enable self-hosted runners for the sigp repo only. + SELF_HOSTED_RUNNERS: ${{ github.repository == 'sigp/lighthouse' }} + # Self-hosted runners need to reference a different host for `./watch` tests. + WATCH_HOST: ${{ github.repository == 'sigp/lighthouse' && 'host.docker.internal' || 'localhost' }} jobs: target-branch-check: name: target-branch-check @@ -48,11 +52,13 @@ jobs: run: make cargo-fmt release-tests-ubuntu: name: release-tests-ubuntu - runs-on: ubuntu-latest + # Use self-hosted runners only on the sigp repo. + runs-on: ${{ github.repository == 'sigp/lighthouse' && fromJson('["self-hosted", "linux", "large"]') || 'ubuntu-latest' }} needs: cargo-fmt steps: - uses: actions/checkout@v3 - name: Get latest version of stable Rust + if: env.SELF_HOSTED_RUNNERS == false run: rustup update stable - name: Install Protoc uses: arduino/setup-protoc@e52d9eb8f7b63115df1ac544a1376fdbf5a39612 @@ -64,11 +70,12 @@ jobs: run: make test-release release-tests-windows: name: release-tests-windows - runs-on: windows-2019 + runs-on: ${{ github.repository == 'sigp/lighthouse' && fromJson('["self-hosted", "windows"]') || 'windows-2019' }} needs: cargo-fmt steps: - uses: actions/checkout@v3 - name: Get latest version of stable Rust + if: env.SELF_HOSTED_RUNNERS == false run: rustup update stable - name: Use Node.js uses: actions/setup-node@v2 @@ -83,6 +90,7 @@ jobs: - name: Install make run: choco install -y make - uses: KyleMayes/install-llvm-action@v1 + if: env.SELF_HOSTED_RUNNERS == false with: version: "15.0" directory: ${{ runner.temp }}/llvm @@ -92,11 +100,13 @@ jobs: run: make test-release beacon-chain-tests: name: beacon-chain-tests - runs-on: ubuntu-latest + # Use self-hosted runners only on the sigp repo. + runs-on: ${{ github.repository == 'sigp/lighthouse' && fromJson('["self-hosted", "linux", "large"]') || 'ubuntu-latest' }} needs: cargo-fmt steps: - uses: actions/checkout@v3 - name: Get latest version of stable Rust + if: env.SELF_HOSTED_RUNNERS == false run: rustup update stable - name: Install Protoc uses: arduino/setup-protoc@e52d9eb8f7b63115df1ac544a1376fdbf5a39612 @@ -130,11 +140,13 @@ jobs: run: make test-slasher debug-tests-ubuntu: name: debug-tests-ubuntu - runs-on: ubuntu-22.04 + # Use self-hosted runners only on the sigp repo. + runs-on: ${{ github.repository == 'sigp/lighthouse' && fromJson('["self-hosted", "linux", "large"]') || 'ubuntu-latest' }} needs: cargo-fmt steps: - uses: actions/checkout@v3 - name: Get latest version of stable Rust + if: env.SELF_HOSTED_RUNNERS == false run: rustup update stable - name: Install Protoc uses: arduino/setup-protoc@e52d9eb8f7b63115df1ac544a1376fdbf5a39612 @@ -160,11 +172,13 @@ jobs: run: make run-state-transition-tests ef-tests-ubuntu: name: ef-tests-ubuntu - runs-on: ubuntu-latest + # Use self-hosted runners only on the sigp repo. + runs-on: ${{ github.repository == 'sigp/lighthouse' && fromJson('["self-hosted", "linux", "small"]') || 'ubuntu-latest' }} needs: cargo-fmt steps: - uses: actions/checkout@v3 - name: Get latest version of stable Rust + if: env.SELF_HOSTED_RUNNERS == false run: rustup update stable - name: Install Protoc uses: arduino/setup-protoc@e52d9eb8f7b63115df1ac544a1376fdbf5a39612 diff --git a/watch/tests/tests.rs b/watch/tests/tests.rs index acdda8c306..28700ccdce 100644 --- a/watch/tests/tests.rs +++ b/watch/tests/tests.rs @@ -22,6 +22,7 @@ use watch::{ }; use log::error; +use std::env; use std::net::SocketAddr; use std::time::Duration; use tokio::{runtime, task::JoinHandle}; @@ -36,6 +37,11 @@ const VALIDATOR_COUNT: usize = 32; const SLOTS_PER_EPOCH: u64 = 32; const DEFAULT_TIMEOUT: Duration = Duration::from_secs(5); +/// Set this environment variable to use a different hostname for connecting to +/// the database. Can be set to `host.docker.internal` for docker-in-docker +/// setups. +const WATCH_HOST_ENV_VARIABLE: &str = "WATCH_HOST"; + fn build_test_config(config: &DatabaseConfig) -> PostgresConfig { let mut postgres_config = PostgresConfig::new(); postgres_config @@ -71,6 +77,10 @@ pub async fn create_test_database(config: &DatabaseConfig) { .expect("Database creation failed"); } +pub fn get_host_from_env() -> String { + env::var(WATCH_HOST_ENV_VARIABLE).unwrap_or_else(|_| "localhost".to_string()) +} + struct TesterBuilder { pub harness: BeaconChainHarness>, pub config: Config, @@ -107,6 +117,7 @@ impl TesterBuilder { database: DatabaseConfig { dbname: random_dbname(), port: database_port, + host: get_host_from_env(), ..Default::default() }, server: ServerConfig { From f1f04bc68a5506c839693d25bc8c626c48ec8669 Mon Sep 17 00:00:00 2001 From: Gua00va <105484243+Gua00va@users.noreply.github.com> Date: Fri, 21 Jul 2023 20:26:57 +0530 Subject: [PATCH 7/9] Add Changes to BlobSidecars Endpoint (#4455) * changed name * Fix sidecars * Added query type and parrameter * added query struct and function * added method * improved filtering method * added blob_sidecar_list_indexed to block_id * minor blobqueryindex fix * function and formatting fix * minor function and naming fix * minor changes --- beacon_node/http_api/src/block_id.rs | 21 +++++++++++++++++++++ beacon_node/http_api/src/lib.rs | 10 ++++++---- common/eth2/src/types.rs | 7 +++++++ 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/beacon_node/http_api/src/block_id.rs b/beacon_node/http_api/src/block_id.rs index d5f6ac8864..1a3d852dab 100644 --- a/beacon_node/http_api/src/block_id.rs +++ b/beacon_node/http_api/src/block_id.rs @@ -1,5 +1,6 @@ use crate::{state_id::checkpoint_slot_and_execution_optimistic, ExecutionOptimistic}; use beacon_chain::{BeaconChain, BeaconChainError, BeaconChainTypes, WhenSlotSkipped}; +use eth2::types::BlobIndicesQuery; use eth2::types::BlockId as CoreBlockId; use std::fmt; use std::str::FromStr; @@ -266,6 +267,26 @@ impl BlockId { Err(e) => Err(warp_utils::reject::beacon_chain_error(e)), } } + + pub async fn blob_sidecar_list_filtered( + &self, + indices: BlobIndicesQuery, + chain: &BeaconChain, + ) -> Result, warp::Rejection> { + let blob_sidecar_list = self.blob_sidecar_list(&chain).await?; + let blob_sidecar_list_filtered = match indices.indices { + Some(vec) => { + let list = blob_sidecar_list + .into_iter() + .filter(|blob_sidecar| vec.contains(&blob_sidecar.index)) + .collect(); + BlobSidecarList::new(list) + .map_err(|e| warp_utils::reject::custom_server_error(format!("{:?}", e)))? + } + None => blob_sidecar_list, + }; + Ok(blob_sidecar_list_filtered) + } } impl FromStr for BlockId { diff --git a/beacon_node/http_api/src/lib.rs b/beacon_node/http_api/src/lib.rs index 6707719ae2..c9b6af0a48 100644 --- a/beacon_node/http_api/src/lib.rs +++ b/beacon_node/http_api/src/lib.rs @@ -1487,21 +1487,23 @@ pub fn serve( .and(warp::path("beacon")) .and(warp::path("blob_sidecars")) .and(block_id_or_err) + .and(warp::query::()) .and(warp::path::end()) .and(chain_filter.clone()) .and(warp::header::optional::("accept")) .and_then( |block_id: BlockId, + indices: api_types::BlobIndicesQuery, chain: Arc>, accept_header: Option| { async move { - let blob_sidecar_list = block_id.blob_sidecar_list(&chain).await?; - + let blob_sidecar_list_filtered = + block_id.blob_sidecar_list_filtered(indices, &chain).await?; match accept_header { Some(api_types::Accept::Ssz) => Response::builder() .status(200) .header("Content-Type", "application/octet-stream") - .body(blob_sidecar_list.as_ssz_bytes().into()) + .body(blob_sidecar_list_filtered.as_ssz_bytes().into()) .map_err(|e| { warp_utils::reject::custom_server_error(format!( "failed to create response: {}", @@ -1509,7 +1511,7 @@ pub fn serve( )) }), _ => Ok(warp::reply::json(&api_types::GenericResponse::from( - blob_sidecar_list, + blob_sidecar_list_filtered, )) .into_response()), } diff --git a/common/eth2/src/types.rs b/common/eth2/src/types.rs index 7bb43d8a9d..29eb0ac616 100644 --- a/common/eth2/src/types.rs +++ b/common/eth2/src/types.rs @@ -648,6 +648,13 @@ pub struct ValidatorBalancesQuery { pub id: Option>, } +#[derive(Clone, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct BlobIndicesQuery { + #[serde(default, deserialize_with = "option_query_vec")] + pub indices: Option>, +} + #[derive(Clone, Serialize, Deserialize)] #[serde(transparent)] pub struct ValidatorIndexData(#[serde(with = "serde_utils::quoted_u64_vec")] pub Vec); From 54c6e1dd3d1b54dfe63f350440a5455fe4e872a7 Mon Sep 17 00:00:00 2001 From: Jimmy Chen Date: Mon, 24 Jul 2023 21:09:07 +1000 Subject: [PATCH 8/9] Fix compilation --- beacon_node/beacon_chain/src/attestation_rewards.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/beacon_node/beacon_chain/src/attestation_rewards.rs b/beacon_node/beacon_chain/src/attestation_rewards.rs index 9fc21b668f..08950d6550 100644 --- a/beacon_node/beacon_chain/src/attestation_rewards.rs +++ b/beacon_node/beacon_chain/src/attestation_rewards.rs @@ -50,9 +50,10 @@ impl BeaconChain { match state { BeaconState::Base(_) => self.compute_attestation_rewards_base(state, validators), - BeaconState::Altair(_) | BeaconState::Merge(_) | BeaconState::Capella(_) => { - self.compute_attestation_rewards_altair(state, validators) - } + BeaconState::Altair(_) + | BeaconState::Merge(_) + | BeaconState::Capella(_) + | BeaconState::Deneb(_) => self.compute_attestation_rewards_altair(state, validators), } } From fe94a05dd11efc2817b77b880b029914d7feb313 Mon Sep 17 00:00:00 2001 From: Jimmy Chen Date: Mon, 24 Jul 2023 23:02:29 +1000 Subject: [PATCH 9/9] Fix lint --- beacon_node/http_api/src/block_id.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beacon_node/http_api/src/block_id.rs b/beacon_node/http_api/src/block_id.rs index 1a3d852dab..fa99ea5b4f 100644 --- a/beacon_node/http_api/src/block_id.rs +++ b/beacon_node/http_api/src/block_id.rs @@ -273,7 +273,7 @@ impl BlockId { indices: BlobIndicesQuery, chain: &BeaconChain, ) -> Result, warp::Rejection> { - let blob_sidecar_list = self.blob_sidecar_list(&chain).await?; + let blob_sidecar_list = self.blob_sidecar_list(chain).await?; let blob_sidecar_list_filtered = match indices.indices { Some(vec) => { let list = blob_sidecar_list