From 07f53b18fcd01bb83385c4c62332d960e6dd3b4e Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Fri, 3 Nov 2023 00:12:18 +0000 Subject: [PATCH 01/23] Block v3 endpoint (#4629) ## Issue Addressed #4582 ## Proposed Changes Add a new v3 block fetching flow that can decide to return a Full OR Blinded payload ## Additional Info Co-authored-by: Michael Sproul --- .../beacon_chain/src/beacon_block_reward.rs | 25 +- beacon_node/beacon_chain/src/beacon_chain.rs | 227 ++++++++++------- .../beacon_chain/src/execution_payload.rs | 43 ++-- beacon_node/beacon_chain/src/lib.rs | 8 +- beacon_node/beacon_chain/src/test_utils.rs | 43 ++-- beacon_node/execution_layer/src/engine_api.rs | 5 + beacon_node/execution_layer/src/lib.rs | 215 ++++++++++------ .../src/test_utils/mock_builder.rs | 118 ++++++--- .../src/test_utils/mock_execution_layer.rs | 73 +++++- .../http_api/src/build_block_contents.rs | 97 ++++---- beacon_node/http_api/src/lib.rs | 133 +--------- beacon_node/http_api/src/produce_block.rs | 231 ++++++++++++++++++ beacon_node/http_api/src/validator.rs | 2 +- beacon_node/http_api/src/version.rs | 49 +++- .../http_api/tests/interactive_tests.rs | 17 +- beacon_node/http_api/tests/tests.rs | 4 +- common/eth2/src/lib.rs | 94 ++++++- common/eth2/src/types.rs | 5 + consensus/types/src/payload.rs | 7 + .../src/test_rig.rs | 35 ++- 20 files changed, 972 insertions(+), 459 deletions(-) create mode 100644 beacon_node/http_api/src/produce_block.rs diff --git a/beacon_node/beacon_chain/src/beacon_block_reward.rs b/beacon_node/beacon_chain/src/beacon_block_reward.rs index 8cbeae371e..d05f7cb4ff 100644 --- a/beacon_node/beacon_chain/src/beacon_block_reward.rs +++ b/beacon_node/beacon_chain/src/beacon_block_reward.rs @@ -33,6 +33,17 @@ impl BeaconChain { state.build_committee_cache(RelativeEpoch::Previous, &self.spec)?; state.build_committee_cache(RelativeEpoch::Current, &self.spec)?; + self.compute_beacon_block_reward_with_cache(block, block_root, state) + } + + // This should only be called after a committee cache has been built + // for both the previous and current epoch + fn compute_beacon_block_reward_with_cache>( + &self, + block: BeaconBlockRef<'_, T::EthSpec, Payload>, + block_root: Hash256, + state: &BeaconState, + ) -> Result { let proposer_index = block.proposer_index(); let sync_aggregate_reward = @@ -178,7 +189,7 @@ impl BeaconChain { >( &self, block: BeaconBlockRef<'_, T::EthSpec, Payload>, - state: &mut BeaconState, + state: &BeaconState, ) -> Result { let total_active_balance = state.get_total_active_balance()?; let base_reward_per_increment = @@ -191,6 +202,9 @@ impl BeaconChain { .safe_mul(WEIGHT_DENOMINATOR)? .safe_div(PROPOSER_WEIGHT)?; + let mut current_epoch_participation = state.current_epoch_participation()?.clone(); + let mut previous_epoch_participation = state.previous_epoch_participation()?.clone(); + for attestation in block.body().attestations() { let data = &attestation.data; let inclusion_delay = state.slot().safe_sub(data.slot)?.as_u64(); @@ -203,13 +217,16 @@ impl BeaconChain { )?; let attesting_indices = get_attesting_indices_from_state(state, attestation)?; - let mut proposer_reward_numerator = 0; for index in attesting_indices { let index = index as usize; for (flag_index, &weight) in PARTICIPATION_FLAG_WEIGHTS.iter().enumerate() { - let epoch_participation = - state.get_epoch_participation_mut(data.target.epoch)?; + let epoch_participation = if data.target.epoch == state.current_epoch() { + &mut current_epoch_participation + } else { + &mut previous_epoch_participation + }; + let validator_participation = epoch_participation .get_mut(index) .ok_or(BeaconStateError::ParticipationOutOfBounds(index))?; diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 9f59733934..f2378b4f9e 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -72,8 +72,8 @@ use crate::{ }; use eth2::types::{EventKind, SseBlobSidecar, SseBlock, SseExtendedPayloadAttributes, SyncDuty}; use execution_layer::{ - BlockProposalContents, BuilderParams, ChainHealth, ExecutionLayer, FailedCondition, - PayloadAttributes, PayloadStatus, + BlockProposalContents, BlockProposalContentsType, BuilderParams, ChainHealth, ExecutionLayer, + FailedCondition, PayloadAttributes, PayloadStatus, }; use fork_choice::{ AttestationFromBlock, ExecutionStatus, ForkChoice, ForkchoiceUpdateParameters, @@ -120,6 +120,7 @@ use tokio_stream::Stream; use tree_hash::TreeHash; use types::beacon_state::CloneConfig; use types::blob_sidecar::{BlobSidecarList, FixedBlobSidecarList}; +use types::payload::BlockProductionVersion; use types::sidecar::BlobItems; use types::*; @@ -320,8 +321,7 @@ pub trait BeaconChainTypes: Send + Sync + 'static { type EthSpec: types::EthSpec; } -/// Used internally to split block production into discrete functions. -struct PartialBeaconBlock> { +struct PartialBeaconBlock { state: BeaconState, slot: Slot, proposer_index: u64, @@ -335,7 +335,7 @@ struct PartialBeaconBlock> { deposits: Vec, voluntary_exits: Vec, sync_aggregate: Option>, - prepare_payload_handle: Option>, + prepare_payload_handle: Option>, bls_to_execution_changes: Vec, } @@ -484,11 +484,18 @@ pub struct BeaconChain { pub kzg: Option>, } -type BeaconBlockAndState = ( - BeaconBlock, - BeaconState, - Option>::Sidecar>>, -); +pub enum BeaconBlockResponseType { + Full(BeaconBlockResponse>), + Blinded(BeaconBlockResponse>), +} + +pub struct BeaconBlockResponse> { + pub block: BeaconBlock, + pub state: BeaconState, + pub maybe_side_car: Option>::Sidecar>>, + pub execution_payload_value: Option, + pub consensus_block_value: Option, +} impl FinalizationAndCanonicity { pub fn is_finalized(self) -> bool { @@ -3949,38 +3956,16 @@ impl BeaconChain { Ok(()) } - /// Produce a new block at the given `slot`. - /// - /// The produced block will not be inherently valid, it must be signed by a block producer. - /// Block signing is out of the scope of this function and should be done by a separate program. - pub async fn produce_block + 'static>( - self: &Arc, - randao_reveal: Signature, - slot: Slot, - validator_graffiti: Option, - ) -> Result, BlockProductionError> { - self.produce_block_with_verification( - randao_reveal, - slot, - validator_graffiti, - ProduceBlockVerification::VerifyRandao, - ) - .await - } - - /// Same as `produce_block` but allowing for configuration of RANDAO-verification. - pub async fn produce_block_with_verification< - Payload: AbstractExecPayload + 'static, - >( + pub async fn produce_block_with_verification( self: &Arc, randao_reveal: Signature, slot: Slot, validator_graffiti: Option, verification: ProduceBlockVerification, - ) -> Result, BlockProductionError> { + block_production_version: BlockProductionVersion, + ) -> Result, BlockProductionError> { metrics::inc_counter(&metrics::BLOCK_PRODUCTION_REQUESTS); let _complete_timer = metrics::start_timer(&metrics::BLOCK_PRODUCTION_TIMES); - // Part 1/2 (blocking) // // Load the parent state from disk. @@ -3998,13 +3983,14 @@ impl BeaconChain { // Part 2/2 (async, with some blocking components) // // Produce the block upon the state - self.produce_block_on_state::( + self.produce_block_on_state( state, state_root_opt, slot, randao_reveal, validator_graffiti, verification, + block_production_version, ) .await } @@ -4568,7 +4554,8 @@ impl BeaconChain { /// The provided `state_root_opt` should only ever be set to `Some` if the contained value is /// equal to the root of `state`. Providing this value will serve as an optimization to avoid /// performing a tree hash in some scenarios. - pub async fn produce_block_on_state + 'static>( + #[allow(clippy::too_many_arguments)] + pub async fn produce_block_on_state( self: &Arc, state: BeaconState, state_root_opt: Option, @@ -4576,7 +4563,8 @@ impl BeaconChain { randao_reveal: Signature, validator_graffiti: Option, verification: ProduceBlockVerification, - ) -> Result, BlockProductionError> { + block_production_version: BlockProductionVersion, + ) -> Result, BlockProductionError> { // Part 1/3 (blocking) // // Perform the state advance and block-packing functions. @@ -4591,6 +4579,7 @@ impl BeaconChain { produce_at_slot, randao_reveal, validator_graffiti, + block_production_version, ) }, "produce_partial_beacon_block", @@ -4598,50 +4587,96 @@ impl BeaconChain { .ok_or(BlockProductionError::ShuttingDown)? .await .map_err(BlockProductionError::TokioJoin)??; - // Part 2/3 (async) // // Wait for the execution layer to return an execution payload (if one is required). let prepare_payload_handle = partial_beacon_block.prepare_payload_handle.take(); - let block_contents = if let Some(prepare_payload_handle) = prepare_payload_handle { - Some( - prepare_payload_handle - .await - .map_err(BlockProductionError::TokioJoin)? - .ok_or(BlockProductionError::ShuttingDown)??, - ) - } else { - None - }; - + let block_contents_type_option = + if let Some(prepare_payload_handle) = prepare_payload_handle { + Some( + prepare_payload_handle + .await + .map_err(BlockProductionError::TokioJoin)? + .ok_or(BlockProductionError::ShuttingDown)??, + ) + } else { + None + }; // Part 3/3 (blocking) - // - // Perform the final steps of combining all the parts and computing the state root. - let chain = self.clone(); - self.task_executor - .spawn_blocking_handle( - move || { - chain.complete_partial_beacon_block( - partial_beacon_block, - block_contents, - verification, - ) - }, - "complete_partial_beacon_block", - ) - .ok_or(BlockProductionError::ShuttingDown)? - .await - .map_err(BlockProductionError::TokioJoin)? + if let Some(block_contents_type) = block_contents_type_option { + match block_contents_type { + BlockProposalContentsType::Full(block_contents) => { + let chain = self.clone(); + let beacon_block_response = self + .task_executor + .spawn_blocking_handle( + move || { + chain.complete_partial_beacon_block( + partial_beacon_block, + Some(block_contents), + verification, + ) + }, + "complete_partial_beacon_block", + ) + .ok_or(BlockProductionError::ShuttingDown)? + .await + .map_err(BlockProductionError::TokioJoin)??; + + Ok(BeaconBlockResponseType::Full(beacon_block_response)) + } + BlockProposalContentsType::Blinded(block_contents) => { + let chain = self.clone(); + let beacon_block_response = self + .task_executor + .spawn_blocking_handle( + move || { + chain.complete_partial_beacon_block( + partial_beacon_block, + Some(block_contents), + verification, + ) + }, + "complete_partial_beacon_block", + ) + .ok_or(BlockProductionError::ShuttingDown)? + .await + .map_err(BlockProductionError::TokioJoin)??; + + Ok(BeaconBlockResponseType::Blinded(beacon_block_response)) + } + } + } else { + let chain = self.clone(); + let beacon_block_response = self + .task_executor + .spawn_blocking_handle( + move || { + chain.complete_partial_beacon_block( + partial_beacon_block, + None, + verification, + ) + }, + "complete_partial_beacon_block", + ) + .ok_or(BlockProductionError::ShuttingDown)? + .await + .map_err(BlockProductionError::TokioJoin)??; + + Ok(BeaconBlockResponseType::Full(beacon_block_response)) + } } - fn produce_partial_beacon_block + 'static>( + fn produce_partial_beacon_block( self: &Arc, mut state: BeaconState, state_root_opt: Option, produce_at_slot: Slot, randao_reveal: Signature, validator_graffiti: Option, - ) -> Result, BlockProductionError> { + block_production_version: BlockProductionVersion, + ) -> Result, BlockProductionError> { let eth1_chain = self .eth1_chain .as_ref() @@ -4701,6 +4736,7 @@ impl BeaconChain { parent_root, proposer_index, builder_params, + block_production_version, )?; Some(prepare_payload_handle) } @@ -4710,6 +4746,7 @@ impl BeaconChain { self.op_pool.get_slashings_and_exits(&state, &self.spec); let eth1_data = eth1_chain.eth1_data_for_block_production(&state, &self.spec)?; + let deposits = eth1_chain.deposits_for_block_inclusion(&state, ð1_data, &self.spec)?; let bls_to_execution_changes = self @@ -4880,10 +4917,10 @@ impl BeaconChain { fn complete_partial_beacon_block>( &self, - partial_beacon_block: PartialBeaconBlock, + partial_beacon_block: PartialBeaconBlock, block_contents: Option>, verification: ProduceBlockVerification, - ) -> Result, BlockProductionError> { + ) -> Result, BlockProductionError> { let PartialBeaconBlock { mut state, slot, @@ -4905,7 +4942,7 @@ impl BeaconChain { bls_to_execution_changes, } = partial_beacon_block; - let (inner_block, blobs_opt, proofs_opt) = match &state { + let (inner_block, blobs_opt, proofs_opt, execution_payload_value) = match &state { BeaconState::Base(_) => ( BeaconBlock::Base(BeaconBlockBase { slot, @@ -4926,6 +4963,7 @@ impl BeaconChain { }), None, None, + Uint256::zero(), ), BeaconState::Altair(_) => ( BeaconBlock::Altair(BeaconBlockAltair { @@ -4949,11 +4987,12 @@ impl BeaconChain { }), None, None, + Uint256::zero(), ), BeaconState::Merge(_) => { - let (payload, _, _, _) = block_contents - .ok_or(BlockProductionError::MissingExecutionPayload)? - .deconstruct(); + let block_proposal_contents = + block_contents.ok_or(BlockProductionError::MissingExecutionPayload)?; + let execution_payload_value = block_proposal_contents.block_value().to_owned(); ( BeaconBlock::Merge(BeaconBlockMerge { slot, @@ -4971,19 +5010,22 @@ impl BeaconChain { voluntary_exits: voluntary_exits.into(), sync_aggregate: sync_aggregate .ok_or(BlockProductionError::MissingSyncAggregate)?, - execution_payload: payload + execution_payload: block_proposal_contents + .to_payload() .try_into() .map_err(|_| BlockProductionError::InvalidPayloadFork)?, }, }), None, None, + execution_payload_value, ) } BeaconState::Capella(_) => { - let (payload, _, _, _) = block_contents - .ok_or(BlockProductionError::MissingExecutionPayload)? - .deconstruct(); + let block_proposal_contents = + block_contents.ok_or(BlockProductionError::MissingExecutionPayload)?; + let execution_payload_value = block_proposal_contents.block_value().to_owned(); + ( BeaconBlock::Capella(BeaconBlockCapella { slot, @@ -5001,7 +5043,8 @@ impl BeaconChain { voluntary_exits: voluntary_exits.into(), sync_aggregate: sync_aggregate .ok_or(BlockProductionError::MissingSyncAggregate)?, - execution_payload: payload + execution_payload: block_proposal_contents + .to_payload() .try_into() .map_err(|_| BlockProductionError::InvalidPayloadFork)?, bls_to_execution_changes: bls_to_execution_changes.into(), @@ -5009,12 +5052,15 @@ impl BeaconChain { }), None, None, + execution_payload_value, ) } BeaconState::Deneb(_) => { - let (payload, kzg_commitments, blobs, proofs) = block_contents - .ok_or(BlockProductionError::MissingExecutionPayload)? - .deconstruct(); + let (payload, kzg_commitments, blobs, proofs, execution_payload_value) = + block_contents + .ok_or(BlockProductionError::MissingExecutionPayload)? + .deconstruct(); + ( BeaconBlock::Deneb(BeaconBlockDeneb { slot, @@ -5042,6 +5088,7 @@ impl BeaconChain { }), blobs, proofs, + execution_payload_value, ) } }; @@ -5057,7 +5104,6 @@ impl BeaconChain { self.log, "Produced block on state"; "block_size" => block_size, - "slot" => block.slot(), ); metrics::observe(&metrics::BLOCK_SIZE, block_size as f64); @@ -5075,6 +5121,11 @@ impl BeaconChain { // Use a context without block root or proposer index so that both are checked. let mut ctxt = ConsensusContext::new(block.slot()); + let consensus_block_value = self + .compute_beacon_block_reward(block.message(), Hash256::zero(), &mut state) + .map(|reward| reward.total) + .unwrap_or(0); + per_block_processing( &mut state, &block, @@ -5154,7 +5205,13 @@ impl BeaconChain { "slot" => block.slot() ); - Ok((block, state, maybe_sidecar_list)) + Ok(BeaconBlockResponse { + block, + state, + maybe_side_car: maybe_sidecar_list, + execution_payload_value: Some(execution_payload_value), + consensus_block_value: Some(consensus_block_value), + }) } /// This method must be called whenever an execution engine indicates that a payload is diff --git a/beacon_node/beacon_chain/src/execution_payload.rs b/beacon_node/beacon_chain/src/execution_payload.rs index 33c97efd26..093255b201 100644 --- a/beacon_node/beacon_chain/src/execution_payload.rs +++ b/beacon_node/beacon_chain/src/execution_payload.rs @@ -13,7 +13,8 @@ use crate::{ ExecutionPayloadError, }; use execution_layer::{ - BlockProposalContents, BuilderParams, NewPayloadRequest, PayloadAttributes, PayloadStatus, + BlockProposalContents, BlockProposalContentsType, BuilderParams, NewPayloadRequest, + PayloadAttributes, PayloadStatus, }; use fork_choice::{InvalidationOperation, PayloadVerificationStatus}; use proto_array::{Block as ProtoBlock, ExecutionStatus}; @@ -26,11 +27,11 @@ use state_processing::per_block_processing::{ use std::sync::Arc; use tokio::task::JoinHandle; use tree_hash::TreeHash; +use types::payload::BlockProductionVersion; use types::*; -pub type PreparePayloadResult = - Result, BlockProductionError>; -pub type PreparePayloadHandle = JoinHandle>>; +pub type PreparePayloadResult = Result, BlockProductionError>; +pub type PreparePayloadHandle = JoinHandle>>; #[derive(PartialEq)] pub enum AllowOptimisticImport { @@ -398,16 +399,14 @@ pub fn validate_execution_payload_for_gossip( /// Equivalent to the `get_execution_payload` function in the Validator Guide: /// /// https://github.com/ethereum/consensus-specs/blob/v1.1.5/specs/merge/validator.md#block-proposal -pub fn get_execution_payload< - T: BeaconChainTypes, - Payload: AbstractExecPayload + 'static, ->( +pub fn get_execution_payload( chain: Arc>, state: &BeaconState, parent_block_root: Hash256, proposer_index: u64, builder_params: BuilderParams, -) -> Result, BlockProductionError> { + block_production_version: BlockProductionVersion, +) -> Result, BlockProductionError> { // Compute all required values from the `state` now to avoid needing to pass it into a spawned // task. let spec = &chain.spec; @@ -440,7 +439,7 @@ pub fn get_execution_payload< .clone() .spawn_handle( async move { - prepare_execution_payload::( + prepare_execution_payload::( &chain, is_merge_transition_complete, timestamp, @@ -450,6 +449,7 @@ pub fn get_execution_payload< builder_params, withdrawals, parent_beacon_block_root, + block_production_version, ) .await }, @@ -475,7 +475,7 @@ pub fn get_execution_payload< /// /// https://github.com/ethereum/consensus-specs/blob/v1.1.5/specs/merge/validator.md#block-proposal #[allow(clippy::too_many_arguments)] -pub async fn prepare_execution_payload( +pub async fn prepare_execution_payload( chain: &Arc>, is_merge_transition_complete: bool, timestamp: u64, @@ -485,10 +485,10 @@ pub async fn prepare_execution_payload( builder_params: BuilderParams, withdrawals: Option>, parent_beacon_block_root: Option, -) -> Result, BlockProductionError> + block_production_version: BlockProductionVersion, +) -> Result, BlockProductionError> where T: BeaconChainTypes, - Payload: AbstractExecPayload, { let current_epoch = builder_params.slot.epoch(T::EthSpec::slots_per_epoch()); let spec = &chain.spec; @@ -506,7 +506,12 @@ where if is_terminal_block_hash_set && !is_activation_epoch_reached { // Use the "empty" payload if there's a terminal block hash, but we haven't reached the // terminal block epoch yet. - return BlockProposalContents::default_at_fork(fork).map_err(Into::into); + return Ok(BlockProposalContentsType::Full( + BlockProposalContents::Payload { + payload: FullPayload::default_at_fork(fork)?, + block_value: Uint256::zero(), + }, + )); } let terminal_pow_block_hash = execution_layer @@ -519,7 +524,12 @@ where } else { // If the merge transition hasn't occurred yet and the EL hasn't found the terminal // block, return an "empty" payload. - return BlockProposalContents::default_at_fork(fork).map_err(Into::into); + return Ok(BlockProposalContentsType::Full( + BlockProposalContents::Payload { + payload: FullPayload::default_at_fork(fork)?, + block_value: Uint256::zero(), + }, + )); } } else { latest_execution_payload_header_block_hash @@ -558,13 +568,14 @@ where // // This future is not executed here, it's up to the caller to await it. let block_contents = execution_layer - .get_payload::( + .get_payload( parent_hash, &payload_attributes, forkchoice_update_params, builder_params, fork, &chain.spec, + block_production_version, ) .await .map_err(BlockProductionError::GetPayloadFailed)?; diff --git a/beacon_node/beacon_chain/src/lib.rs b/beacon_node/beacon_chain/src/lib.rs index 4efc776b2c..e2d37078ac 100644 --- a/beacon_node/beacon_chain/src/lib.rs +++ b/beacon_node/beacon_chain/src/lib.rs @@ -56,10 +56,10 @@ pub mod validator_monitor; pub mod validator_pubkey_cache; pub use self::beacon_chain::{ - AttestationProcessingOutcome, AvailabilityProcessingStatus, BeaconChain, BeaconChainTypes, - BeaconStore, ChainSegmentResult, ForkChoiceError, OverrideForkchoiceUpdate, - ProduceBlockVerification, StateSkipConfig, WhenSlotSkipped, - INVALID_FINALIZED_MERGE_TRANSITION_BLOCK_SHUTDOWN_REASON, + AttestationProcessingOutcome, AvailabilityProcessingStatus, BeaconBlockResponse, + BeaconBlockResponseType, BeaconChain, BeaconChainTypes, BeaconStore, ChainSegmentResult, + ForkChoiceError, OverrideForkchoiceUpdate, ProduceBlockVerification, StateSkipConfig, + WhenSlotSkipped, INVALID_FINALIZED_MERGE_TRANSITION_BLOCK_SHUTDOWN_REASON, INVALID_JUSTIFIED_PAYLOAD_SHUTDOWN_REASON, }; pub use self::beacon_snapshot::BeaconSnapshot; diff --git a/beacon_node/beacon_chain/src/test_utils.rs b/beacon_node/beacon_chain/src/test_utils.rs index 7ef9842544..333318f52f 100644 --- a/beacon_node/beacon_chain/src/test_utils.rs +++ b/beacon_node/beacon_chain/src/test_utils.rs @@ -1,6 +1,7 @@ use crate::block_verification_types::{AsBlock, RpcBlock}; use crate::observed_operations::ObservationOutcome; pub use crate::persisted_beacon_chain::PersistedBeaconChain; +use crate::BeaconBlockResponseType; pub use crate::{ beacon_chain::{BEACON_CHAIN_DB_KEY, ETH1_CACHE_DB_KEY, FORK_CHOICE_DB_KEY, OP_POOL_DB_KEY}, migrate::MigratorConfig, @@ -60,6 +61,7 @@ use store::{config::StoreConfig, HotColdDB, ItemStore, LevelDB, MemoryStore}; use task_executor::TaskExecutor; use task_executor::{test_utils::TestRuntime, ShutdownReason}; use tree_hash::TreeHash; +use types::payload::BlockProductionVersion; use types::sync_selection_proof::SyncSelectionProof; pub use types::test_utils::generate_deterministic_keypairs; use types::test_utils::TestRandom; @@ -878,7 +880,7 @@ where let randao_reveal = self.sign_randao_reveal(&state, proposer_index, slot); - let (block, state, maybe_blob_sidecars) = self + let BeaconBlockResponseType::Full(block_response) = self .chain .produce_block_on_state( state, @@ -887,14 +889,18 @@ where randao_reveal, Some(graffiti), ProduceBlockVerification::VerifyRandao, + BlockProductionVersion::FullV2, ) .await - .unwrap(); + .unwrap() + else { + panic!("Should always be a full payload response"); + }; - let signed_block = block.sign( + let signed_block = block_response.block.sign( &self.validator_keypairs[proposer_index].sk, - &state.fork(), - state.genesis_validators_root(), + &block_response.state.fork(), + block_response.state.genesis_validators_root(), &self.spec, ); @@ -905,11 +911,13 @@ where | SignedBeaconBlock::Capella(_) => (signed_block, None), SignedBeaconBlock::Deneb(_) => ( signed_block, - maybe_blob_sidecars.map(|blobs| self.sign_blobs(blobs, &state, proposer_index)), + block_response + .maybe_side_car + .map(|blobs| self.sign_blobs(blobs, &block_response.state, proposer_index)), ), }; - (block_contents, state) + (block_contents, block_response.state) } /// Useful for the `per_block_processing` tests. Creates a block, and returns the state after @@ -938,7 +946,7 @@ where let pre_state = state.clone(); - let (block, state, maybe_blob_sidecars) = self + let BeaconBlockResponseType::Full(block_response) = self .chain .produce_block_on_state( state, @@ -947,14 +955,18 @@ where randao_reveal, Some(graffiti), ProduceBlockVerification::VerifyRandao, + BlockProductionVersion::FullV2, ) .await - .unwrap(); + .unwrap() + else { + panic!("Should always be a full payload response"); + }; - let signed_block = block.sign( + let signed_block = block_response.block.sign( &self.validator_keypairs[proposer_index].sk, - &state.fork(), - state.genesis_validators_root(), + &block_response.state.fork(), + block_response.state.genesis_validators_root(), &self.spec, ); @@ -964,14 +976,14 @@ where | SignedBeaconBlock::Merge(_) | SignedBeaconBlock::Capella(_) => (signed_block, None), SignedBeaconBlock::Deneb(_) => { - if let Some(blobs) = maybe_blob_sidecars { + if let Some(blobs) = block_response.maybe_side_car { let signed_blobs: SignedSidecarList> = Vec::from(blobs) .into_iter() .map(|blob| { blob.sign( &self.validator_keypairs[proposer_index].sk, - &state.fork(), - state.genesis_validators_root(), + &block_response.state.fork(), + block_response.state.genesis_validators_root(), &self.spec, ) }) @@ -990,7 +1002,6 @@ where } } }; - (block_contents, pre_state) } diff --git a/beacon_node/execution_layer/src/engine_api.rs b/beacon_node/execution_layer/src/engine_api.rs index 683e39a503..19b9a58eb6 100644 --- a/beacon_node/execution_layer/src/engine_api.rs +++ b/beacon_node/execution_layer/src/engine_api.rs @@ -466,6 +466,11 @@ impl From> } } +pub enum GetPayloadResponseType { + Full(GetPayloadResponse), + Blinded(GetPayloadResponse), +} + impl GetPayloadResponse { pub fn execution_payload_ref(&self) -> ExecutionPayloadRef { self.to_ref().into() diff --git a/beacon_node/execution_layer/src/lib.rs b/beacon_node/execution_layer/src/lib.rs index 623965ada3..3f75d0042d 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::builder_bid::BuilderBid; +use types::payload::BlockProductionVersion; use types::sidecar::{BlobItems, Sidecar}; -use types::KzgProofs; +use types::{AbstractExecPayload, ExecutionPayloadDeneb, KzgProofs}; use types::{ - AbstractExecPayload, BeaconStateError, BlindedPayload, BlockType, ChainSpec, Epoch, - ExecPayload, ExecutionPayloadCapella, ExecutionPayloadDeneb, ExecutionPayloadMerge, + BeaconStateError, BlindedPayload, ChainSpec, Epoch, ExecPayload, ExecutionPayloadCapella, + ExecutionPayloadMerge, FullPayload, ProposerPreparationData, PublicKeyBytes, Signature, Slot, }; -use types::{ProposerPreparationData, PublicKeyBytes, Signature, Slot}; mod block_hash; mod engine_api; @@ -87,9 +87,7 @@ pub enum ProvenancedPayload

{ Builder(P), } -impl> TryFrom> - for ProvenancedPayload> -{ +impl TryFrom> for ProvenancedPayload> { type Error = Error; fn try_from(value: BuilderBid) -> Result { @@ -112,12 +110,16 @@ impl> TryFrom> .map_err(|_| Error::InvalidPayloadConversion)?, block_value: builder_bid.value, kzg_commitments: builder_bid.blinded_blobs_bundle.commitments, - blobs: BlobItems::try_from_blob_roots(builder_bid.blinded_blobs_bundle.blob_roots) - .map_err(Error::InvalidBlobConversion)?, + blobs: BlobItems::::try_from_blob_roots( + builder_bid.blinded_blobs_bundle.blob_roots, + ) + .map_err(Error::InvalidBlobConversion)?, proofs: builder_bid.blinded_blobs_bundle.proofs, }, }; - Ok(ProvenancedPayload::Builder(block_proposal_contents)) + Ok(ProvenancedPayload::Builder( + BlockProposalContentsType::Blinded(block_proposal_contents), + )) } } @@ -145,6 +147,7 @@ pub enum Error { InvalidPayloadConversion, InvalidBlobConversion(String), BeaconStateError(BeaconStateError), + PayloadTypeMismatch, } impl From for Error { @@ -159,6 +162,11 @@ impl From for Error { } } +pub enum BlockProposalContentsType { + Full(BlockProposalContents>), + Blinded(BlockProposalContents>), +} + pub enum BlockProposalContents> { Payload { payload: Payload, @@ -173,6 +181,22 @@ pub enum BlockProposalContents> { }, } +impl From>> + for BlockProposalContents> +{ + fn from(item: BlockProposalContents>) -> Self { + let block_value = item.block_value().to_owned(); + + let blinded_payload: BlockProposalContents> = + BlockProposalContents::Payload { + payload: item.to_payload().execution_payload().into(), + block_value, + }; + + blinded_payload + } +} + impl> TryFrom> for BlockProposalContents { @@ -197,6 +221,17 @@ impl> TryFrom> } } +impl TryFrom> for BlockProposalContentsType { + type Error = Error; + + fn try_from(response_type: GetPayloadResponseType) -> Result { + match response_type { + GetPayloadResponseType::Full(response) => Ok(Self::Full(response.try_into()?)), + GetPayloadResponseType::Blinded(response) => Ok(Self::Blinded(response.try_into()?)), + } + } +} + #[allow(clippy::type_complexity)] impl> BlockProposalContents { pub fn deconstruct( @@ -206,19 +241,26 @@ impl> BlockProposalContents>, Option<>::BlobItems>, Option>, + Uint256, ) { match self { Self::Payload { payload, - block_value: _, - } => (payload, None, None, None), + block_value, + } => (payload, None, None, None, block_value), Self::PayloadAndBlobs { payload, - block_value: _, + block_value, kzg_commitments, blobs, proofs, - } => (payload, Some(kzg_commitments), Some(blobs), Some(proofs)), + } => ( + payload, + Some(kzg_commitments), + Some(blobs), + Some(proofs), + block_value, + ), } } @@ -795,7 +837,8 @@ impl ExecutionLayer { /// /// The result will be returned from the first node that returns successfully. No more nodes /// will be contacted. - pub async fn get_payload>( + #[allow(clippy::too_many_arguments)] + pub async fn get_payload( &self, parent_hash: ExecutionBlockHash, payload_attributes: &PayloadAttributes, @@ -803,14 +846,11 @@ impl ExecutionLayer { builder_params: BuilderParams, current_fork: ForkName, spec: &ChainSpec, - ) -> Result, Error> { - let payload_result = match Payload::block_type() { - BlockType::Blinded => { - let _timer = metrics::start_timer_vec( - &metrics::EXECUTION_LAYER_REQUEST_TIMES, - &[metrics::GET_BLINDED_PAYLOAD], - ); - self.get_blinded_payload( + block_production_version: BlockProductionVersion, + ) -> Result, Error> { + let payload_result_type = match block_production_version { + BlockProductionVersion::V3 => match self + .determine_and_fetch_payload( parent_hash, payload_attributes, forkchoice_update_params, @@ -819,27 +859,51 @@ impl ExecutionLayer { spec, ) .await - } - BlockType::Full => { + { + Ok(payload) => payload, + Err(e) => { + metrics::inc_counter_vec( + &metrics::EXECUTION_LAYER_GET_PAYLOAD_OUTCOME, + &[metrics::FAILURE], + ); + return Err(e); + } + }, + BlockProductionVersion::BlindedV2 => { let _timer = metrics::start_timer_vec( &metrics::EXECUTION_LAYER_REQUEST_TIMES, - &[metrics::GET_PAYLOAD], + &[metrics::GET_BLINDED_PAYLOAD], ); - self.get_full_payload( + self.determine_and_fetch_payload( + parent_hash, + payload_attributes, + forkchoice_update_params, + builder_params, + current_fork, + spec, + ) + .await? + } + BlockProductionVersion::FullV2 => self + .get_full_payload_with( parent_hash, payload_attributes, forkchoice_update_params, current_fork, + noop, ) .await - .and_then(GetPayloadResponse::try_into) - .map(ProvenancedPayload::Local) - } + .and_then(GetPayloadResponseType::try_into) + .map(ProvenancedPayload::Local)?, }; - // Track some metrics and return the result. - match payload_result { - Ok(ProvenancedPayload::Local(block_proposal_contents)) => { + let block_proposal_content_type = match payload_result_type { + ProvenancedPayload::Local(local_payload) => local_payload, + ProvenancedPayload::Builder(builder_payload) => builder_payload, + }; + + match block_proposal_content_type { + BlockProposalContentsType::Full(block_proposal_contents) => { metrics::inc_counter_vec( &metrics::EXECUTION_LAYER_GET_PAYLOAD_OUTCOME, &[metrics::SUCCESS], @@ -848,9 +912,15 @@ impl ExecutionLayer { &metrics::EXECUTION_LAYER_GET_PAYLOAD_SOURCE, &[metrics::LOCAL], ); - Ok(block_proposal_contents) + if matches!(block_production_version, BlockProductionVersion::BlindedV2) { + Ok(BlockProposalContentsType::Blinded( + block_proposal_contents.into(), + )) + } else { + Ok(BlockProposalContentsType::Full(block_proposal_contents)) + } } - Ok(ProvenancedPayload::Builder(block_proposal_contents)) => { + BlockProposalContentsType::Blinded(block_proposal_contents) => { metrics::inc_counter_vec( &metrics::EXECUTION_LAYER_GET_PAYLOAD_OUTCOME, &[metrics::SUCCESS], @@ -859,19 +929,12 @@ impl ExecutionLayer { &metrics::EXECUTION_LAYER_GET_PAYLOAD_SOURCE, &[metrics::BUILDER], ); - Ok(block_proposal_contents) - } - Err(e) => { - metrics::inc_counter_vec( - &metrics::EXECUTION_LAYER_GET_PAYLOAD_OUTCOME, - &[metrics::FAILURE], - ); - Err(e) + Ok(BlockProposalContentsType::Blinded(block_proposal_contents)) } } } - async fn get_blinded_payload>( + async fn determine_and_fetch_payload( &self, parent_hash: ExecutionBlockHash, payload_attributes: &PayloadAttributes, @@ -879,11 +942,10 @@ impl ExecutionLayer { builder_params: BuilderParams, current_fork: ForkName, spec: &ChainSpec, - ) -> Result>, Error> { + ) -> Result>, Error> { if let Some(builder) = self.builder() { let slot = builder_params.slot; let pubkey = builder_params.pubkey; - match builder_params.chain_health { ChainHealth::Healthy => { info!( @@ -895,7 +957,7 @@ impl ExecutionLayer { ); // Wait for the builder *and* local EL to produce a payload (or return an error). - let ((relay_result, relay_duration), (local_result, local_duration)) = tokio::join!( + let ((relay_result, relay_duration), (local_result_type, local_duration)) = tokio::join!( timed_future(metrics::GET_BLINDED_PAYLOAD_BUILDER, async { builder .get_builder_header::(slot, parent_hash, &pubkey) @@ -912,6 +974,11 @@ impl ExecutionLayer { }) ); + let local_result = match local_result_type? { + GetPayloadResponseType::Full(payload) => Ok(payload), + GetPayloadResponseType::Blinded(_) => Err(Error::PayloadTypeMismatch), + }; + info!( self.log(), "Requested blinded execution payload"; @@ -939,7 +1006,9 @@ impl ExecutionLayer { "local_block_hash" => ?local.block_hash(), "parent_hash" => ?parent_hash, ); - Ok(ProvenancedPayload::Local(local.try_into()?)) + Ok(ProvenancedPayload::Local(BlockProposalContentsType::Full( + local.try_into()?, + ))) } (Ok(None), Ok(local)) => { info!( @@ -949,7 +1018,9 @@ impl ExecutionLayer { "local_block_hash" => ?local.block_hash(), "parent_hash" => ?parent_hash, ); - Ok(ProvenancedPayload::Local(local.try_into()?)) + Ok(ProvenancedPayload::Local(BlockProposalContentsType::Full( + local.try_into()?, + ))) } (Ok(Some(relay)), Ok(local)) => { let header = &relay.data.message.header(); @@ -973,7 +1044,9 @@ impl ExecutionLayer { "local_block_value" => %local_value, "relay_value" => %relay_value ); - return Ok(ProvenancedPayload::Local(local.try_into()?)); + return Ok(ProvenancedPayload::Local( + BlockProposalContentsType::Full(local.try_into()?), + )); } else if local.should_override_builder().unwrap_or(false) { let percentage_difference = percentage_difference_u256(local_value, *relay_value); @@ -989,7 +1062,9 @@ impl ExecutionLayer { "local_block_value" => %local_value, "relay_value" => %relay_value ); - return Ok(ProvenancedPayload::Local(local.try_into()?)); + return Ok(ProvenancedPayload::Local( + BlockProposalContentsType::Full(local.try_into()?), + )); } } else { info!( @@ -1020,7 +1095,9 @@ impl ExecutionLayer { "relay_block_hash" => ?header.block_hash(), "parent_hash" => ?parent_hash, ); - Ok(ProvenancedPayload::Local(local.try_into()?)) + Ok(ProvenancedPayload::Local(BlockProposalContentsType::Full( + local.try_into()?, + ))) } Err(reason) => { metrics::inc_counter_vec( @@ -1035,7 +1112,9 @@ impl ExecutionLayer { "relay_block_hash" => ?header.block_hash(), "parent_hash" => ?parent_hash, ); - Ok(ProvenancedPayload::Local(local.try_into()?)) + Ok(ProvenancedPayload::Local(BlockProposalContentsType::Full( + local.try_into()?, + ))) } } } @@ -1132,28 +1211,10 @@ impl ExecutionLayer { current_fork, ) .await - .and_then(GetPayloadResponse::try_into) + .and_then(GetPayloadResponseType::try_into) .map(ProvenancedPayload::Local) } - /// Get a full payload without caching its result in the execution layer's payload cache. - async fn get_full_payload( - &self, - parent_hash: ExecutionBlockHash, - payload_attributes: &PayloadAttributes, - forkchoice_update_params: ForkchoiceUpdateParameters, - current_fork: ForkName, - ) -> Result, Error> { - self.get_full_payload_with( - parent_hash, - payload_attributes, - forkchoice_update_params, - current_fork, - noop, - ) - .await - } - /// Get a full payload and cache its result in the execution layer's payload cache. async fn get_full_payload_caching( &self, @@ -1161,7 +1222,7 @@ impl ExecutionLayer { payload_attributes: &PayloadAttributes, forkchoice_update_params: ForkchoiceUpdateParameters, current_fork: ForkName, - ) -> Result, Error> { + ) -> Result, Error> { self.get_full_payload_with( parent_hash, payload_attributes, @@ -1182,7 +1243,7 @@ impl ExecutionLayer { &ExecutionLayer, PayloadContentsRefTuple, ) -> Option>, - ) -> Result, Error> { + ) -> Result, Error> { self.engine() .request(move |engine| async move { let payload_id = if let Some(id) = engine @@ -1244,6 +1305,10 @@ impl ExecutionLayer { "timestamp" => payload_attributes.timestamp(), "parent_hash" => ?parent_hash, ); + let _timer = metrics::start_timer_vec( + &metrics::EXECUTION_LAYER_REQUEST_TIMES, + &[metrics::GET_PAYLOAD], + ); engine.api.get_payload::(current_fork, payload_id).await }.await?; @@ -1268,7 +1333,7 @@ impl ExecutionLayer { ); } - Ok(payload_response) + Ok(GetPayloadResponseType::Full(payload_response)) }) .await .map_err(Box::new) diff --git a/beacon_node/execution_layer/src/test_utils/mock_builder.rs b/beacon_node/execution_layer/src/test_utils/mock_builder.rs index 1e45ef2726..32b352b6ae 100644 --- a/beacon_node/execution_layer/src/test_utils/mock_builder.rs +++ b/beacon_node/execution_layer/src/test_utils/mock_builder.rs @@ -508,11 +508,7 @@ pub fn serve( finalized_hash: Some(finalized_execution_hash), }; - let (payload, _block_value, maybe_blobs_bundle): ( - ExecutionPayload, - Uint256, - Option>, - ) = builder + let payload_response_type = builder .el .get_full_payload_caching( head_execution_hash, @@ -521,38 +517,88 @@ pub fn serve( fork, ) .await - .map_err(|_| reject("couldn't get payload"))? - .into(); + .map_err(|_| reject("couldn't get payload"))?; - let mut message = match fork { - ForkName::Deneb => BuilderBid::Deneb(BuilderBidDeneb { - header: payload - .as_deneb() - .map_err(|_| reject("incorrect payload variant"))? - .into(), - blinded_blobs_bundle: maybe_blobs_bundle - .map(Into::into) - .unwrap_or_default(), - value: Uint256::from(DEFAULT_BUILDER_PAYLOAD_VALUE_WEI), - pubkey: builder.builder_sk.public_key().compress(), - }), - ForkName::Capella => BuilderBid::Capella(BuilderBidCapella { - header: payload - .as_capella() - .map_err(|_| reject("incorrect payload variant"))? - .into(), - value: Uint256::from(DEFAULT_BUILDER_PAYLOAD_VALUE_WEI), - pubkey: builder.builder_sk.public_key().compress(), - }), - ForkName::Merge => BuilderBid::Merge(BuilderBidMerge { - header: payload - .as_merge() - .map_err(|_| reject("incorrect payload variant"))? - .into(), - value: Uint256::from(DEFAULT_BUILDER_PAYLOAD_VALUE_WEI), - pubkey: builder.builder_sk.public_key().compress(), - }), - ForkName::Base | ForkName::Altair => return Err(reject("invalid fork")), + let mut message = match payload_response_type { + crate::GetPayloadResponseType::Full(payload_response) => { + let (payload, _block_value, maybe_blobs_bundle): ( + ExecutionPayload, + Uint256, + Option>, + ) = payload_response.into(); + + match fork { + ForkName::Deneb => BuilderBid::Deneb(BuilderBidDeneb { + header: payload + .as_deneb() + .map_err(|_| reject("incorrect payload variant"))? + .into(), + blinded_blobs_bundle: maybe_blobs_bundle + .map(Into::into) + .unwrap_or_default(), + value: Uint256::from(DEFAULT_BUILDER_PAYLOAD_VALUE_WEI), + pubkey: builder.builder_sk.public_key().compress(), + }), + ForkName::Capella => BuilderBid::Capella(BuilderBidCapella { + header: payload + .as_capella() + .map_err(|_| reject("incorrect payload variant"))? + .into(), + value: Uint256::from(DEFAULT_BUILDER_PAYLOAD_VALUE_WEI), + pubkey: builder.builder_sk.public_key().compress(), + }), + ForkName::Merge => BuilderBid::Merge(BuilderBidMerge { + header: payload + .as_merge() + .map_err(|_| reject("incorrect payload variant"))? + .into(), + value: Uint256::from(DEFAULT_BUILDER_PAYLOAD_VALUE_WEI), + pubkey: builder.builder_sk.public_key().compress(), + }), + ForkName::Base | ForkName::Altair => { + return Err(reject("invalid fork")) + } + } + } + crate::GetPayloadResponseType::Blinded(payload_response) => { + let (payload, _block_value, maybe_blobs_bundle): ( + ExecutionPayload, + Uint256, + Option>, + ) = payload_response.into(); + match fork { + ForkName::Deneb => BuilderBid::Deneb(BuilderBidDeneb { + header: payload + .as_deneb() + .map_err(|_| reject("incorrect payload variant"))? + .into(), + blinded_blobs_bundle: maybe_blobs_bundle + .map(Into::into) + .unwrap_or_default(), + value: Uint256::from(DEFAULT_BUILDER_PAYLOAD_VALUE_WEI), + pubkey: builder.builder_sk.public_key().compress(), + }), + ForkName::Capella => BuilderBid::Capella(BuilderBidCapella { + header: payload + .as_capella() + .map_err(|_| reject("incorrect payload variant"))? + .into(), + value: Uint256::from(DEFAULT_BUILDER_PAYLOAD_VALUE_WEI), + pubkey: builder.builder_sk.public_key().compress(), + }), + ForkName::Merge => BuilderBid::Merge(BuilderBidMerge { + header: payload + .as_merge() + .map_err(|_| reject("incorrect payload variant"))? + .into(), + value: Uint256::from(DEFAULT_BUILDER_PAYLOAD_VALUE_WEI), + pubkey: builder.builder_sk.public_key().compress(), + }), + ForkName::Base | ForkName::Altair => { + return Err(reject("invalid fork")) + } + } + } }; message.set_gas_limit(cached_data.gas_limit); diff --git a/beacon_node/execution_layer/src/test_utils/mock_execution_layer.rs b/beacon_node/execution_layer/src/test_utils/mock_execution_layer.rs index 6717428436..72f0388e24 100644 --- a/beacon_node/execution_layer/src/test_utils/mock_execution_layer.rs +++ b/beacon_node/execution_layer/src/test_utils/mock_execution_layer.rs @@ -5,12 +5,12 @@ use crate::{ }, Config, *, }; +use keccak_hash::H256; use kzg::Kzg; use sensitive_url::SensitiveUrl; use task_executor::TaskExecutor; use tempfile::NamedTempFile; -use tree_hash::TreeHash; -use types::{Address, ChainSpec, Epoch, EthSpec, FullPayload, Hash256, MainnetEthSpec}; +use types::{Address, ChainSpec, Epoch, EthSpec, Hash256, MainnetEthSpec}; pub struct MockExecutionLayer { pub server: MockServer, @@ -133,20 +133,25 @@ impl MockExecutionLayer { let suggested_fee_recipient = self.el.get_suggested_fee_recipient(validator_index).await; let payload_attributes = PayloadAttributes::new(timestamp, prev_randao, suggested_fee_recipient, None, None); - let payload: ExecutionPayload = self + + let block_proposal_content_type = self .el - .get_payload::>( + .get_payload( parent_hash, &payload_attributes, forkchoice_update_params, builder_params, ForkName::Merge, &self.spec, + BlockProductionVersion::FullV2, ) .await - .unwrap() - .to_payload() - .into(); + .unwrap(); + + let payload: ExecutionPayload = match block_proposal_content_type { + BlockProposalContentsType::Full(block) => block.to_payload().into(), + BlockProposalContentsType::Blinded(_) => panic!("Should always be a full payload"), + }; let block_hash = payload.block_hash(); assert_eq!(payload.parent_hash(), parent_hash); @@ -167,20 +172,64 @@ impl MockExecutionLayer { let suggested_fee_recipient = self.el.get_suggested_fee_recipient(validator_index).await; let payload_attributes = PayloadAttributes::new(timestamp, prev_randao, suggested_fee_recipient, None, None); - let payload_header = self + + let block_proposal_content_type = self .el - .get_payload::>( + .get_payload( parent_hash, &payload_attributes, forkchoice_update_params, builder_params, ForkName::Merge, &self.spec, + BlockProductionVersion::BlindedV2, ) .await - .unwrap() - .to_payload(); + .unwrap(); + match block_proposal_content_type { + BlockProposalContentsType::Full(block) => { + let payload_header = block.to_payload(); + self.assert_valid_execution_payload_on_head( + payload, + payload_header, + block_hash, + parent_hash, + block_number, + timestamp, + prev_randao, + ) + .await; + } + BlockProposalContentsType::Blinded(block) => { + let payload_header = block.to_payload(); + self.assert_valid_execution_payload_on_head( + payload, + payload_header, + block_hash, + parent_hash, + block_number, + timestamp, + prev_randao, + ) + .await; + } + }; + + self + } + + #[allow(clippy::too_many_arguments)] + pub async fn assert_valid_execution_payload_on_head>( + &self, + payload: ExecutionPayload, + payload_header: Payload, + block_hash: ExecutionBlockHash, + parent_hash: ExecutionBlockHash, + block_number: u64, + timestamp: u64, + prev_randao: H256, + ) { assert_eq!(payload_header.block_hash(), block_hash); assert_eq!(payload_header.parent_hash(), parent_hash); assert_eq!(payload_header.block_number(), block_number); @@ -224,8 +273,6 @@ impl MockExecutionLayer { assert_eq!(head_execution_block.block_number(), block_number); assert_eq!(head_execution_block.block_hash(), block_hash); assert_eq!(head_execution_block.parent_hash(), parent_hash); - - self } pub fn move_to_block_prior_to_terminal_block(self) -> Self { diff --git a/beacon_node/http_api/src/build_block_contents.rs b/beacon_node/http_api/src/build_block_contents.rs index c8f28fa9ae..f59a4b5215 100644 --- a/beacon_node/http_api/src/build_block_contents.rs +++ b/beacon_node/http_api/src/build_block_contents.rs @@ -1,62 +1,51 @@ use beacon_chain::BlockProductionError; use eth2::types::{BeaconBlockAndBlobSidecars, BlindedBeaconBlockAndBlobSidecars, BlockContents}; -use types::{ - BeaconBlock, BlindedBlobSidecarList, BlindedPayload, BlobSidecarList, EthSpec, ForkName, - FullPayload, -}; - +use types::{AbstractExecPayload, BeaconBlock, EthSpec, ForkName, SidecarList}; type Error = warp::reject::Rejection; -type FullBlockContents = BlockContents>; -type BlindedBlockContents = BlockContents>; -pub fn build_block_contents( +pub fn build_block_contents>( fork_name: ForkName, - block: BeaconBlock>, - maybe_blobs: Option>, -) -> Result, Error> { - match fork_name { - ForkName::Base | ForkName::Altair | ForkName::Merge | ForkName::Capella => { - Ok(BlockContents::Block(block)) - } - ForkName::Deneb => { - if let Some(blob_sidecars) = maybe_blobs { - let block_and_blobs = BeaconBlockAndBlobSidecars { - block, - blob_sidecars, - }; - - Ok(BlockContents::BlockAndBlobSidecars(block_and_blobs)) - } else { - Err(warp_utils::reject::block_production_error( - BlockProductionError::MissingBlobs, - )) + block: BeaconBlock, + maybe_blobs: Option>::Sidecar>>, +) -> Result, Error> { + match Payload::block_type() { + types::BlockType::Blinded => match fork_name { + ForkName::Base | ForkName::Altair | ForkName::Merge | ForkName::Capella => { + Ok(BlockContents::Block(block)) } - } - } -} - -pub fn build_blinded_block_contents( - fork_name: ForkName, - block: BeaconBlock>, - maybe_blobs: Option>, -) -> Result, Error> { - match fork_name { - ForkName::Base | ForkName::Altair | ForkName::Merge | ForkName::Capella => { - Ok(BlockContents::Block(block)) - } - ForkName::Deneb => { - if let Some(blinded_blob_sidecars) = maybe_blobs { - let block_and_blobs = BlindedBeaconBlockAndBlobSidecars { - blinded_block: block, - blinded_blob_sidecars, - }; - - Ok(BlockContents::BlindedBlockAndBlobSidecars(block_and_blobs)) - } else { - Err(warp_utils::reject::block_production_error( - BlockProductionError::MissingBlobs, - )) - } - } + ForkName::Deneb => { + if let Some(blinded_blob_sidecars) = maybe_blobs { + let block_and_blobs = BlindedBeaconBlockAndBlobSidecars { + blinded_block: block, + blinded_blob_sidecars, + }; + + Ok(BlockContents::BlindedBlockAndBlobSidecars(block_and_blobs)) + } else { + Err(warp_utils::reject::block_production_error( + BlockProductionError::MissingBlobs, + )) + } + } + }, + types::BlockType::Full => match fork_name { + ForkName::Base | ForkName::Altair | ForkName::Merge | ForkName::Capella => { + Ok(BlockContents::Block(block)) + } + ForkName::Deneb => { + if let Some(blob_sidecars) = maybe_blobs { + let block_and_blobs = BeaconBlockAndBlobSidecars { + block, + blob_sidecars, + }; + + Ok(BlockContents::BlockAndBlobSidecars(block_and_blobs)) + } else { + Err(warp_utils::reject::block_production_error( + BlockProductionError::MissingBlobs, + )) + } + } + }, } } diff --git a/beacon_node/http_api/src/lib.rs b/beacon_node/http_api/src/lib.rs index a807655189..309db204ae 100644 --- a/beacon_node/http_api/src/lib.rs +++ b/beacon_node/http_api/src/lib.rs @@ -14,6 +14,7 @@ mod build_block_contents; mod builder_states; mod database; mod metrics; +mod produce_block; mod proposer_duties; mod publish_blocks; mod standard_block_rewards; @@ -27,10 +28,11 @@ mod validator; mod validator_inclusion; mod version; +use crate::produce_block::{produce_blinded_block_v2, produce_block_v2, produce_block_v3}; use beacon_chain::{ attestation_verification::VerifiedAttestation, observed_operations::ObservationOutcome, validator_monitor::timestamp_now, AttestationError as AttnError, BeaconChain, BeaconChainError, - BeaconChainTypes, ProduceBlockVerification, WhenSlotSkipped, + BeaconChainTypes, WhenSlotSkipped, }; use beacon_processor::BeaconProcessorSend; pub use block_id::BlockId; @@ -39,8 +41,7 @@ use bytes::Bytes; use directory::DEFAULT_ROOT_DIR; use eth2::types::{ self as api_types, BroadcastValidation, EndpointVersion, ForkChoice, ForkChoiceNode, - SignedBlindedBlockContents, SignedBlockContents, SkipRandaoVerification, ValidatorId, - ValidatorStatus, + SignedBlindedBlockContents, SignedBlockContents, ValidatorId, ValidatorStatus, }; use lighthouse_network::{types::SyncState, EnrExt, NetworkGlobals, PeerId, PubsubMessage}; use lighthouse_version::version_with_platform; @@ -75,7 +76,7 @@ use tokio_stream::{ }; use types::{ Attestation, AttestationData, AttestationShufflingId, AttesterSlashing, BeaconStateError, - BlindedPayload, CommitteeCache, ConfigAndPreset, Epoch, EthSpec, ForkName, FullPayload, + BlindedPayload, CommitteeCache, ConfigAndPreset, Epoch, EthSpec, ForkName, ProposerPreparationData, ProposerSlashing, RelativeEpoch, SignedAggregateAndProof, SignedBlsToExecutionChange, SignedContributionAndProof, SignedValidatorRegistrationData, SignedVoluntaryExit, Slot, SyncCommitteeMessage, SyncContributionData, @@ -83,7 +84,7 @@ use types::{ use validator::pubkey_to_validator_index; use version::{ add_consensus_version_header, execution_optimistic_finalized_fork_versioned_response, - fork_versioned_response, inconsistent_fork_rejection, unsupported_version_rejection, V1, V2, + inconsistent_fork_rejection, unsupported_version_rejection, V1, V2, V3, }; use warp::http::StatusCode; use warp::sse::Event; @@ -3052,17 +3053,17 @@ pub fn serve( )) })) .and(warp::path::end()) + .and(warp::header::optional::("accept")) .and(not_while_syncing_filter.clone()) .and(warp::query::()) - .and(warp::header::optional::("accept")) .and(task_spawner_filter.clone()) .and(chain_filter.clone()) .and(log_filter.clone()) .then( |endpoint_version: EndpointVersion, slot: Slot, - query: api_types::ValidatorBlocksQuery, accept_header: Option, + query: api_types::ValidatorBlocksQuery, task_spawner: TaskSpawner, chain: Arc>, log: Logger| { @@ -3073,60 +3074,10 @@ pub fn serve( "slot" => slot ); - let randao_reveal = query.randao_reveal.decompress().map_err(|e| { - warp_utils::reject::custom_bad_request(format!( - "randao reveal is not a valid BLS signature: {:?}", - e - )) - })?; - - let randao_verification = - if query.skip_randao_verification == SkipRandaoVerification::Yes { - if !randao_reveal.is_infinity() { - return Err(warp_utils::reject::custom_bad_request( - "randao_reveal must be point-at-infinity if verification is skipped" - .into(), - )); - } - ProduceBlockVerification::NoVerification - } else { - ProduceBlockVerification::VerifyRandao - }; - - let (block, _, maybe_blobs) = chain - .produce_block_with_verification::>( - randao_reveal, - slot, - query.graffiti.map(Into::into), - randao_verification, - ) - .await - .map_err(warp_utils::reject::block_production_error)?; - let fork_name = block - .to_ref() - .fork_name(&chain.spec) - .map_err(inconsistent_fork_rejection)?; - - let block_contents = - build_block_contents::build_block_contents(fork_name, block, maybe_blobs)?; - - match accept_header { - Some(api_types::Accept::Ssz) => Response::builder() - .status(200) - .header("Content-Type", "application/octet-stream") - .body(block_contents.as_ssz_bytes().into()) - .map(|res: Response| { - add_consensus_version_header(res, fork_name) - }) - .map_err(|e| { - warp_utils::reject::custom_server_error(format!( - "failed to create response: {}", - e - )) - }), - _ => fork_versioned_response(endpoint_version, fork_name, block_contents) - .map(|response| warp::reply::json(&response).into_response()) - .map(|res| add_consensus_version_header(res, fork_name)), + if endpoint_version == V3 { + produce_block_v3(endpoint_version, accept_header, chain, slot, query).await + } else { + produce_block_v2(endpoint_version, accept_header, chain, slot, query).await } }) }, @@ -3154,65 +3105,8 @@ pub fn serve( task_spawner: TaskSpawner, chain: Arc>| { task_spawner.spawn_async_with_rejection(Priority::P0, async move { - let randao_reveal = query.randao_reveal.decompress().map_err(|e| { - warp_utils::reject::custom_bad_request(format!( - "randao reveal is not a valid BLS signature: {:?}", - e - )) - })?; - - let randao_verification = - if query.skip_randao_verification == SkipRandaoVerification::Yes { - if !randao_reveal.is_infinity() { - return Err(warp_utils::reject::custom_bad_request( - "randao_reveal must be point-at-infinity if verification is skipped" - .into() - )); - } - ProduceBlockVerification::NoVerification - } else { - ProduceBlockVerification::VerifyRandao - }; - - let (block, _, maybe_blobs) = chain - .produce_block_with_verification::>( - randao_reveal, - slot, - query.graffiti.map(Into::into), - randao_verification, - ) + produce_blinded_block_v2(EndpointVersion(2), accept_header, chain, slot, query) .await - .map_err(warp_utils::reject::block_production_error)?; - let fork_name = block - .to_ref() - .fork_name(&chain.spec) - .map_err(inconsistent_fork_rejection)?; - - let block_contents = build_block_contents::build_blinded_block_contents( - fork_name, - block, - maybe_blobs, - )?; - - match accept_header { - Some(api_types::Accept::Ssz) => Response::builder() - .status(200) - .header("Content-Type", "application/octet-stream") - .body(block_contents.as_ssz_bytes().into()) - .map(|res: Response| { - add_consensus_version_header(res, fork_name) - }) - .map_err(|e| { - warp_utils::reject::custom_server_error(format!( - "failed to create response: {}", - e - )) - }), - // Pose as a V2 endpoint so we return the fork `version`. - _ => fork_versioned_response(V2, fork_name, block_contents) - .map(|response| warp::reply::json(&response).into_response()) - .map(|res| add_consensus_version_header(res, fork_name)), - } }) }, ); @@ -3740,7 +3634,6 @@ pub fn serve( .as_ref() .ok_or(BeaconChainError::BuilderMissing) .map_err(warp_utils::reject::beacon_chain_error)?; - builder .post_builder_validators(&filtered_registration_data) .await diff --git a/beacon_node/http_api/src/produce_block.rs b/beacon_node/http_api/src/produce_block.rs new file mode 100644 index 0000000000..73da4853e6 --- /dev/null +++ b/beacon_node/http_api/src/produce_block.rs @@ -0,0 +1,231 @@ +use bytes::Bytes; +use std::sync::Arc; +use types::{payload::BlockProductionVersion, *}; + +use beacon_chain::{ + BeaconBlockResponse, BeaconBlockResponseType, BeaconChain, BeaconChainTypes, + ProduceBlockVerification, +}; +use eth2::types::{self as api_types, EndpointVersion, SkipRandaoVerification}; +use ssz::Encode; +use warp::{ + hyper::{Body, Response}, + Reply, +}; + +use crate::{ + build_block_contents, + version::{ + add_consensus_block_value_header, add_consensus_version_header, + add_execution_payload_blinded_header, add_execution_payload_value_header, + fork_versioned_response, inconsistent_fork_rejection, + }, +}; + +pub fn get_randao_verification( + query: &api_types::ValidatorBlocksQuery, + randao_reveal_infinity: bool, +) -> Result { + let randao_verification = if query.skip_randao_verification == SkipRandaoVerification::Yes { + if !randao_reveal_infinity { + return Err(warp_utils::reject::custom_bad_request( + "randao_reveal must be point-at-infinity if verification is skipped".into(), + )); + } + ProduceBlockVerification::NoVerification + } else { + ProduceBlockVerification::VerifyRandao + }; + + Ok(randao_verification) +} + +pub async fn produce_block_v3( + endpoint_version: EndpointVersion, + accept_header: Option, + chain: Arc>, + slot: Slot, + query: api_types::ValidatorBlocksQuery, +) -> Result, warp::Rejection> { + let randao_reveal = query.randao_reveal.decompress().map_err(|e| { + warp_utils::reject::custom_bad_request(format!( + "randao reveal is not a valid BLS signature: {:?}", + e + )) + })?; + + let randao_verification = get_randao_verification(&query, randao_reveal.is_infinity())?; + + let block_response_type = chain + .produce_block_with_verification( + randao_reveal, + slot, + query.graffiti.map(Into::into), + randao_verification, + BlockProductionVersion::V3, + ) + .await + .map_err(|e| { + warp_utils::reject::custom_bad_request(format!("failed to fetch a block: {:?}", e)) + })?; + + match block_response_type { + BeaconBlockResponseType::Full(block_response) => { + build_response_v3(chain, block_response, endpoint_version, accept_header) + } + BeaconBlockResponseType::Blinded(block_response) => { + build_response_v3(chain, block_response, endpoint_version, accept_header) + } + } +} + +pub fn build_response_v3>( + chain: Arc>, + block_response: BeaconBlockResponse, + endpoint_version: EndpointVersion, + accept_header: Option, +) -> Result, warp::Rejection> { + let fork_name = block_response + .block + .to_ref() + .fork_name(&chain.spec) + .map_err(inconsistent_fork_rejection)?; + + let block_contents = build_block_contents::build_block_contents( + fork_name, + block_response.block, + block_response.maybe_side_car, + )?; + + let execution_payload_blinded = Payload::block_type() == BlockType::Blinded; + + match accept_header { + Some(api_types::Accept::Ssz) => Response::builder() + .status(200) + .header("Content-Type", "application/ssz") + .body(block_contents.as_ssz_bytes().into()) + .map(|res: Response| add_consensus_version_header(res, fork_name)) + .map(|res| add_execution_payload_blinded_header(res, execution_payload_blinded)) + .map(|res: Response| { + add_execution_payload_value_header(res, block_response.execution_payload_value) + }) + .map(|res| add_consensus_block_value_header(res, block_response.consensus_block_value)) + .map_err(|e| -> warp::Rejection { + warp_utils::reject::custom_server_error(format!("failed to create response: {}", e)) + }), + _ => fork_versioned_response(endpoint_version, fork_name, block_contents) + .map(|response| warp::reply::json(&response).into_response()) + .map(|res| add_consensus_version_header(res, fork_name)) + .map(|res| add_execution_payload_blinded_header(res, execution_payload_blinded)) + .map(|res| { + add_execution_payload_value_header(res, block_response.execution_payload_value) + }) + .map(|res| add_consensus_block_value_header(res, block_response.consensus_block_value)), + } +} + +pub async fn produce_blinded_block_v2( + endpoint_version: EndpointVersion, + accept_header: Option, + chain: Arc>, + slot: Slot, + query: api_types::ValidatorBlocksQuery, +) -> Result, warp::Rejection> { + let randao_reveal = query.randao_reveal.decompress().map_err(|e| { + warp_utils::reject::custom_bad_request(format!( + "randao reveal is not a valid BLS signature: {:?}", + e + )) + })?; + + let randao_verification = get_randao_verification(&query, randao_reveal.is_infinity())?; + let block_response_type = chain + .produce_block_with_verification( + randao_reveal, + slot, + query.graffiti.map(Into::into), + randao_verification, + BlockProductionVersion::BlindedV2, + ) + .await + .map_err(warp_utils::reject::block_production_error)?; + + match block_response_type { + BeaconBlockResponseType::Full(block_response) => { + build_response_v2(chain, block_response, endpoint_version, accept_header) + } + BeaconBlockResponseType::Blinded(block_response) => { + build_response_v2(chain, block_response, endpoint_version, accept_header) + } + } +} + +pub async fn produce_block_v2( + endpoint_version: EndpointVersion, + accept_header: Option, + chain: Arc>, + slot: Slot, + query: api_types::ValidatorBlocksQuery, +) -> Result, warp::Rejection> { + let randao_reveal = query.randao_reveal.decompress().map_err(|e| { + warp_utils::reject::custom_bad_request(format!( + "randao reveal is not a valid BLS signature: {:?}", + e + )) + })?; + + let randao_verification = get_randao_verification(&query, randao_reveal.is_infinity())?; + + let block_response_type = chain + .produce_block_with_verification( + randao_reveal, + slot, + query.graffiti.map(Into::into), + randao_verification, + BlockProductionVersion::FullV2, + ) + .await + .map_err(warp_utils::reject::block_production_error)?; + + match block_response_type { + BeaconBlockResponseType::Full(block_response) => { + build_response_v2(chain, block_response, endpoint_version, accept_header) + } + BeaconBlockResponseType::Blinded(block_response) => { + build_response_v2(chain, block_response, endpoint_version, accept_header) + } + } +} + +pub fn build_response_v2>( + chain: Arc>, + block_response: BeaconBlockResponse, + endpoint_version: EndpointVersion, + accept_header: Option, +) -> Result, warp::Rejection> { + let fork_name = block_response + .block + .to_ref() + .fork_name(&chain.spec) + .map_err(inconsistent_fork_rejection)?; + + let block_contents = build_block_contents::build_block_contents( + fork_name, + block_response.block, + block_response.maybe_side_car, + )?; + + match accept_header { + Some(api_types::Accept::Ssz) => Response::builder() + .status(200) + .header("Content-Type", "application/octet-stream") + .body(block_contents.as_ssz_bytes().into()) + .map(|res: Response| add_consensus_version_header(res, fork_name)) + .map_err(|e| { + warp_utils::reject::custom_server_error(format!("failed to create response: {}", e)) + }), + _ => fork_versioned_response(endpoint_version, fork_name, block_contents) + .map(|response| warp::reply::json(&response).into_response()) + .map(|res| add_consensus_version_header(res, fork_name)), + } +} diff --git a/beacon_node/http_api/src/validator.rs b/beacon_node/http_api/src/validator.rs index 18e9dbf636..7f11ddd8f4 100644 --- a/beacon_node/http_api/src/validator.rs +++ b/beacon_node/http_api/src/validator.rs @@ -1,5 +1,5 @@ use beacon_chain::{BeaconChain, BeaconChainError, BeaconChainTypes}; -use types::*; +use types::{BeaconState, PublicKeyBytes}; /// Uses the `chain.validator_pubkey_cache` to resolve a pubkey to a validator /// index and then ensures that the validator exists in the given `state`. diff --git a/beacon_node/http_api/src/version.rs b/beacon_node/http_api/src/version.rs index e01ff98220..7b06901243 100644 --- a/beacon_node/http_api/src/version.rs +++ b/beacon_node/http_api/src/version.rs @@ -1,12 +1,16 @@ use crate::api_types::fork_versioned_response::ExecutionOptimisticFinalizedForkVersionedResponse; use crate::api_types::EndpointVersion; -use eth2::CONSENSUS_VERSION_HEADER; +use eth2::{ + CONSENSUS_BLOCK_VALUE_HEADER, CONSENSUS_VERSION_HEADER, EXECUTION_PAYLOAD_BLINDED_HEADER, + EXECUTION_PAYLOAD_VALUE_HEADER, +}; use serde::Serialize; -use types::{ForkName, ForkVersionedResponse, InconsistentFork}; +use types::{ForkName, ForkVersionedResponse, InconsistentFork, Uint256}; use warp::reply::{self, Reply, Response}; pub const V1: EndpointVersion = EndpointVersion(1); pub const V2: EndpointVersion = EndpointVersion(2); +pub const V3: EndpointVersion = EndpointVersion(3); pub fn fork_versioned_response( endpoint_version: EndpointVersion, @@ -15,7 +19,7 @@ pub fn fork_versioned_response( ) -> Result, warp::reject::Rejection> { let fork_name = if endpoint_version == V1 { None - } else if endpoint_version == V2 { + } else if endpoint_version == V2 || endpoint_version == V3 { Some(fork_name) } else { return Err(unsupported_version_rejection(endpoint_version)); @@ -53,6 +57,45 @@ pub fn add_consensus_version_header(reply: T, fork_name: ForkName) -> reply::with_header(reply, CONSENSUS_VERSION_HEADER, fork_name.to_string()).into_response() } +/// Add the `Eth-Execution-Payload-Blinded` header to a response. +pub fn add_execution_payload_blinded_header( + reply: T, + execution_payload_blinded: bool, +) -> Response { + reply::with_header( + reply, + EXECUTION_PAYLOAD_BLINDED_HEADER, + execution_payload_blinded.to_string(), + ) + .into_response() +} + +/// Add the `Eth-Execution-Payload-Value` header to a response. +pub fn add_execution_payload_value_header( + reply: T, + execution_payload_value: Option, +) -> Response { + reply::with_header( + reply, + EXECUTION_PAYLOAD_VALUE_HEADER, + execution_payload_value.unwrap_or_default().to_string(), + ) + .into_response() +} + +/// Add the `Eth-Consensus-Block-Value` header to a response. +pub fn add_consensus_block_value_header( + reply: T, + consensus_payload_value: Option, +) -> Response { + reply::with_header( + reply, + CONSENSUS_BLOCK_VALUE_HEADER, + consensus_payload_value.unwrap_or_default().to_string(), + ) + .into_response() +} + pub fn inconsistent_fork_rejection(error: InconsistentFork) -> warp::reject::Rejection { warp_utils::reject::custom_server_error(format!("wrong fork: {:?}", error)) } diff --git a/beacon_node/http_api/tests/interactive_tests.rs b/beacon_node/http_api/tests/interactive_tests.rs index 17213d6f53..327215209f 100644 --- a/beacon_node/http_api/tests/interactive_tests.rs +++ b/beacon_node/http_api/tests/interactive_tests.rs @@ -21,6 +21,8 @@ use types::{ MainnetEthSpec, MinimalEthSpec, ProposerPreparationData, Slot, }; +use eth2::types::ForkVersionedBeaconBlockType::{Blinded, Full}; + type E = MainnetEthSpec; // Test that the deposit_contract endpoint returns the correct chain_id and address. @@ -617,13 +619,18 @@ pub async fn proposer_boost_re_org_test( let randao_reveal = harness .sign_randao_reveal(&state_b, proposer_index, slot_c) .into(); - let unsigned_block_contents_c = tester + let unsigned_block_type = tester .client - .get_validator_blocks(slot_c, &randao_reveal, None) + .get_validator_blocks_v3::(slot_c, &randao_reveal, None) .await - .unwrap() - .data; - let (unsigned_block_c, block_c_blobs) = unsigned_block_contents_c.deconstruct(); + .unwrap(); + + let (unsigned_block_c, block_c_blobs) = match unsigned_block_type { + Full(unsigned_block_contents_c) => unsigned_block_contents_c.data.deconstruct(), + Blinded(_) => { + panic!("Should not be a blinded block"); + } + }; let block_c = harness.sign_beacon_block(unsigned_block_c, &state_b); if should_re_org { diff --git a/beacon_node/http_api/tests/tests.rs b/beacon_node/http_api/tests/tests.rs index 82a3fe6eee..d532859c79 100644 --- a/beacon_node/http_api/tests/tests.rs +++ b/beacon_node/http_api/tests/tests.rs @@ -3544,7 +3544,6 @@ impl ApiTester { .cached_head() .head_random() .unwrap(); - let (_, randao_reveal) = self.get_test_randao(slot, epoch).await; let payload: BlindedPayload = self @@ -4586,8 +4585,7 @@ impl ApiTester { assert_eq!(withdrawal_response.finalized, Some(false)); assert_eq!(withdrawal_response.data, expected_withdrawals.to_vec()); } - Err(e) => { - println!("{:?}", e); + Err(_) => { panic!("query failed incorrectly"); } } diff --git a/common/eth2/src/lib.rs b/common/eth2/src/lib.rs index 2a3e3242ce..c59e59abf5 100644 --- a/common/eth2/src/lib.rs +++ b/common/eth2/src/lib.rs @@ -38,8 +38,12 @@ use store::fork_versioned_response::ExecutionOptimisticFinalizedForkVersionedRes pub const V1: EndpointVersion = EndpointVersion(1); pub const V2: EndpointVersion = EndpointVersion(2); +pub const V3: EndpointVersion = EndpointVersion(3); pub const CONSENSUS_VERSION_HEADER: &str = "Eth-Consensus-Version"; +pub const EXECUTION_PAYLOAD_BLINDED_HEADER: &str = "Eth-Execution-Payload-Blinded"; +pub const EXECUTION_PAYLOAD_VALUE_HEADER: &str = "Eth-Execution-Payload-Value"; +pub const CONSENSUS_BLOCK_VALUE_HEADER: &str = "Eth-Consensus-Block-Value"; #[derive(Debug)] pub enum Error { @@ -1628,6 +1632,26 @@ impl BeaconNodeHttpClient { .await } + /// `GET v2/validator/blocks/{slot}` + pub async fn get_validator_blocks_modular>( + &self, + slot: Slot, + randao_reveal: &SignatureBytes, + graffiti: Option<&Graffiti>, + skip_randao_verification: SkipRandaoVerification, + ) -> Result>, Error> { + let path = self + .get_validator_blocks_path::( + slot, + randao_reveal, + graffiti, + skip_randao_verification, + ) + .await?; + + self.get(path).await + } + /// returns `GET v2/validator/blocks/{slot}` URL path pub async fn get_validator_blocks_path>( &self, @@ -1660,24 +1684,70 @@ impl BeaconNodeHttpClient { Ok(path) } - /// `GET v2/validator/blocks/{slot}` - pub async fn get_validator_blocks_modular>( + /// `GET v3/validator/blocks/{slot}` + pub async fn get_validator_blocks_v3( + &self, + slot: Slot, + randao_reveal: &SignatureBytes, + graffiti: Option<&Graffiti>, + ) -> Result, Error> { + self.get_validator_blocks_v3_modular( + slot, + randao_reveal, + graffiti, + SkipRandaoVerification::No, + ) + .await + } + + /// `GET v3/validator/blocks/{slot}` + pub async fn get_validator_blocks_v3_modular( &self, slot: Slot, randao_reveal: &SignatureBytes, graffiti: Option<&Graffiti>, skip_randao_verification: SkipRandaoVerification, - ) -> Result>, Error> { - let path = self - .get_validator_blocks_path::( - slot, - randao_reveal, - graffiti, - skip_randao_verification, - ) - .await?; + ) -> Result, Error> { + let mut path = self.eth_path(V3)?; - self.get(path).await + path.path_segments_mut() + .map_err(|()| Error::InvalidUrl(self.server.clone()))? + .push("validator") + .push("blocks") + .push(&slot.to_string()); + + path.query_pairs_mut() + .append_pair("randao_reveal", &randao_reveal.to_string()); + + if let Some(graffiti) = graffiti { + path.query_pairs_mut() + .append_pair("graffiti", &graffiti.to_string()); + } + + if skip_randao_verification == SkipRandaoVerification::Yes { + path.query_pairs_mut() + .append_pair("skip_randao_verification", ""); + } + + let response = self.get_response(path, |b| b).await?; + + let is_blinded_payload = response + .headers() + .get(EXECUTION_PAYLOAD_BLINDED_HEADER) + .map(|value| value.to_str().unwrap_or_default().to_lowercase() == "true") + .unwrap_or(false); + + if is_blinded_payload { + let blinded_payload = response + .json::>>>() + .await?; + Ok(ForkVersionedBeaconBlockType::Blinded(blinded_payload)) + } else { + let full_payload = response + .json::>>>() + .await?; + Ok(ForkVersionedBeaconBlockType::Full(full_payload)) + } } /// `GET v2/validator/blocks/{slot}` in ssz format diff --git a/common/eth2/src/types.rs b/common/eth2/src/types.rs index e87f254f10..c2d85a31d3 100644 --- a/common/eth2/src/types.rs +++ b/common/eth2/src/types.rs @@ -1384,6 +1384,11 @@ pub mod serde_status_code { } } +pub enum ForkVersionedBeaconBlockType { + Full(ForkVersionedResponse>>), + Blinded(ForkVersionedResponse>>), +} + #[cfg(test)] mod tests { use super::*; diff --git a/consensus/types/src/payload.rs b/consensus/types/src/payload.rs index ff7caffcf3..6d584fc1eb 100644 --- a/consensus/types/src/payload.rs +++ b/consensus/types/src/payload.rs @@ -979,3 +979,10 @@ impl From> for ExecutionPayloadHeader { } } } + +/// The block production flow version to be used. +pub enum BlockProductionVersion { + V3, + BlindedV2, + FullV2, +} diff --git a/testing/execution_engine_integration/src/test_rig.rs b/testing/execution_engine_integration/src/test_rig.rs index 48195f871d..42667f27c6 100644 --- a/testing/execution_engine_integration/src/test_rig.rs +++ b/testing/execution_engine_integration/src/test_rig.rs @@ -4,7 +4,8 @@ use crate::execution_engine::{ use crate::transactions::transactions; use ethers_providers::Middleware; use execution_layer::{ - BuilderParams, ChainHealth, ExecutionLayer, PayloadAttributes, PayloadStatus, + BlockProposalContentsType, BuilderParams, ChainHealth, ExecutionLayer, PayloadAttributes, + PayloadStatus, }; use fork_choice::ForkchoiceUpdateParameters; use reqwest::{header::CONTENT_TYPE, Client}; @@ -14,9 +15,10 @@ use std::sync::Arc; use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; use task_executor::TaskExecutor; use tokio::time::sleep; +use types::payload::BlockProductionVersion; use types::{ Address, ChainSpec, EthSpec, ExecutionBlockHash, ExecutionPayload, ExecutionPayloadHeader, - ForkName, FullPayload, Hash256, MainnetEthSpec, PublicKeyBytes, Slot, Uint256, + ForkName, Hash256, MainnetEthSpec, PublicKeyBytes, Slot, Uint256, }; const EXECUTION_ENGINE_START_TIMEOUT: Duration = Duration::from_secs(60); @@ -322,21 +324,26 @@ impl TestRig { Some(vec![]), None, ); - let valid_payload = self + let block_proposal_content_type = self .ee_a .execution_layer - .get_payload::>( + .get_payload( parent_hash, &payload_attributes, forkchoice_update_params, builder_params, TEST_FORK, &self.spec, + BlockProductionVersion::FullV2, ) .await - .unwrap() - .to_payload() - .execution_payload(); + .unwrap(); + + let valid_payload = match block_proposal_content_type { + BlockProposalContentsType::Full(block) => block.to_payload().execution_payload(), + BlockProposalContentsType::Blinded(_) => panic!("Should always be a full payload"), + }; + assert_eq!(valid_payload.transactions().len(), pending_txs.len()); /* @@ -468,21 +475,25 @@ impl TestRig { Some(vec![]), None, ); - let second_payload = self + let block_proposal_content_type = self .ee_a .execution_layer - .get_payload::>( + .get_payload( parent_hash, &payload_attributes, forkchoice_update_params, builder_params, TEST_FORK, &self.spec, + BlockProductionVersion::FullV2, ) .await - .unwrap() - .to_payload() - .execution_payload(); + .unwrap(); + + let second_payload = match block_proposal_content_type { + BlockProposalContentsType::Full(block) => block.to_payload().execution_payload(), + BlockProposalContentsType::Blinded(_) => panic!("Should always be a full payload"), + }; /* * Execution Engine A: From 36d88498135d311048a299f2f69a9324033cafe6 Mon Sep 17 00:00:00 2001 From: Jimmy Chen Date: Fri, 3 Nov 2023 00:12:19 +0000 Subject: [PATCH 02/23] Add commmand for pruning states (#4835) ## Issue Addressed Closes #4481. (Continuation of #4648) ## Proposed Changes - [x] Add `lighthouse db prune-states` - [x] Make it work - [x] Ensure block roots are handled correctly (to be addressed in 4735) - [x] Check perf on mainnet/Goerli/Gnosis (takes a few seconds max) - [x] Run block root healing logic (#4875 ) at the beginning - [x] Add some tests - [x] Update docs - [x] Add `--freezer` flag and other improvements to `lighthouse db inspect` Co-authored-by: Michael Sproul Co-authored-by: Jimmy Chen Co-authored-by: Michael Sproul --- beacon_node/beacon_chain/tests/store_tests.rs | 73 +++++++- beacon_node/store/src/hot_cold_store.rs | 89 ++++++++++ beacon_node/store/src/leveldb_store.rs | 18 +- beacon_node/store/src/lib.rs | 7 +- beacon_node/store/src/memory_store.rs | 2 +- book/src/database-migrations.md | 35 ++++ consensus/fork_choice/tests/tests.rs | 14 +- database_manager/src/lib.rs | 164 +++++++++++++++++- 8 files changed, 373 insertions(+), 29 deletions(-) diff --git a/beacon_node/beacon_chain/tests/store_tests.rs b/beacon_node/beacon_chain/tests/store_tests.rs index e8bb76778b..59a4c1decc 100644 --- a/beacon_node/beacon_chain/tests/store_tests.rs +++ b/beacon_node/beacon_chain/tests/store_tests.rs @@ -27,7 +27,7 @@ use std::collections::HashSet; use std::convert::TryInto; use std::sync::Arc; use std::time::Duration; -use store::metadata::{SchemaVersion, CURRENT_SCHEMA_VERSION}; +use store::metadata::{SchemaVersion, CURRENT_SCHEMA_VERSION, STATE_UPPER_LIMIT_NO_RETAIN}; use store::{ chunked_vector::{chunk_key, Field}, get_key_for_col, @@ -3306,6 +3306,77 @@ fn check_blob_existence( } } +#[tokio::test] +async fn prune_historic_states() { + let num_blocks_produced = E::slots_per_epoch() * 5; + let db_path = tempdir().unwrap(); + let store = get_store(&db_path); + let harness = get_harness(store.clone(), LOW_VALIDATOR_COUNT); + let genesis_state_root = harness.chain.genesis_state_root; + let genesis_state = harness + .chain + .get_state(&genesis_state_root, None) + .unwrap() + .unwrap(); + + harness + .extend_chain( + num_blocks_produced as usize, + BlockStrategy::OnCanonicalHead, + AttestationStrategy::AllValidators, + ) + .await; + + // Check historical state is present. + let state_roots_iter = harness + .chain + .forwards_iter_state_roots(Slot::new(0)) + .unwrap(); + for (state_root, slot) in state_roots_iter + .take(E::slots_per_epoch() as usize) + .map(Result::unwrap) + { + assert!(store.get_state(&state_root, Some(slot)).unwrap().is_some()); + } + + store + .prune_historic_states(genesis_state_root, &genesis_state) + .unwrap(); + + // Check that anchor info is updated. + let anchor_info = store.get_anchor_info().unwrap(); + assert_eq!(anchor_info.state_lower_limit, 0); + assert_eq!(anchor_info.state_upper_limit, STATE_UPPER_LIMIT_NO_RETAIN); + + // Historical states should be pruned. + let state_roots_iter = harness + .chain + .forwards_iter_state_roots(Slot::new(1)) + .unwrap(); + for (state_root, slot) in state_roots_iter + .take(E::slots_per_epoch() as usize) + .map(Result::unwrap) + { + assert!(store.get_state(&state_root, Some(slot)).unwrap().is_none()); + } + + // Ensure that genesis state is still accessible + let genesis_state_root = harness.chain.genesis_state_root; + assert!(store + .get_state(&genesis_state_root, Some(Slot::new(0))) + .unwrap() + .is_some()); + + // Run for another two epochs. + let additional_blocks_produced = 2 * E::slots_per_epoch(); + harness + .extend_slots(additional_blocks_produced as usize) + .await; + + check_finalization(&harness, num_blocks_produced + additional_blocks_produced); + check_split_slot(&harness, store); +} + /// Checks that two chains are the same, for the purpose of these tests. /// /// Several fields that are hard/impossible to check are ignored (e.g., the store). diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index 640647db35..6e2a2ae583 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -2222,6 +2222,8 @@ impl, Cold: ItemStore> HotColdDB Ok(()) } + /// This function fills in missing block roots between last restore point slot and split + /// slot, if any. pub fn heal_freezer_block_roots(&self) -> Result<(), Error> { let split = self.get_split_info(); let last_restore_point_slot = (split.slot - 1) / self.config.slots_per_restore_point @@ -2250,6 +2252,93 @@ impl, Cold: ItemStore> HotColdDB Ok(()) } + + /// Delete *all* states from the freezer database and update the anchor accordingly. + /// + /// WARNING: this method deletes the genesis state and replaces it with the provided + /// `genesis_state`. This is to support its use in schema migrations where the storage scheme of + /// the genesis state may be modified. It is the responsibility of the caller to ensure that the + /// genesis state is correct, else a corrupt database will be created. + pub fn prune_historic_states( + &self, + genesis_state_root: Hash256, + genesis_state: &BeaconState, + ) -> Result<(), Error> { + // Make sure there is no missing block roots before pruning + self.heal_freezer_block_roots()?; + + // Update the anchor to use the dummy state upper limit and disable historic state storage. + let old_anchor = self.get_anchor_info(); + let new_anchor = if let Some(old_anchor) = old_anchor.clone() { + AnchorInfo { + state_upper_limit: STATE_UPPER_LIMIT_NO_RETAIN, + state_lower_limit: Slot::new(0), + ..old_anchor.clone() + } + } else { + AnchorInfo { + anchor_slot: Slot::new(0), + oldest_block_slot: Slot::new(0), + oldest_block_parent: Hash256::zero(), + state_upper_limit: STATE_UPPER_LIMIT_NO_RETAIN, + state_lower_limit: Slot::new(0), + } + }; + + // Commit the anchor change immediately: if the cold database ops fail they can always be + // retried, and we can't do them atomically with this change anyway. + self.compare_and_set_anchor_info_with_write(old_anchor, Some(new_anchor))?; + + // Stage freezer data for deletion. Do not bother loading and deserializing values as this + // wastes time and is less schema-agnostic. My hope is that this method will be useful for + // migrating to the tree-states schema (delete everything in the freezer then start afresh). + let mut cold_ops = vec![]; + + let columns = [ + DBColumn::BeaconState, + DBColumn::BeaconStateSummary, + DBColumn::BeaconRestorePoint, + DBColumn::BeaconStateRoots, + DBColumn::BeaconHistoricalRoots, + DBColumn::BeaconRandaoMixes, + DBColumn::BeaconHistoricalSummaries, + ]; + + for column in columns { + for res in self.cold_db.iter_column_keys::>(column) { + let key = res?; + cold_ops.push(KeyValueStoreOp::DeleteKey(get_key_for_col( + column.as_str(), + &key, + ))); + } + } + + // XXX: We need to commit the mass deletion here *before* re-storing the genesis state, as + // the current schema performs reads as part of `store_cold_state`. This can be deleted + // once the target schema is tree-states. If the process is killed before the genesis state + // is written this can be fixed by re-running. + info!( + self.log, + "Deleting historic states"; + "num_kv" => cold_ops.len(), + ); + self.cold_db.do_atomically(std::mem::take(&mut cold_ops))?; + + // If we just deleted the the genesis state, re-store it using the *current* schema, which + // may be different from the schema of the genesis state we just deleted. + if self.get_split_slot() > 0 { + info!( + self.log, + "Re-storing genesis state"; + "state_root" => ?genesis_state_root, + ); + self.store_cold_state(&genesis_state_root, genesis_state, &mut cold_ops)?; + self.cold_db.do_atomically(cold_ops)?; + } + + Ok(()) + } } /// Advance the split point of the store, moving new finalized states to the freezer. diff --git a/beacon_node/store/src/leveldb_store.rs b/beacon_node/store/src/leveldb_store.rs index 0e4cd0e3b1..62619dd2c7 100644 --- a/beacon_node/store/src/leveldb_store.rs +++ b/beacon_node/store/src/leveldb_store.rs @@ -169,6 +169,7 @@ impl KeyValueStore for LevelDB { for (start_key, end_key) in [ endpoints(DBColumn::BeaconStateTemporary), endpoints(DBColumn::BeaconState), + endpoints(DBColumn::BeaconStateSummary), ] { self.db.compact(&start_key, &end_key); } @@ -225,9 +226,9 @@ impl KeyValueStore for LevelDB { } /// Iterate through all keys and values in a particular column. - fn iter_column_keys(&self, column: DBColumn) -> ColumnKeyIter { + fn iter_column_keys(&self, column: DBColumn) -> ColumnKeyIter { let start_key = - BytesKey::from_vec(get_key_for_col(column.into(), Hash256::zero().as_bytes())); + BytesKey::from_vec(get_key_for_col(column.into(), &vec![0; column.key_size()])); let iter = self.db.keys_iter(self.read_options()); iter.seek(&start_key); @@ -235,13 +236,12 @@ impl KeyValueStore for LevelDB { Box::new( iter.take_while(move |key| key.matches_column(column)) .map(move |bytes_key| { - let key = - bytes_key - .remove_column(column) - .ok_or(HotColdDBError::IterationError { - unexpected_key: bytes_key, - })?; - Ok(key) + let key = bytes_key.remove_column_variable(column).ok_or_else(|| { + HotColdDBError::IterationError { + unexpected_key: bytes_key.clone(), + } + })?; + K::from_bytes(key) }), ) } diff --git a/beacon_node/store/src/lib.rs b/beacon_node/store/src/lib.rs index 0f54b4664a..eacd28d2db 100644 --- a/beacon_node/store/src/lib.rs +++ b/beacon_node/store/src/lib.rs @@ -45,7 +45,7 @@ use strum::{EnumString, IntoStaticStr}; pub use types::*; pub type ColumnIter<'a, K> = Box), Error>> + 'a>; -pub type ColumnKeyIter<'a> = Box> + 'a>; +pub type ColumnKeyIter<'a, K> = Box> + 'a>; pub type RawEntryIter<'a> = Box, Vec), Error>> + 'a>; pub type RawKeyIter<'a> = Box, Error>> + 'a>; @@ -88,6 +88,7 @@ pub trait KeyValueStore: Sync + Send + Sized + 'static { self.iter_column_from(column, &vec![0; column.key_size()]) } + /// Iterate through all keys and values in a column from a given starting point. fn iter_column_from(&self, column: DBColumn, from: &[u8]) -> ColumnIter; fn iter_raw_entries(&self, _column: DBColumn, _prefix: &[u8]) -> RawEntryIter { @@ -99,7 +100,7 @@ pub trait KeyValueStore: Sync + Send + Sized + 'static { } /// Iterate through all keys in a particular column. - fn iter_column_keys(&self, column: DBColumn) -> ColumnKeyIter; + fn iter_column_keys(&self, column: DBColumn) -> ColumnKeyIter; } pub trait Key: Sized + 'static { @@ -274,7 +275,7 @@ impl DBColumn { /// This function returns the number of bytes used by keys in a given column. pub fn key_size(self) -> usize { match self { - Self::OverflowLRUCache => 40, + Self::OverflowLRUCache => 33, // See `OverflowKey` encode impl. Self::BeaconMeta | Self::BeaconBlock | Self::BeaconState diff --git a/beacon_node/store/src/memory_store.rs b/beacon_node/store/src/memory_store.rs index 7a276a1fad..c2e494dce6 100644 --- a/beacon_node/store/src/memory_store.rs +++ b/beacon_node/store/src/memory_store.rs @@ -100,7 +100,7 @@ impl KeyValueStore for MemoryStore { })) } - fn iter_column_keys(&self, column: DBColumn) -> ColumnKeyIter { + fn iter_column_keys(&self, column: DBColumn) -> ColumnKeyIter { Box::new(self.iter_column(column).map(|res| res.map(|(k, _)| k))) } diff --git a/book/src/database-migrations.md b/book/src/database-migrations.md index 9b60ca2e18..ed7f7d72f5 100644 --- a/book/src/database-migrations.md +++ b/book/src/database-migrations.md @@ -158,3 +158,38 @@ lighthouse db version --network mainnet ``` [run-correctly]: #how-to-run-lighthouse-db-correctly + +## How to prune historic states + +Pruning historic states helps in managing the disk space used by the Lighthouse beacon node by removing old beacon +states from the freezer database. This can be especially useful when the database has accumulated a significant amount +of historic data. This command is intended for nodes synced before 4.4.1, as newly synced node no longer store +historic states by default. + +Here are the steps to prune historic states: + +1. Before running the prune command, make sure that the Lighthouse beacon node is not running. If you are using systemd, you might stop the Lighthouse beacon node with a command like: + + ```bash + sudo systemctl stop lighthousebeacon + ``` + +2. Use the `prune-states` command to prune the historic states. You can do a test run without the `--confirm` flag to check that the database can be pruned: + + ```bash + sudo -u "$LH_USER" lighthouse db prune-states --datadir "$LH_DATADIR" --network "$NET" + ``` + +3. If you are ready to prune the states irreversibly, add the `--confirm` flag to commit the changes: + + ```bash + sudo -u "$LH_USER" lighthouse db prune-states --confirm --datadir "$LH_DATADIR" --network "$NET" + ``` + + The `--confirm` flag ensures that you are aware the action is irreversible, and historic states will be permanently removed. + +4. After successfully pruning the historic states, you can restart the Lighthouse beacon node: + + ```bash + sudo systemctl start lighthousebeacon + ``` diff --git a/consensus/fork_choice/tests/tests.rs b/consensus/fork_choice/tests/tests.rs index 9d39eb3e38..c21b8ed567 100644 --- a/consensus/fork_choice/tests/tests.rs +++ b/consensus/fork_choice/tests/tests.rs @@ -92,8 +92,7 @@ impl ForkChoiceTest { T: Fn(&BeaconForkChoiceStore, MemoryStore>) -> U, { func( - &self - .harness + self.harness .chain .canonical_head .fork_choice_read_lock() @@ -386,8 +385,7 @@ impl ForkChoiceTest { &self.harness.chain.spec, self.harness.logger(), ) - .err() - .expect("on_block did not return an error"); + .expect_err("on_block did not return an error"); comparison_func(err); self } @@ -841,7 +839,7 @@ async fn valid_attestation() { .apply_attestation_to_chain( MutationDelay::NoDelay, |_, _| {}, - |result| assert_eq!(result.unwrap(), ()), + |result| assert!(result.is_ok()), ) .await; } @@ -1074,7 +1072,7 @@ async fn invalid_attestation_delayed_slot() { .apply_attestation_to_chain( MutationDelay::NoDelay, |_, _| {}, - |result| assert_eq!(result.unwrap(), ()), + |result| assert!(result.is_ok()), ) .await .inspect_queued_attestations(|queue| assert_eq!(queue.len(), 1)) @@ -1183,7 +1181,7 @@ async fn weak_subjectivity_check_fails_early_epoch() { let mut checkpoint = setup_harness.harness.finalized_checkpoint(); - checkpoint.epoch = checkpoint.epoch - 1; + checkpoint.epoch -= 1; let chain_config = ChainConfig { weak_subjectivity_checkpoint: Some(checkpoint), @@ -1210,7 +1208,7 @@ async fn weak_subjectivity_check_fails_late_epoch() { let mut checkpoint = setup_harness.harness.finalized_checkpoint(); - checkpoint.epoch = checkpoint.epoch + 1; + checkpoint.epoch += 1; let chain_config = ChainConfig { weak_subjectivity_checkpoint: Some(checkpoint), diff --git a/database_manager/src/lib.rs b/database_manager/src/lib.rs index 079419aba5..93654b8dd5 100644 --- a/database_manager/src/lib.rs +++ b/database_manager/src/lib.rs @@ -5,17 +5,18 @@ use beacon_chain::{ use beacon_node::{get_data_dir, get_slots_per_restore_point, ClientConfig}; use clap::{App, Arg, ArgMatches}; use environment::{Environment, RuntimeContext}; -use slog::{info, Logger}; +use slog::{info, warn, Logger}; use std::fs; use std::io::Write; use std::path::PathBuf; +use store::metadata::STATE_UPPER_LIMIT_NO_RETAIN; use store::{ errors::Error, metadata::{SchemaVersion, CURRENT_SCHEMA_VERSION}, DBColumn, HotColdDB, KeyValueStore, LevelDB, }; use strum::{EnumString, EnumVariantNames, VariantNames}; -use types::EthSpec; +use types::{BeaconState, EthSpec, Slot}; pub const CMD: &str = "database_manager"; @@ -88,17 +89,35 @@ pub fn inspect_cli_app<'a, 'b>() -> App<'a, 'b> { } pub fn prune_payloads_app<'a, 'b>() -> App<'a, 'b> { - App::new("prune_payloads") + App::new("prune-payloads") + .alias("prune_payloads") .setting(clap::AppSettings::ColoredHelp) .about("Prune finalized execution payloads") } pub fn prune_blobs_app<'a, 'b>() -> App<'a, 'b> { - App::new("prune_blobs") + App::new("prune-blobs") + .alias("prune_blobs") .setting(clap::AppSettings::ColoredHelp) .about("Prune blobs older than data availability boundary") } +pub fn prune_states_app<'a, 'b>() -> App<'a, 'b> { + App::new("prune-states") + .alias("prune_states") + .arg( + Arg::with_name("confirm") + .long("confirm") + .help( + "Commit to pruning states irreversably. Without this flag the command will \ + just check that the database is capable of being pruned.", + ) + .takes_value(false), + ) + .setting(clap::AppSettings::ColoredHelp) + .about("Prune all beacon states from the freezer database") +} + pub fn cli_app<'a, 'b>() -> App<'a, 'b> { App::new(CMD) .visible_aliases(&["db"]) @@ -145,6 +164,7 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { .subcommand(inspect_cli_app()) .subcommand(prune_payloads_app()) .subcommand(prune_blobs_app()) + .subcommand(prune_states_app()) } fn parse_client_config( @@ -213,7 +233,7 @@ pub fn display_db_version( Ok(()) } -#[derive(Debug, EnumString, EnumVariantNames)] +#[derive(Debug, PartialEq, Eq, EnumString, EnumVariantNames)] pub enum InspectTarget { #[strum(serialize = "sizes")] ValueSizes, @@ -221,6 +241,8 @@ pub enum InspectTarget { ValueTotal, #[strum(serialize = "values")] Values, + #[strum(serialize = "gaps")] + Gaps, } pub struct InspectConfig { @@ -286,6 +308,9 @@ pub fn inspect_db( let skip = inspect_config.skip.unwrap_or(0); let limit = inspect_config.limit.unwrap_or(usize::MAX); + let mut prev_key = 0; + let mut found_gaps = false; + let base_path = &inspect_config.output_dir; if let InspectTarget::Values = inspect_config.target { @@ -304,6 +329,23 @@ pub fn inspect_db( InspectTarget::ValueSizes => { println!("{}: {} bytes", hex::encode(&key), value.len()); } + InspectTarget::Gaps => { + // Convert last 8 bytes of key to u64. + let numeric_key = u64::from_be_bytes( + key[key.len() - 8..] + .try_into() + .expect("key is at least 8 bytes"), + ); + + if numeric_key > prev_key + 1 { + println!( + "gap between keys {} and {} (offset: {})", + prev_key, numeric_key, num_keys, + ); + found_gaps = true; + } + prev_key = numeric_key; + } InspectTarget::ValueTotal => (), InspectTarget::Values => { let file_path = base_path.join(format!( @@ -332,6 +374,10 @@ pub fn inspect_db( num_keys += 1; } + if inspect_config.target == InspectTarget::Gaps && !found_gaps { + println!("No gaps found!"); + } + println!("Num keys: {}", num_keys); println!("Total: {} bytes", total); @@ -442,6 +488,86 @@ pub fn prune_blobs( db.try_prune_most_blobs(true) } +pub struct PruneStatesConfig { + confirm: bool, +} + +fn parse_prune_states_config(cli_args: &ArgMatches) -> Result { + let confirm = cli_args.is_present("confirm"); + Ok(PruneStatesConfig { confirm }) +} + +pub fn prune_states( + client_config: ClientConfig, + prune_config: PruneStatesConfig, + mut genesis_state: BeaconState, + runtime_context: &RuntimeContext, + log: Logger, +) -> Result<(), String> { + let spec = &runtime_context.eth2_config.spec; + let hot_path = client_config.get_db_path(); + let cold_path = client_config.get_freezer_db_path(); + let blobs_path = client_config.get_blobs_db_path(); + + let db = HotColdDB::, LevelDB>::open( + &hot_path, + &cold_path, + blobs_path, + |_, _, _| Ok(()), + client_config.store, + spec.clone(), + log.clone(), + ) + .map_err(|e| format!("Unable to open database: {e:?}"))?; + + // Load the genesis state from the database to ensure we're deleting states for the + // correct network, and that we don't end up storing the wrong genesis state. + let genesis_from_db = db + .load_cold_state_by_slot(Slot::new(0)) + .map_err(|e| format!("Error reading genesis state: {e:?}"))? + .ok_or("Error: genesis state missing from database. Check schema version.")?; + + if genesis_from_db.genesis_validators_root() != genesis_state.genesis_validators_root() { + return Err(format!( + "Error: Wrong network. Genesis state in DB does not match {} genesis.", + spec.config_name.as_deref().unwrap_or("") + )); + } + + // Check that the user has confirmed they want to proceed. + if !prune_config.confirm { + match db.get_anchor_info() { + Some(anchor_info) if anchor_info.state_upper_limit == STATE_UPPER_LIMIT_NO_RETAIN => { + info!(log, "States have already been pruned"); + return Ok(()); + } + _ => { + info!(log, "Ready to prune states"); + } + } + warn!( + log, + "Pruning states is irreversible"; + ); + warn!( + log, + "Re-run this command with --confirm to commit to state deletion" + ); + info!(log, "Nothing has been pruned on this run"); + return Err("Error: confirmation flag required".into()); + } + + // Delete all historic state data and *re-store* the genesis state. + let genesis_state_root = genesis_state + .update_tree_hash_cache() + .map_err(|e| format!("Error computing genesis state root: {e:?}"))?; + db.prune_historic_states(genesis_state_root, &genesis_state) + .map_err(|e| format!("Failed to prune due to error: {e:?}"))?; + + info!(log, "Historic states pruned successfully"); + Ok(()) +} + /// Run the database manager, returning an error string if the operation did not succeed. pub fn run(cli_args: &ArgMatches<'_>, env: Environment) -> Result<(), String> { let client_config = parse_client_config(cli_args, &env)?; @@ -461,10 +587,34 @@ pub fn run(cli_args: &ArgMatches<'_>, env: Environment) -> Result let inspect_config = parse_inspect_config(cli_args)?; inspect_db(inspect_config, client_config, &context, log) } - ("prune_payloads", Some(_)) => { + ("prune-payloads", Some(_)) => { prune_payloads(client_config, &context, log).map_err(format_err) } - ("prune_blobs", Some(_)) => prune_blobs(client_config, &context, log).map_err(format_err), + ("prune-blobs", Some(_)) => prune_blobs(client_config, &context, log).map_err(format_err), + ("prune-states", Some(cli_args)) => { + let executor = env.core_context().executor; + let network_config = context + .eth2_network_config + .clone() + .ok_or("Missing network config")?; + + let genesis_state = executor + .block_on_dangerous( + network_config.genesis_state::( + client_config.genesis_state_url.as_deref(), + client_config.genesis_state_url_timeout, + &log, + ), + "get_genesis_state", + ) + .ok_or("Shutting down")? + .map_err(|e| format!("Error getting genesis state: {e}"))? + .ok_or("Genesis state missing")?; + + let prune_config = parse_prune_states_config(cli_args)?; + + prune_states(client_config, prune_config, genesis_state, &context, log) + } _ => Err("Unknown subcommand, for help `lighthouse database_manager --help`".into()), } } From f2aabe915b9c396af3d361498de7be7d429e8cef Mon Sep 17 00:00:00 2001 From: Justin Traglia Date: Fri, 3 Nov 2023 12:38:13 +0000 Subject: [PATCH 03/23] Fix comment for blob sidecar observation pruning (#4893) ## Issue Addressed The comment implies that observations for the given slot would be retained but they are not. ## Proposed Changes I'm pretty sure the functionality is correct and the comment is slightly incorrect, so just update the comment. The comment needs to say something along the lines of "less than or equal to" rather than just "less than." ## Additional Info It doesn't make sense to keep finalized observations since those are no longer accepted. --- beacon_node/beacon_chain/src/observed_blob_sidecars.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beacon_node/beacon_chain/src/observed_blob_sidecars.rs b/beacon_node/beacon_chain/src/observed_blob_sidecars.rs index 6c2f07ff59..f16f38bad5 100644 --- a/beacon_node/beacon_chain/src/observed_blob_sidecars.rs +++ b/beacon_node/beacon_chain/src/observed_blob_sidecars.rs @@ -87,7 +87,7 @@ impl ObservedBlobSidecars { Ok(()) } - /// Prune all values earlier than the given slot. + /// Prune `blob_sidecar` observations for slots less than or equal to the given slot. pub fn prune(&mut self, finalized_slot: Slot) { if finalized_slot == 0 { return; From bcca88a15012d32195ddade26cbea285457e41e2 Mon Sep 17 00:00:00 2001 From: realbigsean Date: Fri, 3 Nov 2023 13:31:27 +0000 Subject: [PATCH 04/23] more logging improvements (#4885) ## Issue Addressed -downgrades `Missing components over rpc` to debug because this isn't unusual and just results in a re-try -removes the result from `Block component processed for lookup` because this prints the full block on an unknown parent error Co-authored-by: realbigsean --- .../network/src/network_beacon_processor/sync_methods.rs | 2 +- beacon_node/network/src/sync/block_lookups/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/beacon_node/network/src/network_beacon_processor/sync_methods.rs b/beacon_node/network/src/network_beacon_processor/sync_methods.rs index 7d6bde6344..b034d0ff31 100644 --- a/beacon_node/network/src/network_beacon_processor/sync_methods.rs +++ b/beacon_node/network/src/network_beacon_processor/sync_methods.rs @@ -340,7 +340,7 @@ impl NetworkBeaconProcessor { ); } Ok(AvailabilityProcessingStatus::MissingComponents(_, _)) => { - warn!( + debug!( self.log, "Missing components over rpc"; "block_hash" => %block_root, diff --git a/beacon_node/network/src/sync/block_lookups/mod.rs b/beacon_node/network/src/sync/block_lookups/mod.rs index d13bb8cb88..c5732069a0 100644 --- a/beacon_node/network/src/sync/block_lookups/mod.rs +++ b/beacon_node/network/src/sync/block_lookups/mod.rs @@ -786,7 +786,7 @@ impl BlockLookups { self.log, "Block component processed for lookup"; "response_type" => ?R::response_type(), - "result" => ?result, + "block_root" => ?root, ); match result { From ac8811afacb1a73f66d3db41ea5a1e73babceb5d Mon Sep 17 00:00:00 2001 From: Joel Rousseau Date: Thu, 9 Nov 2023 04:05:14 +0000 Subject: [PATCH 05/23] Add missed blocks to monitored validators (#4731) * add missed_block metric * init missed_block in constructor * declare beaconproposercache in ValidatorMonitor * refacto proposer_shuffling_decision_root to use epoch instead of current.epoch * imple new proposer_shuffling_decision_root in callers * push missed_blocks * prune missed_blocks * only add to hashmap if it's a monitored validator * remove current_epoch dup + typos * extract in func * add prom metrics * checkpoint is not only epoch but slot as well * add safeguard if we start a new chain at slot 0 * clean * remove unnecessary negative value for a slot * typo in comment * remove unused current_epoch * share beacon_proposer_cache between validator_monitor and beacon_chain * pass Hash256::zero() * debug objects * fix loop: lag is at the head * sed s/get_slot/get_epoch * fewer calls to cache.get_epoch * fix typos * remove cache first call * export TYPICAL_SLOTS_PER_EPOCH and use it in validator_monitor * switch to gauge & loop over missed_blocks hashset * fix subnet_service tests * remove unused var * clean + fix nits * add beacon_proposer_cache + validator_monitor in builder * fix store_tests * fix builder tests * add tests * add validator monitor set of tests * clean tests * nits * optimise imports * lint * typo * added self.aggregatable * duplicate proposer_shuffling_decision_root * remove duplication in passing beacon_proposer_cache * remove duplication in passing beacon_proposer_cache * using indices * fmt * implement missed blocks total * nits * avoid heap allocation * remove recursion limit * fix lint * Fix valdiator monitor builder pattern Unify validator monitor config struct * renaming metrics * renaming metrics in validator monitor * add log if there's a missing validator index * consistent log * fix loop * better loop * move gauge to counter * fmt * add error message * lint * fix prom metrics * set gauge to 0 when non-finalized epochs * better wording * remove hash256::zero in favour of block_root * fix gauge total label * fix last missed block validator * Add `MissedBlock` struct * Fix comment * Refactor non-finalized block loop * Fix off-by-one * Avoid string allocation * Fix compile error * Remove non-finalized blocks metric * fix func clojure * remove unused variable * remove unused DEFAULT_INDIVIDUAL_TRACKING_THRESHOLD * remove unused DEFAULT_INDIVIDUAL_TRACKING_THRESHOLD in builder * add validator index depending on the fork name * typos --------- Co-authored-by: Paul Hauner --- beacon_node/beacon_chain/src/beacon_chain.rs | 2 +- .../beacon_chain/src/beacon_proposer_cache.rs | 2 +- beacon_node/beacon_chain/src/builder.rs | 50 ++- beacon_node/beacon_chain/src/metrics.rs | 5 + beacon_node/beacon_chain/src/test_utils.rs | 18 +- .../beacon_chain/src/validator_monitor.rs | 188 ++++++++++- beacon_node/beacon_chain/tests/main.rs | 1 + beacon_node/beacon_chain/tests/store_tests.rs | 3 +- .../beacon_chain/tests/validator_monitor.rs | 299 ++++++++++++++++++ beacon_node/client/src/builder.rs | 10 +- beacon_node/client/src/config.rs | 18 +- beacon_node/http_api/src/proposer_duties.rs | 2 +- .../network/src/subnet_service/tests/mod.rs | 2 - beacon_node/src/config.rs | 14 +- consensus/types/src/beacon_state.rs | 29 +- lighthouse/tests/beacon_node.rs | 22 +- 16 files changed, 575 insertions(+), 90 deletions(-) create mode 100644 beacon_node/beacon_chain/tests/validator_monitor.rs diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index f2378b4f9e..2fd70056cc 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -453,7 +453,7 @@ pub struct BeaconChain { /// A cache of eth1 deposit data at epoch boundaries for deposit finalization pub eth1_finalization_cache: TimeoutRwLock, /// Caches the beacon block proposer shuffling for a given epoch and shuffling key root. - pub beacon_proposer_cache: Mutex, + pub beacon_proposer_cache: Arc>, /// Caches a map of `validator_index -> validator_pubkey`. pub(crate) validator_pubkey_cache: TimeoutRwLock>, /// A cache used when producing attestations. diff --git a/beacon_node/beacon_chain/src/beacon_proposer_cache.rs b/beacon_node/beacon_chain/src/beacon_proposer_cache.rs index eae71bd63e..b51592cadd 100644 --- a/beacon_node/beacon_chain/src/beacon_proposer_cache.rs +++ b/beacon_node/beacon_chain/src/beacon_proposer_cache.rs @@ -25,7 +25,7 @@ const CACHE_SIZE: usize = 16; /// This value is fairly unimportant, it's used to avoid heap allocations. The result of it being /// incorrect is non-substantial from a consensus perspective (and probably also from a /// performance perspective). -const TYPICAL_SLOTS_PER_EPOCH: usize = 32; +pub const TYPICAL_SLOTS_PER_EPOCH: usize = 32; /// For some given slot, this contains the proposer index (`index`) and the `fork` that should be /// used to verify their signature. diff --git a/beacon_node/beacon_chain/src/builder.rs b/beacon_node/beacon_chain/src/builder.rs index fd8a3f0460..bffb23aeb7 100644 --- a/beacon_node/beacon_chain/src/builder.rs +++ b/beacon_node/beacon_chain/src/builder.rs @@ -1,4 +1,5 @@ use crate::beacon_chain::{CanonicalHead, BEACON_CHAIN_DB_KEY, ETH1_CACHE_DB_KEY, OP_POOL_DB_KEY}; +use crate::beacon_proposer_cache::BeaconProposerCache; use crate::data_availability_checker::DataAvailabilityChecker; use crate::eth1_chain::{CachingEth1Backend, SszEth1}; use crate::eth1_finalization_cache::Eth1FinalizationCache; @@ -10,7 +11,7 @@ use crate::persisted_beacon_chain::PersistedBeaconChain; use crate::shuffling_cache::{BlockShufflingIds, ShufflingCache}; use crate::snapshot_cache::{SnapshotCache, DEFAULT_SNAPSHOT_CACHE_SIZE}; use crate::timeout_rw_lock::TimeoutRwLock; -use crate::validator_monitor::ValidatorMonitor; +use crate::validator_monitor::{ValidatorMonitor, ValidatorMonitorConfig}; use crate::validator_pubkey_cache::ValidatorPubkeyCache; use crate::ChainConfig; use crate::{ @@ -23,10 +24,10 @@ use fork_choice::{ForkChoice, ResetPayloadStatuses}; use futures::channel::mpsc::Sender; use kzg::{Kzg, TrustedSetup}; use operation_pool::{OperationPool, PersistedOperationPool}; -use parking_lot::RwLock; +use parking_lot::{Mutex, RwLock}; use proto_array::{DisallowedReOrgOffsets, ReOrgThreshold}; use slasher::Slasher; -use slog::{crit, debug, error, info, Logger}; +use slog::{crit, debug, error, info, o, Logger}; use slot_clock::{SlotClock, TestingSlotClock}; use state_processing::per_slot_processing; use std::marker::PhantomData; @@ -35,8 +36,8 @@ use std::time::Duration; use store::{Error as StoreError, HotColdDB, ItemStore, KeyValueStoreOp}; use task_executor::{ShutdownReason, TaskExecutor}; use types::{ - BeaconBlock, BeaconState, ChainSpec, Checkpoint, Epoch, EthSpec, Graffiti, Hash256, - PublicKeyBytes, Signature, SignedBeaconBlock, Slot, + BeaconBlock, BeaconState, ChainSpec, Checkpoint, Epoch, EthSpec, Graffiti, Hash256, Signature, + SignedBeaconBlock, Slot, }; /// An empty struct used to "witness" all the `BeaconChainTypes` traits. It has no user-facing @@ -93,12 +94,12 @@ pub struct BeaconChainBuilder { log: Option, graffiti: Graffiti, slasher: Option>>, - validator_monitor: Option>, // Pending I/O batch that is constructed during building and should be executed atomically // alongside `PersistedBeaconChain` storage when `BeaconChainBuilder::build` is called. pending_io_batch: Vec, trusted_setup: Option, task_executor: Option, + validator_monitor_config: Option, } impl @@ -135,10 +136,10 @@ where log: None, graffiti: Graffiti::default(), slasher: None, - validator_monitor: None, pending_io_batch: vec![], trusted_setup: None, task_executor: None, + validator_monitor_config: None, } } @@ -623,19 +624,8 @@ where /// Register some validators for additional monitoring. /// /// `validators` is a comma-separated string of 0x-formatted BLS pubkeys. - pub fn monitor_validators( - mut self, - auto_register: bool, - validators: Vec, - individual_metrics_threshold: usize, - log: Logger, - ) -> Self { - self.validator_monitor = Some(ValidatorMonitor::new( - validators, - auto_register, - individual_metrics_threshold, - log.clone(), - )); + pub fn validator_monitor_config(mut self, config: ValidatorMonitorConfig) -> Self { + self.validator_monitor_config = Some(config); self } @@ -671,11 +661,16 @@ where let genesis_state_root = self .genesis_state_root .ok_or("Cannot build without a genesis state root")?; - let mut validator_monitor = self - .validator_monitor - .ok_or("Cannot build without a validator monitor")?; + let validator_monitor_config = self.validator_monitor_config.unwrap_or_default(); let head_tracker = Arc::new(self.head_tracker.unwrap_or_default()); + let beacon_proposer_cache: Arc> = <_>::default(); + let mut validator_monitor = ValidatorMonitor::new( + validator_monitor_config, + beacon_proposer_cache.clone(), + log.new(o!("service" => "val_mon")), + ); + let current_slot = if slot_clock .is_prior_to_genesis() .ok_or("Unable to read slot clock")? @@ -911,7 +906,7 @@ where log.clone(), )), eth1_finalization_cache: TimeoutRwLock::new(Eth1FinalizationCache::new(log.clone())), - beacon_proposer_cache: <_>::default(), + beacon_proposer_cache, block_times_cache: <_>::default(), pre_finalization_block_cache: <_>::default(), validator_pubkey_cache: TimeoutRwLock::new(validator_pubkey_cache), @@ -1097,7 +1092,6 @@ fn descriptive_db_error(item: &str, error: &StoreError) -> String { mod test { use super::*; use crate::test_utils::EphemeralHarnessType; - use crate::validator_monitor::DEFAULT_INDIVIDUAL_TRACKING_THRESHOLD; use ethereum_hashing::hash; use genesis::{ generate_deterministic_keypairs, interop_genesis_state, DEFAULT_ETH1_BLOCK_HASH, @@ -1155,12 +1149,6 @@ mod test { .testing_slot_clock(Duration::from_secs(1)) .expect("should configure testing slot clock") .shutdown_sender(shutdown_tx) - .monitor_validators( - true, - vec![], - DEFAULT_INDIVIDUAL_TRACKING_THRESHOLD, - log.clone(), - ) .build() .expect("should build"); diff --git a/beacon_node/beacon_chain/src/metrics.rs b/beacon_node/beacon_chain/src/metrics.rs index a23bcdc0b5..0fe68ba19e 100644 --- a/beacon_node/beacon_chain/src/metrics.rs +++ b/beacon_node/beacon_chain/src/metrics.rs @@ -1033,6 +1033,11 @@ lazy_static! { "beacon_aggregated_attestation_subsets_total", "Count of new aggregated attestations that are subsets of already known aggregates" ); + pub static ref VALIDATOR_MONITOR_MISSED_BLOCKS_TOTAL: Result = try_create_int_counter_vec( + "validator_monitor_missed_blocks_total", + "Number of non-finalized blocks missed", + &["validator"] + ); /* * Kzg related metrics diff --git a/beacon_node/beacon_chain/src/test_utils.rs b/beacon_node/beacon_chain/src/test_utils.rs index 333318f52f..23af0c8126 100644 --- a/beacon_node/beacon_chain/src/test_utils.rs +++ b/beacon_node/beacon_chain/src/test_utils.rs @@ -6,7 +6,7 @@ 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, + validator_monitor::{ValidatorMonitor, ValidatorMonitorConfig}, BeaconChainError, NotifyExecutionLayer, ProduceBlockVerification, }; use crate::{ @@ -181,6 +181,7 @@ pub struct Builder { execution_layer: Option>, mock_execution_layer: Option>, testing_slot_clock: Option, + validator_monitor_config: Option, runtime: TestRuntime, log: Logger, } @@ -316,6 +317,7 @@ where execution_layer: None, mock_execution_layer: None, testing_slot_clock: None, + validator_monitor_config: None, runtime, log, } @@ -388,6 +390,14 @@ where self } + pub fn validator_monitor_config( + mut self, + validator_monitor_config: ValidatorMonitorConfig, + ) -> Self { + self.validator_monitor_config = Some(validator_monitor_config); + self + } + /// Purposefully replace the `store_mutator`. pub fn override_store_mutator(mut self, mutator: BoxedMutator) -> Self { assert!(self.store_mutator.is_some(), "store mutator not set"); @@ -494,11 +504,13 @@ where let validator_keypairs = self .validator_keypairs .expect("cannot build without validator keypairs"); - let chain_config = self.chain_config.unwrap_or_default(); let trusted_setup: TrustedSetup = serde_json::from_reader(TRUSTED_SETUP_BYTES) .map_err(|e| format!("Unable to read trusted setup file: {}", e)) .unwrap(); + let validator_monitor_config = self.validator_monitor_config.unwrap_or_default(); + + let chain_config = self.chain_config.unwrap_or_default(); let mut builder = BeaconChainBuilder::new(self.eth_spec_instance) .logger(log.clone()) .custom_spec(spec) @@ -518,7 +530,7 @@ where log.clone(), 5, ))) - .monitor_validators(true, vec![], DEFAULT_INDIVIDUAL_TRACKING_THRESHOLD, log) + .validator_monitor_config(validator_monitor_config) .trusted_setup(trusted_setup); builder = if let Some(mutator) = self.initial_mutator { diff --git a/beacon_node/beacon_chain/src/validator_monitor.rs b/beacon_node/beacon_chain/src/validator_monitor.rs index 396aac71b0..015cdb1c26 100644 --- a/beacon_node/beacon_chain/src/validator_monitor.rs +++ b/beacon_node/beacon_chain/src/validator_monitor.rs @@ -2,10 +2,14 @@ //! //! This component should not affect consensus. +use crate::beacon_proposer_cache::{BeaconProposerCache, TYPICAL_SLOTS_PER_EPOCH}; use crate::metrics; -use parking_lot::RwLock; -use slog::{crit, debug, info, Logger}; +use itertools::Itertools; +use parking_lot::{Mutex, RwLock}; +use serde::{Deserialize, Serialize}; +use slog::{crit, debug, error, info, warn, Logger}; use slot_clock::SlotClock; +use smallvec::SmallVec; use state_processing::per_epoch_processing::{ errors::EpochProcessingError, EpochProcessingSummary, }; @@ -14,6 +18,7 @@ use std::convert::TryFrom; use std::io; use std::marker::PhantomData; use std::str::Utf8Error; +use std::sync::Arc; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use store::AbstractExecPayload; use types::{ @@ -35,7 +40,34 @@ pub const HISTORIC_EPOCHS: usize = 10; /// Once the validator monitor reaches this number of validators it will stop /// tracking their metrics/logging individually in an effort to reduce /// Prometheus cardinality and log volume. -pub const DEFAULT_INDIVIDUAL_TRACKING_THRESHOLD: usize = 64; +const DEFAULT_INDIVIDUAL_TRACKING_THRESHOLD: usize = 64; + +/// Lag slots used in detecting missed blocks for the monitored validators +pub const MISSED_BLOCK_LAG_SLOTS: usize = 4; + +/// The number of epochs to look back when determining if a validator has missed a block. This value is used with +/// the beacon_proposer_cache to determine if a validator has missed a block. +/// And so, setting this value to anything higher than 1 is likely going to be problematic because the beacon_proposer_cache +/// is only populated for the current and the previous epoch. +pub const MISSED_BLOCK_LOOKBACK_EPOCHS: u64 = 1; + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +// Initial configuration values for the `ValidatorMonitor`. +pub struct ValidatorMonitorConfig { + pub auto_register: bool, + pub validators: Vec, + pub individual_tracking_threshold: usize, +} + +impl Default for ValidatorMonitorConfig { + fn default() -> Self { + Self { + auto_register: false, + validators: vec![], + individual_tracking_threshold: DEFAULT_INDIVIDUAL_TRACKING_THRESHOLD, + } + } +} #[derive(Debug)] pub enum Error { @@ -323,6 +355,13 @@ impl MonitoredValidator { } } +#[derive(PartialEq, Hash, Eq)] +struct MissedBlock { + slot: Slot, + parent_root: Hash256, + validator_index: u64, +} + /// Holds a collection of `MonitoredValidator` and is notified about a variety of events on the P2P /// network, HTTP API and `BeaconChain`. /// @@ -343,26 +382,37 @@ pub struct ValidatorMonitor { /// large validator counts causing infeasibly high cardinailty for /// Prometheus and high log volumes. individual_tracking_threshold: usize, + /// A Map representing the (non-finalized) missed blocks by epoch, validator_index(state.validators) and slot + missed_blocks: HashSet, + // A beacon proposer cache + beacon_proposer_cache: Arc>, log: Logger, _phantom: PhantomData, } impl ValidatorMonitor { pub fn new( - pubkeys: Vec, - auto_register: bool, - individual_tracking_threshold: usize, + config: ValidatorMonitorConfig, + beacon_proposer_cache: Arc>, log: Logger, ) -> Self { + let ValidatorMonitorConfig { + auto_register, + validators, + individual_tracking_threshold, + } = config; + let mut s = Self { validators: <_>::default(), indices: <_>::default(), auto_register, individual_tracking_threshold, + missed_blocks: <_>::default(), + beacon_proposer_cache, log, _phantom: PhantomData, }; - for pubkey in pubkeys { + for pubkey in validators { s.add_validator_pubkey(pubkey) } s @@ -411,6 +461,9 @@ impl ValidatorMonitor { self.indices.insert(i, validator.pubkey); }); + // Add missed non-finalized blocks for the monitored validators + self.add_validators_missed_blocks(state); + // Update metrics for individual validators. for monitored_validator in self.validators.values() { if let Some(i) = monitored_validator.index { @@ -489,6 +542,116 @@ impl ValidatorMonitor { } } } + + // Prune missed blocks that are prior to last finalized epochs - MISSED_BLOCK_LOOKBACK_EPOCHS + let finalized_epoch = state.finalized_checkpoint().epoch; + self.missed_blocks.retain(|missed_block| { + let epoch = missed_block.slot.epoch(T::slots_per_epoch()); + epoch + Epoch::new(MISSED_BLOCK_LOOKBACK_EPOCHS) >= finalized_epoch + }); + } + + /// Add missed non-finalized blocks for the monitored validators + fn add_validators_missed_blocks(&mut self, state: &BeaconState) { + // Define range variables + let current_slot = state.slot(); + let current_epoch = current_slot.epoch(T::slots_per_epoch()); + // start_slot needs to be coherent with what can be retrieved from the beacon_proposer_cache + let start_slot = current_epoch.start_slot(T::slots_per_epoch()) + - Slot::new(MISSED_BLOCK_LOOKBACK_EPOCHS * T::slots_per_epoch()); + + let end_slot = current_slot.saturating_sub(MISSED_BLOCK_LAG_SLOTS).as_u64(); + + // List of proposers per epoch from the beacon_proposer_cache + let mut proposers_per_epoch: Option> = None; + + for (prev_slot, slot) in (start_slot.as_u64()..=end_slot) + .map(Slot::new) + .tuple_windows() + { + // Condition for missed_block is defined such as block_root(slot) == block_root(slot - 1) + // where the proposer who missed the block is the proposer of the block at block_root(slot) + if let (Ok(block_root), Ok(prev_block_root)) = + (state.get_block_root(slot), state.get_block_root(prev_slot)) + { + // Found missed block + if block_root == prev_block_root { + let slot_epoch = slot.epoch(T::slots_per_epoch()); + let prev_slot_epoch = prev_slot.epoch(T::slots_per_epoch()); + + if let Ok(shuffling_decision_block) = + state.proposer_shuffling_decision_root_at_epoch(slot_epoch, *block_root) + { + // Only update the cache if it needs to be initialised or because + // slot is at epoch + 1 + if proposers_per_epoch.is_none() || slot_epoch != prev_slot_epoch { + proposers_per_epoch = self.get_proposers_by_epoch_from_cache( + slot_epoch, + shuffling_decision_block, + ); + } + + // Only add missed blocks for the proposer if it's in the list of monitored validators + let slot_in_epoch = slot % T::slots_per_epoch(); + if let Some(proposer_index) = proposers_per_epoch + .as_deref() + .and_then(|proposers| proposers.get(slot_in_epoch.as_usize())) + { + let i = *proposer_index as u64; + if let Some(pub_key) = self.indices.get(&i) { + if let Some(validator) = self.validators.get(pub_key) { + let missed_block = MissedBlock { + slot, + parent_root: *prev_block_root, + validator_index: i, + }; + // Incr missed block counter for the validator only if it doesn't already exist in the hashset + if self.missed_blocks.insert(missed_block) { + self.aggregatable_metric(&validator.id, |label| { + metrics::inc_counter_vec( + &metrics::VALIDATOR_MONITOR_MISSED_BLOCKS_TOTAL, + &[label], + ); + }); + error!( + self.log, + "Validator missed a block"; + "index" => i, + "slot" => slot, + "parent block root" => ?prev_block_root, + ); + } + } else { + warn!( + self.log, + "Missing validator index"; + "info" => "potentially inconsistency in the validator manager", + "index" => i, + ) + } + } + } else { + debug!( + self.log, + "Could not get proposers for from cache"; + "epoch" => ?slot_epoch + ); + } + } + } + } + } + } + + fn get_proposers_by_epoch_from_cache( + &mut self, + epoch: Epoch, + shuffling_decision_block: Hash256, + ) -> Option> { + let mut cache = self.beacon_proposer_cache.lock(); + cache + .get_epoch::(shuffling_decision_block, epoch) + .cloned() } /// Run `func` with the `TOTAL_LABEL` and optionally the @@ -822,6 +985,17 @@ impl ValidatorMonitor { } } + pub fn get_monitored_validator_missed_block_count(&self, validator_index: u64) -> u64 { + self.missed_blocks + .iter() + .filter(|missed_block| missed_block.validator_index == validator_index) + .count() as u64 + } + + pub fn get_beacon_proposer_cache(&self) -> Arc> { + self.beacon_proposer_cache.clone() + } + /// If `self.auto_register == true`, add the `validator_index` to `self.monitored_validators`. /// Otherwise, do nothing. pub fn auto_register_local_validator(&mut self, validator_index: u64) { diff --git a/beacon_node/beacon_chain/tests/main.rs b/beacon_node/beacon_chain/tests/main.rs index 332f6a4829..e0564e1510 100644 --- a/beacon_node/beacon_chain/tests/main.rs +++ b/beacon_node/beacon_chain/tests/main.rs @@ -10,3 +10,4 @@ mod rewards; mod store_tests; mod sync_committee_verification; mod tests; +mod validator_monitor; diff --git a/beacon_node/beacon_chain/tests/store_tests.rs b/beacon_node/beacon_chain/tests/store_tests.rs index 59a4c1decc..5107396ac2 100644 --- a/beacon_node/beacon_chain/tests/store_tests.rs +++ b/beacon_node/beacon_chain/tests/store_tests.rs @@ -8,7 +8,6 @@ use beacon_chain::test_utils::{ mock_execution_layer_from_parts, test_spec, AttestationStrategy, BeaconChainHarness, BlockStrategy, DiskHarnessType, }; -use beacon_chain::validator_monitor::DEFAULT_INDIVIDUAL_TRACKING_THRESHOLD; use beacon_chain::{ data_availability_checker::MaybeAvailableBlock, historical_blocks::HistoricalBlockError, migrate::MigratorConfig, BeaconChain, BeaconChainError, BeaconChainTypes, BeaconSnapshot, @@ -2358,6 +2357,7 @@ async fn weak_subjectivity_sync_test(slots: Vec, checkpoint_slot: Slot) { Duration::from_secs(seconds_per_slot), ); slot_clock.set_slot(harness.get_current_slot().as_u64()); + let beacon_chain = BeaconChainBuilder::>::new(MinimalEthSpec) .store(store.clone()) .custom_spec(test_spec::()) @@ -2376,7 +2376,6 @@ async fn weak_subjectivity_sync_test(slots: Vec, checkpoint_slot: Slot) { 1, ))) .execution_layer(Some(mock.el)) - .monitor_validators(true, vec![], DEFAULT_INDIVIDUAL_TRACKING_THRESHOLD, log) .trusted_setup(trusted_setup) .build() .expect("should build"); diff --git a/beacon_node/beacon_chain/tests/validator_monitor.rs b/beacon_node/beacon_chain/tests/validator_monitor.rs new file mode 100644 index 0000000000..5bc6b758c2 --- /dev/null +++ b/beacon_node/beacon_chain/tests/validator_monitor.rs @@ -0,0 +1,299 @@ +use lazy_static::lazy_static; + +use beacon_chain::test_utils::{ + AttestationStrategy, BeaconChainHarness, BlockStrategy, EphemeralHarnessType, +}; +use beacon_chain::validator_monitor::{ValidatorMonitorConfig, MISSED_BLOCK_LAG_SLOTS}; +use types::{Epoch, EthSpec, Keypair, MainnetEthSpec, PublicKeyBytes, Slot}; + +// Should ideally be divisible by 3. +pub const VALIDATOR_COUNT: usize = 48; + +lazy_static! { + /// A cached set of keys. + static ref KEYPAIRS: Vec = types::test_utils::generate_deterministic_keypairs(VALIDATOR_COUNT); +} + +type E = MainnetEthSpec; + +fn get_harness( + validator_count: usize, + validator_indexes_to_monitor: Vec, +) -> BeaconChainHarness> { + let harness = BeaconChainHarness::builder(MainnetEthSpec) + .default_spec() + .keypairs(KEYPAIRS[0..validator_count].to_vec()) + .fresh_ephemeral_store() + .mock_execution_layer() + .validator_monitor_config(ValidatorMonitorConfig { + validators: validator_indexes_to_monitor + .iter() + .map(|i| PublicKeyBytes::from(KEYPAIRS[*i].pk.clone())) + .collect(), + ..<_>::default() + }) + .build(); + + harness.advance_slot(); + + harness +} + +#[tokio::test] +async fn produces_missed_blocks() { + let validator_count = 16; + + let slots_per_epoch = E::slots_per_epoch(); + + let nb_epoch_to_simulate = Epoch::new(2); + + // Generate 63 slots (2 epochs * 32 slots per epoch - 1) + let initial_blocks = slots_per_epoch * nb_epoch_to_simulate.as_u64() - 1; + + // The validator index of the validator that is 'supposed' to miss a block + let mut validator_index_to_monitor = 1; + + // 1st scenario // + // + // Missed block happens when slot and prev_slot are in the same epoch + let harness1 = get_harness(validator_count, vec![validator_index_to_monitor]); + harness1 + .extend_chain( + initial_blocks as usize, + BlockStrategy::OnCanonicalHead, + AttestationStrategy::AllValidators, + ) + .await; + + let mut _state = &mut harness1.get_current_state(); + let mut epoch = _state.current_epoch(); + + // We have a total of 63 slots and we want slot 57 to be a missed block + // and this is slot=25 in epoch=1 + let mut idx = initial_blocks - 6; + let mut slot = Slot::new(idx); + let mut slot_in_epoch = slot % slots_per_epoch; + let mut prev_slot = Slot::new(idx - 1); + let mut duplicate_block_root = *_state.block_roots().get(idx as usize).unwrap(); + let mut validator_indexes = _state.get_beacon_proposer_indices(&harness1.spec).unwrap(); + let mut validator_index = validator_indexes[slot_in_epoch.as_usize()]; + let mut proposer_shuffling_decision_root = _state + .proposer_shuffling_decision_root(duplicate_block_root) + .unwrap(); + + let beacon_proposer_cache = harness1 + .chain + .validator_monitor + .read() + .get_beacon_proposer_cache(); + + // Let's fill the cache with the proposers for the current epoch + // and push the duplicate_block_root to the block_roots vector + assert_eq!( + beacon_proposer_cache.lock().insert( + epoch, + proposer_shuffling_decision_root, + validator_indexes.into_iter().collect::>(), + _state.fork() + ), + Ok(()) + ); + + // Modify the block root of the previous slot to be the same as the block root of the current slot + // in order to simulate a missed block + assert_eq!( + _state.set_block_root(prev_slot, duplicate_block_root), + Ok(()) + ); + + { + // Let's validate the state which will call the function responsible for + // adding the missed blocks to the validator monitor + let mut validator_monitor = harness1.chain.validator_monitor.write(); + validator_monitor.process_valid_state(nb_epoch_to_simulate, _state); + + // We should have one entry in the missed blocks map + assert_eq!( + validator_monitor.get_monitored_validator_missed_block_count(validator_index as u64), + 1 + ); + } + + // 2nd scenario // + // + // Missed block happens when slot and prev_slot are not in the same epoch + // making sure that the cache reloads when the epoch changes + // in that scenario the slot that missed a block is the first slot of the epoch + validator_index_to_monitor = 7; + // We are adding other validators to monitor as thoses one will miss a block depending on + // the fork name specified when running the test as the proposer cache differs depending on the fork name (cf. seed) + let validator_index_to_monitor_altair = 2; + // Same as above but for the merge upgrade + let validator_index_to_monitor_merge = 4; + // Same as above but for the capella upgrade + let validator_index_to_monitor_capella = 11; + // Same as above but for the deneb upgrade + let validator_index_to_monitor_deneb = 3; + let harness2 = get_harness( + validator_count, + vec![ + validator_index_to_monitor, + validator_index_to_monitor_altair, + validator_index_to_monitor_merge, + validator_index_to_monitor_capella, + validator_index_to_monitor_deneb, + ], + ); + let advance_slot_by = 9; + harness2 + .extend_chain( + (initial_blocks + advance_slot_by) as usize, + BlockStrategy::OnCanonicalHead, + AttestationStrategy::AllValidators, + ) + .await; + + let mut _state2 = &mut harness2.get_current_state(); + epoch = _state2.current_epoch(); + + // We have a total of 72 slots and we want slot 64 to be the missed block + // and this is slot=64 in epoch=2 + idx = initial_blocks + (advance_slot_by) - 8; + slot = Slot::new(idx); + prev_slot = Slot::new(idx - 1); + slot_in_epoch = slot % slots_per_epoch; + duplicate_block_root = *_state2.block_roots().get(idx as usize).unwrap(); + validator_indexes = _state2.get_beacon_proposer_indices(&harness2.spec).unwrap(); + validator_index = validator_indexes[slot_in_epoch.as_usize()]; + + let beacon_proposer_cache = harness2 + .chain + .validator_monitor + .read() + .get_beacon_proposer_cache(); + + // Let's fill the cache with the proposers for the current epoch + // and push the duplicate_block_root to the block_roots vector + assert_eq!( + beacon_proposer_cache.lock().insert( + epoch, + duplicate_block_root, + validator_indexes.into_iter().collect::>(), + _state2.fork() + ), + Ok(()) + ); + + assert_eq!( + _state2.set_block_root(prev_slot, duplicate_block_root), + Ok(()) + ); + + { + // Let's validate the state which will call the function responsible for + // adding the missed blocks to the validator monitor + let mut validator_monitor2 = harness2.chain.validator_monitor.write(); + validator_monitor2.process_valid_state(epoch, _state2); + // We should have one entry in the missed blocks map + assert_eq!( + validator_monitor2.get_monitored_validator_missed_block_count(validator_index as u64), + 1 + ); + + // 3rd scenario // + // + // A missed block happens but the validator is not monitored + // it should not be flagged as a missed block + idx = initial_blocks + (advance_slot_by) - 7; + slot = Slot::new(idx); + prev_slot = Slot::new(idx - 1); + slot_in_epoch = slot % slots_per_epoch; + duplicate_block_root = *_state2.block_roots().get(idx as usize).unwrap(); + validator_indexes = _state2.get_beacon_proposer_indices(&harness2.spec).unwrap(); + let not_monitored_validator_index = validator_indexes[slot_in_epoch.as_usize()]; + + assert_eq!( + _state2.set_block_root(prev_slot, duplicate_block_root), + Ok(()) + ); + + // Let's validate the state which will call the function responsible for + // adding the missed blocks to the validator monitor + validator_monitor2.process_valid_state(epoch, _state2); + + // We shouldn't have any entry in the missed blocks map + assert_ne!(validator_index, not_monitored_validator_index); + assert_eq!( + validator_monitor2 + .get_monitored_validator_missed_block_count(not_monitored_validator_index as u64), + 0 + ); + } + + // 4th scenario // + // + // A missed block happens at state.slot - LOG_SLOTS_PER_EPOCH + // it shouldn't be flagged as a missed block + let harness3 = get_harness(validator_count, vec![validator_index_to_monitor]); + harness3 + .extend_chain( + slots_per_epoch as usize, + BlockStrategy::OnCanonicalHead, + AttestationStrategy::AllValidators, + ) + .await; + + let mut _state3 = &mut harness3.get_current_state(); + epoch = _state3.current_epoch(); + + // We have a total of 32 slots and we want slot 30 to be a missed block + // and this is slot=30 in epoch=0 + idx = slots_per_epoch - MISSED_BLOCK_LAG_SLOTS as u64 + 2; + slot = Slot::new(idx); + slot_in_epoch = slot % slots_per_epoch; + prev_slot = Slot::new(idx - 1); + duplicate_block_root = *_state3.block_roots().get(idx as usize).unwrap(); + validator_indexes = _state3.get_beacon_proposer_indices(&harness3.spec).unwrap(); + validator_index = validator_indexes[slot_in_epoch.as_usize()]; + proposer_shuffling_decision_root = _state3 + .proposer_shuffling_decision_root_at_epoch(epoch, duplicate_block_root) + .unwrap(); + + let beacon_proposer_cache = harness3 + .chain + .validator_monitor + .read() + .get_beacon_proposer_cache(); + + // Let's fill the cache with the proposers for the current epoch + // and push the duplicate_block_root to the block_roots vector + assert_eq!( + beacon_proposer_cache.lock().insert( + epoch, + proposer_shuffling_decision_root, + validator_indexes.into_iter().collect::>(), + _state3.fork() + ), + Ok(()) + ); + + // Modify the block root of the previous slot to be the same as the block root of the current slot + // in order to simulate a missed block + assert_eq!( + _state3.set_block_root(prev_slot, duplicate_block_root), + Ok(()) + ); + + { + // Let's validate the state which will call the function responsible for + // adding the missed blocks to the validator monitor + let mut validator_monitor3 = harness3.chain.validator_monitor.write(); + validator_monitor3.process_valid_state(epoch, _state3); + + // We shouldn't have one entry in the missed blocks map + assert_eq!( + validator_monitor3.get_monitored_validator_missed_block_count(validator_index as u64), + 0 + ); + } +} diff --git a/beacon_node/client/src/builder.rs b/beacon_node/client/src/builder.rs index d1184cf75d..fe8a56084d 100644 --- a/beacon_node/client/src/builder.rs +++ b/beacon_node/client/src/builder.rs @@ -191,15 +191,7 @@ where .graffiti(graffiti) .event_handler(event_handler) .execution_layer(execution_layer) - .monitor_validators( - config.validator_monitor_auto, - config.validator_monitor_pubkeys.clone(), - config.validator_monitor_individual_tracking_threshold, - runtime_context - .service_context("val_mon".to_string()) - .log() - .clone(), - ); + .validator_monitor_config(config.validator_monitor.clone()); let builder = if let Some(slasher) = self.slasher.clone() { builder.slasher(slasher) diff --git a/beacon_node/client/src/config.rs b/beacon_node/client/src/config.rs index 8b47d0fc62..492431cd36 100644 --- a/beacon_node/client/src/config.rs +++ b/beacon_node/client/src/config.rs @@ -1,4 +1,4 @@ -use beacon_chain::validator_monitor::DEFAULT_INDIVIDUAL_TRACKING_THRESHOLD; +use beacon_chain::validator_monitor::ValidatorMonitorConfig; use beacon_chain::TrustedSetup; use beacon_processor::BeaconProcessorConfig; use directory::DEFAULT_ROOT_DIR; @@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize}; use std::fs; use std::path::PathBuf; use std::time::Duration; -use types::{Graffiti, PublicKeyBytes}; +use types::Graffiti; /// Default directory name for the freezer database under the top-level data dir. const DEFAULT_FREEZER_DB_DIR: &str = "freezer_db"; @@ -56,15 +56,7 @@ pub struct Config { pub sync_eth1_chain: bool, /// Graffiti to be inserted everytime we create a block. pub graffiti: Graffiti, - /// When true, automatically monitor validators using the HTTP API. - pub validator_monitor_auto: bool, - /// A list of validator pubkeys to monitor. - pub validator_monitor_pubkeys: Vec, - /// Once the number of monitored validators goes above this threshold, we - /// will stop tracking metrics on a per-validator basis. This prevents large - /// validator counts causing infeasibly high cardinailty for Prometheus and - /// high log volumes. - pub validator_monitor_individual_tracking_threshold: usize, + pub validator_monitor: ValidatorMonitorConfig, #[serde(skip)] /// The `genesis` field is not serialized or deserialized by `serde` to ensure it is defined /// via the CLI at runtime, instead of from a configuration file saved to disk. @@ -107,9 +99,7 @@ impl Default for Config { http_metrics: <_>::default(), monitoring_api: None, slasher: None, - validator_monitor_auto: false, - validator_monitor_pubkeys: vec![], - validator_monitor_individual_tracking_threshold: DEFAULT_INDIVIDUAL_TRACKING_THRESHOLD, + validator_monitor: <_>::default(), logger_config: LoggerConfig::default(), beacon_processor: <_>::default(), genesis_state_url: <_>::default(), diff --git a/beacon_node/http_api/src/proposer_duties.rs b/beacon_node/http_api/src/proposer_duties.rs index 708df39b4d..c31dd9b1fa 100644 --- a/beacon_node/http_api/src/proposer_duties.rs +++ b/beacon_node/http_api/src/proposer_duties.rs @@ -97,12 +97,12 @@ fn try_proposer_duties_from_cache( let head = chain.canonical_head.cached_head(); let head_block = &head.snapshot.beacon_block; let head_block_root = head.head_block_root(); + let head_epoch = head_block.slot().epoch(T::EthSpec::slots_per_epoch()); let head_decision_root = head .snapshot .beacon_state .proposer_shuffling_decision_root(head_block_root) .map_err(warp_utils::reject::beacon_state_error)?; - let head_epoch = head_block.slot().epoch(T::EthSpec::slots_per_epoch()); let execution_optimistic = chain .is_optimistic_or_invalid_head_block(head_block) .map_err(warp_utils::reject::beacon_chain_error)?; diff --git a/beacon_node/network/src/subnet_service/tests/mod.rs b/beacon_node/network/src/subnet_service/tests/mod.rs index 3b8c89a442..769775a62c 100644 --- a/beacon_node/network/src/subnet_service/tests/mod.rs +++ b/beacon_node/network/src/subnet_service/tests/mod.rs @@ -2,7 +2,6 @@ use super::*; use beacon_chain::{ builder::{BeaconChainBuilder, Witness}, eth1_chain::CachingEth1Backend, - validator_monitor::DEFAULT_INDIVIDUAL_TRACKING_THRESHOLD, BeaconChain, }; use futures::prelude::*; @@ -76,7 +75,6 @@ impl TestBeaconChain { Duration::from_millis(SLOT_DURATION_MILLIS), )) .shutdown_sender(shutdown_tx) - .monitor_validators(true, vec![], DEFAULT_INDIVIDUAL_TRACKING_THRESHOLD, log) .build() .expect("should build"), ); diff --git a/beacon_node/src/config.rs b/beacon_node/src/config.rs index 94b94677a5..a4fdf94c66 100644 --- a/beacon_node/src/config.rs +++ b/beacon_node/src/config.rs @@ -719,7 +719,7 @@ pub fn get_config( } if cli_args.is_present("validator-monitor-auto") { - client_config.validator_monitor_auto = true; + client_config.validator_monitor.auto_register = true; } if let Some(pubkeys) = cli_args.value_of("validator-monitor-pubkeys") { @@ -729,7 +729,8 @@ pub fn get_config( .collect::, _>>() .map_err(|e| format!("Invalid --validator-monitor-pubkeys value: {:?}", e))?; client_config - .validator_monitor_pubkeys + .validator_monitor + .validators .extend_from_slice(&pubkeys); } @@ -747,14 +748,17 @@ pub fn get_config( .collect::, _>>() .map_err(|e| format!("Invalid --validator-monitor-file contents: {:?}", e))?; client_config - .validator_monitor_pubkeys + .validator_monitor + .validators .extend_from_slice(&pubkeys); } if let Some(count) = clap_utils::parse_optional(cli_args, "validator-monitor-individual-tracking-threshold")? { - client_config.validator_monitor_individual_tracking_threshold = count; + client_config + .validator_monitor + .individual_tracking_threshold = count; } if cli_args.is_present("disable-lock-timeouts") { @@ -850,7 +854,7 @@ pub fn get_config( // Graphical user interface config. if cli_args.is_present("gui") { client_config.http_api.enabled = true; - client_config.validator_monitor_auto = true; + client_config.validator_monitor.auto_register = true; } // Optimistic finalized sync. diff --git a/consensus/types/src/beacon_state.rs b/consensus/types/src/beacon_state.rs index 18b9866f35..e2e25f24b8 100644 --- a/consensus/types/src/beacon_state.rs +++ b/consensus/types/src/beacon_state.rs @@ -626,6 +626,25 @@ impl BeaconState { cache.get_all_beacon_committees() } + /// Returns the block root which decided the proposer shuffling for the epoch passed in parameter. This root + /// can be used to key this proposer shuffling. + /// + /// ## Notes + /// + /// The `block_root` must be equal to the latest block applied to `self`. + pub fn proposer_shuffling_decision_root_at_epoch( + &self, + epoch: Epoch, + block_root: Hash256, + ) -> Result { + let decision_slot = self.proposer_shuffling_decision_slot(epoch); + if self.slot() <= decision_slot { + Ok(block_root) + } else { + self.get_block_root(decision_slot).map(|root| *root) + } + } + /// Returns the block root which decided the proposer shuffling for the current epoch. This root /// can be used to key this proposer shuffling. /// @@ -634,7 +653,7 @@ impl BeaconState { /// The `block_root` covers the one-off scenario where the genesis block decides its own /// shuffling. It should be set to the latest block applied to `self` or the genesis block root. pub fn proposer_shuffling_decision_root(&self, block_root: Hash256) -> Result { - let decision_slot = self.proposer_shuffling_decision_slot(); + let decision_slot = self.proposer_shuffling_decision_slot(self.current_epoch()); if self.slot() == decision_slot { Ok(block_root) } else { @@ -643,11 +662,9 @@ impl BeaconState { } /// Returns the slot at which the proposer shuffling was decided. The block root at this slot - /// can be used to key the proposer shuffling for the current epoch. - fn proposer_shuffling_decision_slot(&self) -> Slot { - self.current_epoch() - .start_slot(T::slots_per_epoch()) - .saturating_sub(1_u64) + /// can be used to key the proposer shuffling for the given epoch. + fn proposer_shuffling_decision_slot(&self, epoch: Epoch) -> Slot { + epoch.start_slot(T::slots_per_epoch()).saturating_sub(1_u64) } /// Returns the block root which decided the attester shuffling for the given `relative_epoch`. diff --git a/lighthouse/tests/beacon_node.rs b/lighthouse/tests/beacon_node.rs index 7937b1d496..c416aba4fc 100644 --- a/lighthouse/tests/beacon_node.rs +++ b/lighthouse/tests/beacon_node.rs @@ -1772,11 +1772,17 @@ fn metrics_allow_origin_all_flag() { // Tests for Validator Monitor flags. #[test] +fn validator_monitor_default_values() { + CommandLineTest::new() + .run_with_zero_port() + .with_config(|config| assert!(config.validator_monitor == <_>::default())); +} +#[test] fn validator_monitor_auto_flag() { CommandLineTest::new() .flag("validator-monitor-auto", None) .run_with_zero_port() - .with_config(|config| assert!(config.validator_monitor_auto)); + .with_config(|config| assert!(config.validator_monitor.auto_register)); } #[test] fn validator_monitor_pubkeys_flag() { @@ -1785,8 +1791,8 @@ fn validator_monitor_pubkeys_flag() { 0xbeefdeadbeefdeaddeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef")) .run_with_zero_port() .with_config(|config| { - assert_eq!(config.validator_monitor_pubkeys[0].to_string(), "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); - assert_eq!(config.validator_monitor_pubkeys[1].to_string(), "0xbeefdeadbeefdeaddeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); + assert_eq!(config.validator_monitor.validators[0].to_string(), "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); + assert_eq!(config.validator_monitor.validators[1].to_string(), "0xbeefdeadbeefdeaddeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); }); } #[test] @@ -1800,8 +1806,8 @@ fn validator_monitor_file_flag() { .flag("validator-monitor-file", dir.path().join("pubkeys.txt").as_os_str().to_str()) .run_with_zero_port() .with_config(|config| { - assert_eq!(config.validator_monitor_pubkeys[0].to_string(), "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); - assert_eq!(config.validator_monitor_pubkeys[1].to_string(), "0xbeefdeadbeefdeaddeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); + assert_eq!(config.validator_monitor.validators[0].to_string(), "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); + assert_eq!(config.validator_monitor.validators[1].to_string(), "0xbeefdeadbeefdeaddeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); }); } #[test] @@ -1810,7 +1816,7 @@ fn validator_monitor_metrics_threshold_default() { .run_with_zero_port() .with_config(|config| { assert_eq!( - config.validator_monitor_individual_tracking_threshold, + config.validator_monitor.individual_tracking_threshold, // If this value changes make sure to update the help text for // the CLI command. 64 @@ -1826,7 +1832,7 @@ fn validator_monitor_metrics_threshold_custom() { ) .run_with_zero_port() .with_config(|config| { - assert_eq!(config.validator_monitor_individual_tracking_threshold, 42) + assert_eq!(config.validator_monitor.individual_tracking_threshold, 42) }); } @@ -2472,7 +2478,7 @@ fn gui_flag() { .run_with_zero_port() .with_config(|config| { assert!(config.http_api.enabled); - assert!(config.validator_monitor_auto); + assert!(config.validator_monitor.auto_register); }); } From a380f6ef1f04cd2b483224c7d5494294eeea976c Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Thu, 9 Nov 2023 06:05:35 +0200 Subject: [PATCH 06/23] Add block-v3 SSZ tests (#4902) * create block-v3 ssz tests * fmt --- beacon_node/http_api/tests/tests.rs | 106 +++++++++++++++++++++ common/eth2/src/lib.rs | 137 ++++++++++++++++++++++++---- 2 files changed, 223 insertions(+), 20 deletions(-) diff --git a/beacon_node/http_api/tests/tests.rs b/beacon_node/http_api/tests/tests.rs index d532859c79..bd238a7799 100644 --- a/beacon_node/http_api/tests/tests.rs +++ b/beacon_node/http_api/tests/tests.rs @@ -2604,6 +2604,98 @@ impl ApiTester { self } + pub async fn test_block_production_v3_ssz(self) -> Self { + let fork = self.chain.canonical_head.cached_head().head_fork(); + let genesis_validators_root = self.chain.genesis_validators_root; + + for _ in 0..E::slots_per_epoch() * 3 { + let slot = self.chain.slot().unwrap(); + let epoch = self.chain.epoch().unwrap(); + + let proposer_pubkey_bytes = self + .client + .get_validator_duties_proposer(epoch) + .await + .unwrap() + .data + .into_iter() + .find(|duty| duty.slot == slot) + .map(|duty| duty.pubkey) + .unwrap(); + let proposer_pubkey = (&proposer_pubkey_bytes).try_into().unwrap(); + + let sk = self + .validator_keypairs() + .iter() + .find(|kp| kp.pk == proposer_pubkey) + .map(|kp| kp.sk.clone()) + .unwrap(); + + let randao_reveal = { + let domain = self.chain.spec.get_domain( + epoch, + Domain::Randao, + &fork, + genesis_validators_root, + ); + let message = epoch.signing_root(domain); + sk.sign(message).into() + }; + + let (fork_version_response_bytes, is_blinded_payload) = self + .client + .get_validator_blocks_v3_ssz::(slot, &randao_reveal, None) + .await + .unwrap(); + + if is_blinded_payload { + let block_contents = >>::from_ssz_bytes( + &fork_version_response_bytes.unwrap(), + &self.chain.spec, + ) + .expect("block contents bytes can be decoded"); + + let signed_block_contents = + block_contents.sign(&sk, &fork, genesis_validators_root, &self.chain.spec); + + self.client + .post_beacon_blocks_ssz(&signed_block_contents) + .await + .unwrap(); + + // This converts the generic `Payload` to a concrete type for comparison. + let signed_block = signed_block_contents.deconstruct().0; + let head_block = SignedBeaconBlock::from(signed_block.clone()); + assert_eq!(head_block, signed_block); + + self.chain.slot_clock.set_slot(slot.as_u64() + 1); + } else { + let block_contents = >>::from_ssz_bytes( + &fork_version_response_bytes.unwrap(), + &self.chain.spec, + ) + .expect("block contents bytes can be decoded"); + + let signed_block_contents = + block_contents.sign(&sk, &fork, genesis_validators_root, &self.chain.spec); + + self.client + .post_beacon_blocks_ssz(&signed_block_contents) + .await + .unwrap(); + + assert_eq!( + self.chain.head_beacon_block().as_ref(), + signed_block_contents.signed_block() + ); + + self.chain.slot_clock.set_slot(slot.as_u64() + 1); + } + } + + self + } + pub async fn test_block_production_no_verify_randao(self) -> Self { for _ in 0..E::slots_per_epoch() { let slot = self.chain.slot().unwrap(); @@ -5053,6 +5145,20 @@ async fn block_production_ssz_with_skip_slots() { .await; } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn block_production_ssz_v3() { + ApiTester::new().await.test_block_production_v3_ssz().await; +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn block_production_v3_ssz_with_skip_slots() { + ApiTester::new() + .await + .skip_slots(E::slots_per_epoch() * 2) + .test_block_production_v3_ssz() + .await; +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn blinded_block_production_full_payload_premerge() { ApiTester::new() diff --git a/common/eth2/src/lib.rs b/common/eth2/src/lib.rs index c59e59abf5..043c0f197e 100644 --- a/common/eth2/src/lib.rs +++ b/common/eth2/src/lib.rs @@ -272,6 +272,31 @@ impl BeaconNodeHttpClient { } } + /// Perform a HTTP GET request using an 'accept' header, returning `None` on a 404 error. + pub async fn get_bytes_response_with_response_headers( + &self, + url: U, + accept_header: Accept, + timeout: Duration, + ) -> Result<(Option>, Option), Error> { + let opt_response = self + .get_response(url, |b| b.accept(accept_header).timeout(timeout)) + .await + .optional()?; + + // let headers = opt_response.headers(); + match opt_response { + Some(resp) => { + let response_headers = resp.headers().clone(); + Ok(( + Some(resp.bytes().await?.into_iter().collect::>()), + Some(response_headers), + )) + } + None => Ok((None, None)), + } + } + /// Perform a HTTP POST request. async fn post(&self, url: U, body: &T) -> Result<(), Error> { self.post_generic(url, body, None).await?; @@ -1684,6 +1709,38 @@ impl BeaconNodeHttpClient { Ok(path) } + /// returns `GET v3/validator/blocks/{slot}` URL path + pub async fn get_validator_blocks_v3_path( + &self, + slot: Slot, + randao_reveal: &SignatureBytes, + graffiti: Option<&Graffiti>, + skip_randao_verification: SkipRandaoVerification, + ) -> Result { + let mut path = self.eth_path(V3)?; + + path.path_segments_mut() + .map_err(|()| Error::InvalidUrl(self.server.clone()))? + .push("validator") + .push("blocks") + .push(&slot.to_string()); + + path.query_pairs_mut() + .append_pair("randao_reveal", &randao_reveal.to_string()); + + if let Some(graffiti) = graffiti { + path.query_pairs_mut() + .append_pair("graffiti", &graffiti.to_string()); + } + + if skip_randao_verification == SkipRandaoVerification::Yes { + path.query_pairs_mut() + .append_pair("skip_randao_verification", ""); + } + + Ok(path) + } + /// `GET v3/validator/blocks/{slot}` pub async fn get_validator_blocks_v3( &self, @@ -1708,26 +1765,14 @@ impl BeaconNodeHttpClient { graffiti: Option<&Graffiti>, skip_randao_verification: SkipRandaoVerification, ) -> Result, Error> { - let mut path = self.eth_path(V3)?; - - path.path_segments_mut() - .map_err(|()| Error::InvalidUrl(self.server.clone()))? - .push("validator") - .push("blocks") - .push(&slot.to_string()); - - path.query_pairs_mut() - .append_pair("randao_reveal", &randao_reveal.to_string()); - - if let Some(graffiti) = graffiti { - path.query_pairs_mut() - .append_pair("graffiti", &graffiti.to_string()); - } - - if skip_randao_verification == SkipRandaoVerification::Yes { - path.query_pairs_mut() - .append_pair("skip_randao_verification", ""); - } + let path = self + .get_validator_blocks_v3_path::( + slot, + randao_reveal, + graffiti, + skip_randao_verification, + ) + .await?; let response = self.get_response(path, |b| b).await?; @@ -1750,6 +1795,58 @@ impl BeaconNodeHttpClient { } } + /// `GET v3/validator/blocks/{slot}` in ssz format + pub async fn get_validator_blocks_v3_ssz( + &self, + slot: Slot, + randao_reveal: &SignatureBytes, + graffiti: Option<&Graffiti>, + ) -> Result<(Option>, bool), Error> { + self.get_validator_blocks_v3_modular_ssz::( + slot, + randao_reveal, + graffiti, + SkipRandaoVerification::No, + ) + .await + } + + /// `GET v3/validator/blocks/{slot}` in ssz format + pub async fn get_validator_blocks_v3_modular_ssz( + &self, + slot: Slot, + randao_reveal: &SignatureBytes, + graffiti: Option<&Graffiti>, + skip_randao_verification: SkipRandaoVerification, + ) -> Result<(Option>, bool), Error> { + let path = self + .get_validator_blocks_v3_path::( + slot, + randao_reveal, + graffiti, + skip_randao_verification, + ) + .await?; + + let (response_content, response_headers) = self + .get_bytes_response_with_response_headers( + path, + Accept::Ssz, + self.timeouts.get_validator_block_ssz, + ) + .await?; + + let is_blinded_payload = match response_headers { + Some(headers) => headers + .get(EXECUTION_PAYLOAD_BLINDED_HEADER) + .map(|value| value.to_str().unwrap_or_default().to_lowercase() == "true") + .unwrap_or(false), + None => false, + }; + + Ok((response_content, is_blinded_payload)) + } + /// `GET v2/validator/blocks/{slot}` in ssz format pub async fn get_validator_blocks_ssz>( &self, From 7818100777fe01ca5b7ca869145e20eeef0fcaa8 Mon Sep 17 00:00:00 2001 From: ethDreamer <37123614+ethDreamer@users.noreply.github.com> Date: Wed, 8 Nov 2023 22:05:44 -0600 Subject: [PATCH 07/23] Verify KZG in Bulk During Block Sync (#4903) --- .../beacon_chain/src/block_verification.rs | 53 +++++++------- .../src/block_verification_types.rs | 7 ++ .../src/data_availability_checker.rs | 71 ++++++++++++++++++- .../tests/attestation_production.rs | 4 +- beacon_node/beacon_chain/tests/store_tests.rs | 4 +- .../network_beacon_processor/sync_methods.rs | 12 ++-- 6 files changed, 110 insertions(+), 41 deletions(-) diff --git a/beacon_node/beacon_chain/src/block_verification.rs b/beacon_node/beacon_chain/src/block_verification.rs index 7f1a596ec3..65cf7a728b 100644 --- a/beacon_node/beacon_chain/src/block_verification.rs +++ b/beacon_node/beacon_chain/src/block_verification.rs @@ -583,29 +583,33 @@ pub fn signature_verify_chain_segment( &chain.spec, )?; + // unzip chain segment and verify kzg in bulk + let (roots, blocks): (Vec<_>, Vec<_>) = chain_segment.into_iter().unzip(); + let maybe_available_blocks = chain + .data_availability_checker + .verify_kzg_for_rpc_blocks(blocks)?; + // zip it back up + let mut signature_verified_blocks = roots + .into_iter() + .zip(maybe_available_blocks) + .map(|(block_root, maybe_available_block)| { + let consensus_context = ConsensusContext::new(maybe_available_block.slot()) + .set_current_block_root(block_root); + SignatureVerifiedBlock { + block: maybe_available_block, + block_root, + parent: None, + consensus_context, + } + }) + .collect::>(); + + // verify signatures let pubkey_cache = get_validator_pubkey_cache(chain)?; let mut signature_verifier = get_signature_verifier(&state, &pubkey_cache, &chain.spec); - - let mut signature_verified_blocks = Vec::with_capacity(chain_segment.len()); - - for (block_root, block) in &chain_segment { - let mut consensus_context = - ConsensusContext::new(block.slot()).set_current_block_root(*block_root); - - signature_verifier.include_all_signatures(block.as_block(), &mut consensus_context)?; - - let maybe_available_block = chain - .data_availability_checker - .check_rpc_block_availability(block.clone())?; - - // Save the block and its consensus context. The context will have had its proposer index - // and attesting indices filled in, which can be used to accelerate later block processing. - signature_verified_blocks.push(SignatureVerifiedBlock { - block: maybe_available_block, - block_root: *block_root, - parent: None, - consensus_context, - }); + for svb in &mut signature_verified_blocks { + signature_verifier + .include_all_signatures(svb.block.as_block(), &mut svb.consensus_context)?; } if signature_verifier.verify().is_err() { @@ -1159,10 +1163,7 @@ impl IntoExecutionPendingBlock for Arc IntoExecutionPendingBlock for RpcBlock .map_err(|e| BlockSlashInfo::SignatureNotChecked(self.signed_block_header(), e))?; let maybe_available = chain .data_availability_checker - .check_rpc_block_availability(self.clone()) + .verify_kzg_for_rpc_block(self.clone()) .map_err(|e| { BlockSlashInfo::SignatureNotChecked( self.signed_block_header(), diff --git a/beacon_node/beacon_chain/src/block_verification_types.rs b/beacon_node/beacon_chain/src/block_verification_types.rs index d236e94f93..9cd853ba8c 100644 --- a/beacon_node/beacon_chain/src/block_verification_types.rs +++ b/beacon_node/beacon_chain/src/block_verification_types.rs @@ -45,6 +45,13 @@ impl RpcBlock { RpcBlockInner::BlockAndBlobs(block, _) => block, } } + + pub fn blobs(&self) -> Option<&BlobSidecarList> { + match &self.block { + RpcBlockInner::Block(_) => None, + RpcBlockInner::BlockAndBlobs(_, blobs) => Some(blobs), + } + } } /// Note: This variant is intentionally private because we want to safely construct the diff --git a/beacon_node/beacon_chain/src/data_availability_checker.rs b/beacon_node/beacon_chain/src/data_availability_checker.rs index a8d077a6d0..ad328077d0 100644 --- a/beacon_node/beacon_chain/src/data_availability_checker.rs +++ b/beacon_node/beacon_chain/src/data_availability_checker.rs @@ -240,9 +240,12 @@ impl DataAvailabilityChecker { .put_pending_executed_block(executed_block) } - /// Checks if a block is available, returns a `MaybeAvailableBlock` that may include the fully - /// available block. - pub fn check_rpc_block_availability( + /// Verifies kzg commitments for an RpcBlock, returns a `MaybeAvailableBlock` that may + /// include the fully available block. + /// + /// WARNING: This function assumes all required blobs are already present, it does NOT + /// check if there are any missing blobs. + pub fn verify_kzg_for_rpc_block( &self, block: RpcBlock, ) -> Result, AvailabilityCheckError> { @@ -279,6 +282,68 @@ impl DataAvailabilityChecker { } } + /// Checks if a vector of blocks are available. Returns a vector of `MaybeAvailableBlock` + /// This is more efficient than calling `verify_kzg_for_rpc_block` in a loop as it does + /// all kzg verification at once + /// + /// WARNING: This function assumes all required blobs are already present, it does NOT + /// check if there are any missing blobs. + pub fn verify_kzg_for_rpc_blocks( + &self, + blocks: Vec>, + ) -> Result>, AvailabilityCheckError> { + let mut results = Vec::with_capacity(blocks.len()); + let all_blobs: BlobSidecarList = blocks + .iter() + .filter(|block| self.blobs_required_for_block(block.as_block())) + // this clone is cheap as it's cloning an Arc + .filter_map(|block| block.blobs().cloned()) + .flatten() + .collect::>() + .into(); + + // verify kzg for all blobs at once + if !all_blobs.is_empty() { + let kzg = self + .kzg + .as_ref() + .ok_or(AvailabilityCheckError::KzgNotInitialized)?; + verify_kzg_for_blob_list(&all_blobs, kzg)?; + } + + for block in blocks { + let (block_root, block, blobs) = block.deconstruct(); + match blobs { + None => { + if self.blobs_required_for_block(&block) { + results.push(MaybeAvailableBlock::AvailabilityPending { block_root, block }) + } else { + results.push(MaybeAvailableBlock::Available(AvailableBlock { + block_root, + block, + blobs: None, + })) + } + } + Some(blob_list) => { + let verified_blobs = if self.blobs_required_for_block(&block) { + Some(blob_list) + } else { + None + }; + // already verified kzg for all blobs + results.push(MaybeAvailableBlock::Available(AvailableBlock { + block_root, + block, + blobs: verified_blobs, + })) + } + } + } + + Ok(results) + } + /// Determines the blob requirements for a block. If the block is pre-deneb, no blobs are required. /// If the block's epoch is from prior to the data availability boundary, no blobs are required. fn blobs_required_for_block(&self, block: &SignedBeaconBlock) -> bool { diff --git a/beacon_node/beacon_chain/tests/attestation_production.rs b/beacon_node/beacon_chain/tests/attestation_production.rs index a8ad75304b..fdc37b5529 100644 --- a/beacon_node/beacon_chain/tests/attestation_production.rs +++ b/beacon_node/beacon_chain/tests/attestation_production.rs @@ -140,7 +140,7 @@ async fn produces_attestations() { available_block, ) = chain .data_availability_checker - .check_rpc_block_availability(rpc_block) + .verify_kzg_for_rpc_block(rpc_block) .unwrap() else { panic!("block should be available") @@ -218,7 +218,7 @@ async fn early_attester_cache_old_request() { harness .chain .data_availability_checker - .check_rpc_block_availability(rpc_block) + .verify_kzg_for_rpc_block(rpc_block) .unwrap() else { panic!("block should be available") diff --git a/beacon_node/beacon_chain/tests/store_tests.rs b/beacon_node/beacon_chain/tests/store_tests.rs index 5107396ac2..d3f6428851 100644 --- a/beacon_node/beacon_chain/tests/store_tests.rs +++ b/beacon_node/beacon_chain/tests/store_tests.rs @@ -2464,10 +2464,10 @@ async fn weak_subjectivity_sync_test(slots: Vec, checkpoint_slot: Slot) { if let MaybeAvailableBlock::Available(block) = harness .chain .data_availability_checker - .check_rpc_block_availability( + .verify_kzg_for_rpc_block( RpcBlock::new(Some(block_root), Arc::new(full_block), Some(blobs)).unwrap(), ) - .expect("should check availability") + .expect("should verify kzg") { available_blocks.push(block); } diff --git a/beacon_node/network/src/network_beacon_processor/sync_methods.rs b/beacon_node/network/src/network_beacon_processor/sync_methods.rs index b034d0ff31..d76ce5aadd 100644 --- a/beacon_node/network/src/network_beacon_processor/sync_methods.rs +++ b/beacon_node/network/src/network_beacon_processor/sync_methods.rs @@ -560,14 +560,10 @@ impl NetworkBeaconProcessor { downloaded_blocks: Vec>, ) -> (usize, Result<(), ChainSegmentFailed>) { let total_blocks = downloaded_blocks.len(); - let available_blocks = match downloaded_blocks - .into_iter() - .map(|block| { - self.chain - .data_availability_checker - .check_rpc_block_availability(block) - }) - .collect::, _>>() + let available_blocks = match self + .chain + .data_availability_checker + .verify_kzg_for_rpc_blocks(downloaded_blocks) { Ok(blocks) => blocks .into_iter() From 7fd9389a8cc8ebc8826c0ee434717d8a1971f175 Mon Sep 17 00:00:00 2001 From: chonghe <44791194+chong-he@users.noreply.github.com> Date: Thu, 9 Nov 2023 12:05:55 +0800 Subject: [PATCH 08/23] Delete deprecated cli flags (#4906) * Delete BN spec flag and VC beacon-node flag * Remove warn * slog * add warn * delete eth1-endpoint * delete server from vc cli.rs * delete server flag in config.rs * delete delete-lockfiles in vc * delete allow-unsynced flag in VC * delete strict-fee-recipient in VC and warn log * delete merge flag in bn (hidden) * delete count-unrealized and count-unrealized-full in bn (hidden) * delete http-disable-legacy-spec in bn (hidden) * delete eth1-endpoint in lcli * delete warn message lcli * delete eth1-endpoints * delete minify in slashing protection * delete minify related * Remove mut * add back warn! log * Indentation * Delete count-unrealized * Delete eth1-endpoints * Delete eth1-endpoint test * delete eth1-endpints test * delete allow-unsynced test * Add back lcli eth1-endpoint --------- Co-authored-by: Michael Sproul --- .../src/validator/slashing_protection.rs | 48 +------- beacon_node/src/cli.rs | 48 -------- beacon_node/src/config.rs | 66 ---------- lighthouse/src/main.rs | 21 +--- lighthouse/tests/beacon_node.rs | 114 ------------------ lighthouse/tests/validator_client.rs | 6 - validator_client/src/cli.rs | 42 ------- validator_client/src/config.rs | 46 +------ 8 files changed, 5 insertions(+), 386 deletions(-) diff --git a/account_manager/src/validator/slashing_protection.rs b/account_manager/src/validator/slashing_protection.rs index c6d81275a5..0a98a452b8 100644 --- a/account_manager/src/validator/slashing_protection.rs +++ b/account_manager/src/validator/slashing_protection.rs @@ -16,7 +16,6 @@ pub const EXPORT_CMD: &str = "export"; pub const IMPORT_FILE_ARG: &str = "IMPORT-FILE"; pub const EXPORT_FILE_ARG: &str = "EXPORT-FILE"; -pub const MINIFY_FLAG: &str = "minify"; pub const PUBKEYS_FLAG: &str = "pubkeys"; pub fn cli_app<'a, 'b>() -> App<'a, 'b> { @@ -31,16 +30,6 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { .value_name("FILE") .help("The slashing protection interchange file to import (.json)"), ) - .arg( - Arg::with_name(MINIFY_FLAG) - .long(MINIFY_FLAG) - .takes_value(true) - .possible_values(&["false", "true"]) - .help( - "Deprecated: Lighthouse no longer requires minification on import \ - because it always minifies", - ), - ), ) .subcommand( App::new(EXPORT_CMD) @@ -61,17 +50,6 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { comma-separated. All known keys will be exported if omitted", ), ) - .arg( - Arg::with_name(MINIFY_FLAG) - .long(MINIFY_FLAG) - .takes_value(true) - .default_value("false") - .possible_values(&["false", "true"]) - .help( - "Minify the output file. This will make it smaller and faster to \ - import, but not faster to generate.", - ), - ), ) } @@ -92,7 +70,6 @@ pub fn cli_run( match matches.subcommand() { (IMPORT_CMD, Some(matches)) => { let import_filename: PathBuf = clap_utils::parse_required(matches, IMPORT_FILE_ARG)?; - let minify: Option = clap_utils::parse_optional(matches, MINIFY_FLAG)?; let import_file = File::open(&import_filename).map_err(|e| { format!( "Unable to open import file at {}: {:?}", @@ -102,23 +79,10 @@ pub fn cli_run( })?; eprint!("Loading JSON file into memory & deserializing"); - let mut interchange = Interchange::from_json_reader(&import_file) + let interchange = Interchange::from_json_reader(&import_file) .map_err(|e| format!("Error parsing file for import: {:?}", e))?; eprintln!(" [done]."); - if let Some(minify) = minify { - eprintln!( - "WARNING: --minify flag is deprecated and will be removed in a future release" - ); - if minify { - eprint!("Minifying input file for faster loading"); - interchange = interchange - .minify() - .map_err(|e| format!("Minification failed: {:?}", e))?; - eprintln!(" [done]."); - } - } - let slashing_protection_database = SlashingDatabase::open_or_create(&slashing_protection_db_path).map_err(|e| { format!( @@ -206,7 +170,6 @@ pub fn cli_run( } (EXPORT_CMD, Some(matches)) => { let export_filename: PathBuf = clap_utils::parse_required(matches, EXPORT_FILE_ARG)?; - let minify: bool = clap_utils::parse_required(matches, MINIFY_FLAG)?; let selected_pubkeys = if let Some(pubkeys) = clap_utils::parse_optional::(matches, PUBKEYS_FLAG)? @@ -237,17 +200,10 @@ pub fn cli_run( ) })?; - let mut interchange = slashing_protection_database + let interchange = slashing_protection_database .export_interchange_info(genesis_validators_root, selected_pubkeys.as_deref()) .map_err(|e| format!("Error during export: {:?}", e))?; - if minify { - eprintln!("Minifying output file"); - interchange = interchange - .minify() - .map_err(|e| format!("Unable to minify output: {:?}", e))?; - } - let output_file = File::create(export_filename) .map_err(|e| format!("Error creating output file: {:?}", e))?; diff --git a/beacon_node/src/cli.rs b/beacon_node/src/cli.rs index 7b9cd757a5..d76f2f375f 100644 --- a/beacon_node/src/cli.rs +++ b/beacon_node/src/cli.rs @@ -388,12 +388,6 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { address of this server (e.g., http://localhost:5052).") .takes_value(true), ) - .arg( - Arg::with_name("http-disable-legacy-spec") - .long("http-disable-legacy-spec") - .requires("enable_http") - .hidden(true) - ) .arg( Arg::with_name("http-spec-fork") .long("http-spec-fork") @@ -569,24 +563,6 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { .help("If present, uses an eth1 backend that generates static dummy data.\ Identical to the method used at the 2019 Canada interop.") ) - .arg( - Arg::with_name("eth1-endpoint") - .long("eth1-endpoint") - .value_name("HTTP-ENDPOINT") - .help("Deprecated. Use --eth1-endpoints.") - .takes_value(true) - ) - .arg( - Arg::with_name("eth1-endpoints") - .long("eth1-endpoints") - .value_name("HTTP-ENDPOINTS") - .conflicts_with("eth1-endpoint") - .help("One http endpoint for a web3 connection to an execution node. \ - Note: This flag is now only useful for testing, use `--execution-endpoint` \ - flag to connect to an execution node on mainnet and testnets. - Defaults to http://127.0.0.1:8545.") - .takes_value(true) - ) .arg( Arg::with_name("eth1-purge-cache") .long("eth1-purge-cache") @@ -649,14 +625,6 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { /* * Execution Layer Integration */ - .arg( - Arg::with_name("merge") - .long("merge") - .help("Deprecated. The feature activates automatically when --execution-endpoint \ - is supplied.") - .takes_value(false) - .hidden(true) - ) .arg( Arg::with_name("execution-endpoint") .long("execution-endpoint") @@ -1200,22 +1168,6 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { .requires("builder") .takes_value(true) ) - .arg( - Arg::with_name("count-unrealized") - .long("count-unrealized") - .hidden(true) - .help("This flag is deprecated and has no effect.") - .takes_value(true) - .default_value("true") - ) - .arg( - Arg::with_name("count-unrealized-full") - .long("count-unrealized-full") - .hidden(true) - .help("This flag is deprecated and has no effect.") - .takes_value(true) - .default_value("false") - ) .arg( Arg::with_name("reset-payload-statuses") .long("reset-payload-statuses") diff --git a/beacon_node/src/config.rs b/beacon_node/src/config.rs index a4fdf94c66..609626ae88 100644 --- a/beacon_node/src/config.rs +++ b/beacon_node/src/config.rs @@ -120,13 +120,6 @@ pub fn get_config( client_config.http_api.allow_origin = Some(allow_origin.to_string()); } - if cli_args.is_present("http-disable-legacy-spec") { - warn!( - log, - "The flag --http-disable-legacy-spec is deprecated and will be removed" - ); - } - if let Some(fork_name) = clap_utils::parse_optional(cli_args, "http-spec-fork")? { client_config.http_api.spec_fork_name = Some(fork_name); } @@ -240,25 +233,6 @@ pub fn get_config( client_config.sync_eth1_chain = true; } - // Defines the URL to reach the eth1 node. - if let Some(endpoint) = cli_args.value_of("eth1-endpoint") { - warn!( - log, - "The --eth1-endpoint flag is deprecated"; - "msg" => "please use --eth1-endpoints instead" - ); - client_config.sync_eth1_chain = true; - - let endpoint = SensitiveUrl::parse(endpoint) - .map_err(|e| format!("eth1-endpoint was an invalid URL: {:?}", e))?; - client_config.eth1.endpoint = Eth1Endpoint::NoAuth(endpoint); - } else if let Some(endpoint) = cli_args.value_of("eth1-endpoints") { - client_config.sync_eth1_chain = true; - let endpoint = SensitiveUrl::parse(endpoint) - .map_err(|e| format!("eth1-endpoints contains an invalid URL {:?}", e))?; - client_config.eth1.endpoint = Eth1Endpoint::NoAuth(endpoint); - } - if let Some(val) = cli_args.value_of("eth1-blocks-per-log-query") { client_config.eth1.blocks_per_log_query = val .parse() @@ -275,20 +249,6 @@ pub fn get_config( client_config.eth1.cache_follow_distance = Some(follow_distance); } - if cli_args.is_present("merge") { - if cli_args.is_present("execution-endpoint") { - warn!( - log, - "The --merge flag is deprecated"; - "info" => "the --execution-endpoint flag automatically enables this feature" - ) - } else { - return Err("The --merge flag is deprecated. \ - Supply a value to --execution-endpoint instead." - .into()); - } - } - if let Some(endpoints) = cli_args.value_of("execution-endpoint") { let mut el_config = execution_layer::Config::default(); @@ -364,16 +324,6 @@ pub fn get_config( clap_utils::parse_required(cli_args, "execution-timeout-multiplier")?; el_config.execution_timeout_multiplier = Some(execution_timeout_multiplier); - // If `--execution-endpoint` is provided, we should ignore any `--eth1-endpoints` values and - // use `--execution-endpoint` instead. Also, log a deprecation warning. - if cli_args.is_present("eth1-endpoints") || cli_args.is_present("eth1-endpoint") { - warn!( - log, - "Ignoring --eth1-endpoints flag"; - "info" => "the value for --execution-endpoint will be used instead. \ - --eth1-endpoints has been deprecated for post-merge configurations" - ); - } client_config.eth1.endpoint = Eth1Endpoint::Auth { endpoint: execution_endpoint, jwt_path: secret_file, @@ -816,22 +766,6 @@ pub fn get_config( client_config.chain.fork_choice_before_proposal_timeout_ms = timeout; } - if !clap_utils::parse_required::(cli_args, "count-unrealized")? { - warn!( - log, - "The flag --count-unrealized is deprecated and will be removed"; - "info" => "any use of the flag will have no effect" - ); - } - - if clap_utils::parse_required::(cli_args, "count-unrealized-full")? { - warn!( - log, - "The flag --count-unrealized-full is deprecated and will be removed"; - "info" => "setting it to `true` has no effect" - ); - } - client_config.chain.always_reset_payload_statuses = cli_args.is_present("reset-payload-statuses"); diff --git a/lighthouse/src/main.rs b/lighthouse/src/main.rs index f98af96176..edf661abab 100644 --- a/lighthouse/src/main.rs +++ b/lighthouse/src/main.rs @@ -11,7 +11,7 @@ use ethereum_hashing::have_sha_extensions; use futures::TryFutureExt; use lighthouse_version::VERSION; use malloc_utils::configure_memory_allocator; -use slog::{crit, info, warn}; +use slog::{crit, info}; use std::path::PathBuf; use std::process::exit; use task_executor::ShutdownReason; @@ -81,16 +81,6 @@ fn main() { cfg!(feature = "gnosis"), ).as_str() ) - .arg( - Arg::with_name("spec") - .short("s") - .long("spec") - .value_name("DEPRECATED") - .help("This flag is deprecated, it will be disallowed in a future release. This \ - value is now derived from the --network or --testnet-dir flags.") - .takes_value(true) - .global(true) - ) .arg( Arg::with_name("env_log") .short("l") @@ -549,16 +539,9 @@ fn run( // Allow Prometheus access to the version and commit of the Lighthouse build. metrics::expose_lighthouse_version(); - if matches.is_present("spec") { - warn!( - log, - "The --spec flag is deprecated and will be removed in a future release" - ); - } - #[cfg(all(feature = "modern", target_arch = "x86_64"))] if !std::is_x86_feature_detected!("adx") { - warn!( + slog::warn!( log, "CPU seems incompatible with optimized Lighthouse build"; "advice" => "If you get a SIGILL, please try Lighthouse portable build" diff --git a/lighthouse/tests/beacon_node.rs b/lighthouse/tests/beacon_node.rs index c416aba4fc..c8e064e224 100644 --- a/lighthouse/tests/beacon_node.rs +++ b/lighthouse/tests/beacon_node.rs @@ -243,60 +243,6 @@ fn paranoid_block_proposal_on() { .with_config(|config| assert!(config.chain.paranoid_block_proposal)); } -#[test] -fn count_unrealized_no_arg() { - CommandLineTest::new() - .flag("count-unrealized", None) - // This flag should be ignored, so there's nothing to test but that the - // client starts with the flag present. - .run_with_zero_port(); -} - -#[test] -fn count_unrealized_false() { - CommandLineTest::new() - .flag("count-unrealized", Some("false")) - // This flag should be ignored, so there's nothing to test but that the - // client starts with the flag present. - .run_with_zero_port(); -} - -#[test] -fn count_unrealized_true() { - CommandLineTest::new() - .flag("count-unrealized", Some("true")) - // This flag should be ignored, so there's nothing to test but that the - // client starts with the flag present. - .run_with_zero_port(); -} - -#[test] -fn count_unrealized_full_no_arg() { - CommandLineTest::new() - .flag("count-unrealized-full", None) - // This flag should be ignored, so there's nothing to test but that the - // client starts with the flag present. - .run_with_zero_port(); -} - -#[test] -fn count_unrealized_full_false() { - CommandLineTest::new() - .flag("count-unrealized-full", Some("false")) - // This flag should be ignored, so there's nothing to test but that the - // client starts with the flag present. - .run_with_zero_port(); -} - -#[test] -fn count_unrealized_full_true() { - CommandLineTest::new() - .flag("count-unrealized-full", Some("true")) - // This flag should be ignored, so there's nothing to test but that the - // client starts with the flag present. - .run_with_zero_port(); -} - #[test] fn reset_payload_statuses_default() { CommandLineTest::new() @@ -388,23 +334,6 @@ fn eth1_flag() { .with_config(|config| assert!(config.sync_eth1_chain)); } #[test] -fn eth1_endpoints_flag() { - CommandLineTest::new() - .flag("eth1-endpoints", Some("http://localhost:9545")) - .run_with_zero_port() - .with_config(|config| { - assert_eq!( - config.eth1.endpoint.get_endpoint().full.to_string(), - "http://localhost:9545/" - ); - assert_eq!( - config.eth1.endpoint.get_endpoint().to_string(), - "http://localhost:9545/" - ); - assert!(config.sync_eth1_chain); - }); -} -#[test] fn eth1_blocks_per_log_query_flag() { CommandLineTest::new() .flag("eth1-blocks-per-log-query", Some("500")) @@ -527,49 +456,6 @@ fn merge_execution_endpoints_flag() { fn merge_execution_endpoint_flag() { run_merge_execution_endpoints_flag_test("execution-endpoint") } -fn run_execution_endpoints_overrides_eth1_endpoints_test(eth1_flag: &str, execution_flag: &str) { - use sensitive_url::SensitiveUrl; - - let eth1_endpoint = "http://bad.bad"; - let execution_endpoint = "http://good.good"; - - assert!(eth1_endpoint != execution_endpoint); - - let dir = TempDir::new().expect("Unable to create temporary directory"); - let jwt_path = dir.path().join("jwt-file"); - - CommandLineTest::new() - .flag(eth1_flag, Some(ð1_endpoint)) - .flag(execution_flag, Some(&execution_endpoint)) - .flag("execution-jwt", jwt_path.as_os_str().to_str()) - .run_with_zero_port() - .with_config(|config| { - assert_eq!( - config.execution_layer.as_ref().unwrap().execution_endpoints, - vec![SensitiveUrl::parse(execution_endpoint).unwrap()] - ); - - // The eth1 endpoint should have been set to the --execution-endpoint value in defiance - // of --eth1-endpoints. - assert_eq!( - config.eth1.endpoint, - Eth1Endpoint::Auth { - endpoint: SensitiveUrl::parse(execution_endpoint).unwrap(), - jwt_path: jwt_path.clone(), - jwt_id: None, - jwt_version: None, - } - ); - }); -} -#[test] -fn execution_endpoints_overrides_eth1_endpoints() { - run_execution_endpoints_overrides_eth1_endpoints_test("eth1-endpoints", "execution-endpoints"); -} -#[test] -fn execution_endpoint_overrides_eth1_endpoint() { - run_execution_endpoints_overrides_eth1_endpoints_test("eth1-endpoint", "execution-endpoint"); -} #[test] fn merge_jwt_secrets_flag() { let dir = TempDir::new().expect("Unable to create temporary directory"); diff --git a/lighthouse/tests/validator_client.rs b/lighthouse/tests/validator_client.rs index 062b7e7786..a3b878ad21 100644 --- a/lighthouse/tests/validator_client.rs +++ b/lighthouse/tests/validator_client.rs @@ -101,12 +101,6 @@ fn beacon_nodes_flag() { }); } -#[test] -fn allow_unsynced_flag() { - // No-op, but doesn't crash. - CommandLineTest::new().flag("allow-unsynced", None).run(); -} - #[test] fn disable_auto_discover_flag() { CommandLineTest::new() diff --git a/validator_client/src/cli.rs b/validator_client/src/cli.rs index 0af92a9e39..4ce4035175 100644 --- a/validator_client/src/cli.rs +++ b/validator_client/src/cli.rs @@ -8,15 +8,6 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { "When connected to a beacon node, performs the duties of a staked \ validator (e.g., proposing blocks and attestations).", ) - // This argument is deprecated, use `--beacon-nodes` instead. - .arg( - Arg::with_name("beacon-node") - .long("beacon-node") - .value_name("NETWORK_ADDRESS") - .help("Deprecated. Use --beacon-nodes.") - .takes_value(true) - .conflicts_with("beacon-nodes"), - ) .arg( Arg::with_name("beacon-nodes") .long("beacon-nodes") @@ -45,15 +36,6 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { api calls only go out to the first available and synced beacon node") .takes_value(false) ) - // This argument is deprecated, use `--beacon-nodes` instead. - .arg( - Arg::with_name("server") - .long("server") - .value_name("NETWORK_ADDRESS") - .help("Deprecated. Use --beacon-nodes.") - .takes_value(true) - .conflicts_with_all(&["beacon-node", "beacon-nodes"]), - ) .arg( Arg::with_name("validators-dir") .long("validators-dir") @@ -80,13 +62,6 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { .takes_value(true) .conflicts_with("datadir") ) - .arg( - Arg::with_name("delete-lockfiles") - .long("delete-lockfiles") - .help( - "DEPRECATED. This flag does nothing and will be removed in a future release." - ) - ) .arg( Arg::with_name("init-slashing-protection") .long("init-slashing-protection") @@ -106,11 +81,6 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { will need to be manually added to the validator_definitions.yml file." ) ) - .arg( - Arg::with_name("allow-unsynced") - .long("allow-unsynced") - .help("DEPRECATED: this flag does nothing"), - ) .arg( Arg::with_name("use-long-timeouts") .long("use-long-timeouts") @@ -319,18 +289,6 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { headers during proposals and will sign over headers. Useful for outsourcing \ execution payload construction during proposals.") .takes_value(false), - ).arg( - Arg::with_name("strict-fee-recipient") - .long("strict-fee-recipient") - .help("[DEPRECATED] If this flag is set, Lighthouse will refuse to sign any block whose \ - `fee_recipient` does not match the `suggested_fee_recipient` sent by this validator. \ - This applies to both the normal block proposal flow, as well as block proposals \ - through the builder API. Proposals through the builder API are more likely to have a \ - discrepancy in `fee_recipient` so you should be aware of how your connected relay \ - sends proposer payments before using this flag. If this flag is used, a fee recipient \ - mismatch in the builder API flow will result in a fallback to the local execution engine \ - for payload construction, where a strict fee recipient check will still be applied.") - .takes_value(false), ) .arg( Arg::with_name("builder-registration-timestamp-override") diff --git a/validator_client/src/config.rs b/validator_client/src/config.rs index aa5ce6c983..c93fe6c141 100644 --- a/validator_client/src/config.rs +++ b/validator_client/src/config.rs @@ -9,7 +9,7 @@ use directory::{ use eth2::types::Graffiti; use sensitive_url::SensitiveUrl; use serde::{Deserialize, Serialize}; -use slog::{info, warn, Logger}; +use slog::{info, Logger}; use std::fs; use std::net::IpAddr; use std::path::PathBuf; @@ -171,27 +171,6 @@ impl Config { .collect::>() .map_err(|e| format!("Unable to parse beacon node URL: {:?}", e))?; } - // To be deprecated. - else if let Some(beacon_node) = parse_optional::(cli_args, "beacon-node")? { - warn!( - log, - "The --beacon-node flag is deprecated"; - "msg" => "please use --beacon-nodes instead" - ); - config.beacon_nodes = vec![SensitiveUrl::parse(&beacon_node) - .map_err(|e| format!("Unable to parse beacon node URL: {:?}", e))?]; - } - // To be deprecated. - else if let Some(server) = parse_optional::(cli_args, "server")? { - warn!( - log, - "The --server flag is deprecated"; - "msg" => "please use --beacon-nodes instead" - ); - config.beacon_nodes = vec![SensitiveUrl::parse(&server) - .map_err(|e| format!("Unable to parse beacon node URL: {:?}", e))?]; - } - if let Some(proposer_nodes) = parse_optional::(cli_args, "proposer_nodes")? { config.proposer_nodes = proposer_nodes .split(',') @@ -200,21 +179,6 @@ impl Config { .map_err(|e| format!("Unable to parse proposer node URL: {:?}", e))?; } - if cli_args.is_present("delete-lockfiles") { - warn!( - log, - "The --delete-lockfiles flag is deprecated"; - "msg" => "it is no longer necessary, and no longer has any effect", - ); - } - - if cli_args.is_present("allow-unsynced") { - warn!( - log, - "The --allow-unsynced flag is deprecated"; - "msg" => "it no longer has any effect", - ); - } config.disable_run_on_all = cli_args.is_present("disable-run-on-all"); config.disable_auto_discover = cli_args.is_present("disable-auto-discover"); config.init_slashing_protection = cli_args.is_present("init-slashing-protection"); @@ -380,14 +344,6 @@ impl Config { ); } - if cli_args.is_present("strict-fee-recipient") { - warn!( - log, - "The flag `--strict-fee-recipient` has been deprecated due to a bug causing \ - missed proposals. The flag will be ignored." - ); - } - config.enable_latency_measurement_service = parse_optional(cli_args, "latency-measurement-service")?.unwrap_or(true); From 1b8c0ed987f493d1ca6d1f93b7c883a51a170691 Mon Sep 17 00:00:00 2001 From: chonghe <44791194+chong-he@users.noreply.github.com> Date: Thu, 9 Nov 2023 12:06:10 +0800 Subject: [PATCH 09/23] Update local testnet doc and parameters (#4749) * update * revise link * update parameters * update doc * Update doc * add quic port * remove debug log * Fix el_bootnode not being killed * Fix time * Fix doc in manual creation of testnet * Update file * update api doc * Revert "update api doc" This reverts commit ed695743deca4818ad074e5285e04764965b1be5. * add git clone * Fix path * Fix path * Update scripts/local_testnet/setup_time.sh Co-authored-by: Jimmy Chen * Update scripts/local_testnet/README.md Co-authored-by: Jimmy Chen * Fix SLOT_PER_EPOCH that changes with mainnet or minimal * Embedded setup_time.sh in start_local_testnet.sh * fix slot per epoch constant * Add comment * Add CANCUN_TIME * Fix CANCUN_TIME constant 32 slots * Correct typo * chmod +x ./setup_time.sh --------- Co-authored-by: Jimmy Chen --- scripts/local_testnet/README.md | 95 +++++++++++++++----- scripts/local_testnet/el_bootnode.sh | 2 +- scripts/local_testnet/setup.sh | 2 +- scripts/local_testnet/setup_time.sh | 32 +++++++ scripts/local_testnet/start_local_testnet.sh | 12 +-- scripts/local_testnet/vars.env | 2 +- 6 files changed, 111 insertions(+), 34 deletions(-) create mode 100755 scripts/local_testnet/setup_time.sh diff --git a/scripts/local_testnet/README.md b/scripts/local_testnet/README.md index 4f54154d1d..87565b0cae 100644 --- a/scripts/local_testnet/README.md +++ b/scripts/local_testnet/README.md @@ -5,58 +5,105 @@ This setup can be useful for testing and development. ## Requirements -The scripts require `lcli`, `lighthouse`, `geth`, `bootnode` to be installed on `PATH`. +The scripts require `lcli`, `lighthouse`, `geth`, `bootnode` to be installed on `PATH` (run `echo $PATH` to view all `PATH` directories). MacOS users need to install GNU `sed` and GNU `grep`, and add them both to `PATH` as well. -From the -root of this repository, run: +The first step is to install Rust and dependencies. Refer to the [Lighthouse Book](https://lighthouse-book.sigmaprime.io/installation-source.html#dependencies) for installation. We will also need [jq](https://jqlang.github.io/jq/), which can be installed with `sudo apt install jq`. + +Then, we clone the Lighthouse repository: +```bash +cd ~ +git clone https://github.com/sigp/lighthouse.git +cd lighthouse +``` +We are now ready to build Lighthouse. Run the command: ```bash make make install-lcli ``` +This will build `lighthouse` and `lcli`. For `geth` and `bootnode`, go to [geth website](https://geth.ethereum.org/downloads) and download the `Geth & Tools`. For example, to download and extract `Geth & Tools 1.13.1`: + +```bash +cd ~ +curl -LO https://gethstore.blob.core.windows.net/builds/geth-alltools-linux-amd64-1.13.1-3f40e65c.tar.gz +tar xvf geth-alltools-linux-amd64-1.13.1-3f40e65c.tar.gz +``` + +After extraction, copy `geth` and `bootnode` to the `PATH`. A typical directory is `/usr/local/bin`. + +```bash +cd geth-alltools-linux-amd64-1.13.1-3f40e65c +sudo cp geth bootnode /usr/local/bin +``` + +After that We can remove the downloaded files: + +```bash +cd ~ +rm -r geth-alltools-linux-amd64-1.13.1-3f40e65c geth-alltools-linux-amd64-1.13.1-3f40e65c.tar.gz +``` + +We are now ready to start a local testnet. + ## Starting the testnet -Modify `vars.env` as desired. +To start a testnet using the predetermined settings: + +```bash +cd ~ +cd ./lighthouse/scripts/local_testnet +./start_local_testnet.sh genesis.json +``` + +This will execute the script and if the testnet setup is successful, you will see "Started!" at the end. The testnet starts with a post-merge genesis state. -Start a consensus layer and execution layer boot node along with `BN_COUNT` -number of beacon nodes each connected to a geth execution client and `VC_COUNT` validator clients. +The testnet starts a consensus layer and execution layer boot node along with `BN_COUNT` +(the number of beacon nodes) each connected to a geth execution client and `VC_COUNT` (the number of validator clients). By default, `BN_COUNT=4`, `VC_COUNT=4`. The `start_local_testnet.sh` script takes four options `-v VC_COUNT`, `-d DEBUG_LEVEL`, `-p` to enable builder proposals and `-h` for help. It also takes a mandatory `GENESIS_FILE` for initialising geth's state. A sample `genesis.json` is provided in this directory. -The `ETH1_BLOCK_HASH` environment variable is set to the block_hash of the genesis execution layer block which depends on the contents of `genesis.json`. Users of these scripts need to ensure that the `ETH1_BLOCK_HASH` variable is updated if genesis file is modified. - The options may be in any order or absent in which case they take the default value specified. - VC_COUNT: the number of validator clients to create, default: `BN_COUNT` - DEBUG_LEVEL: one of { error, warn, info, debug, trace }, default: `info` +The `ETH1_BLOCK_HASH` environment variable is set to the block_hash of the genesis execution layer block which depends on the contents of `genesis.json`. Users of these scripts need to ensure that the `ETH1_BLOCK_HASH` variable is updated if genesis file is modified. +To view the beacon, validator client and geth logs: ```bash -./start_local_testnet.sh genesis.json +tail -f ~/.lighthouse/local-testnet/testnet/beacon_node_1.log +taif -f ~/.lighthouse/local-testnet/testnet/validator_node_1.log +tail -f ~/.lighthouse/local-testnet/testnet/geth_1.log ``` +where `beacon_node_1` can be changed to `beacon_node_2`, `beacon_node_3` or `beacon_node_4` to view logs for different beacon nodes. The same applies to validator clients and geth nodes. + ## Stopping the testnet -This is not necessary before `start_local_testnet.sh` as it invokes `stop_local_testnet.sh` automatically. +To stop the testnet, navigate to the directory `cd ~/lighthouse/scripts/local_testnet`, then run the command: + ```bash ./stop_local_testnet.sh ``` +Once a testnet is stopped, it cannot be continued from where it left off. When the start local testnet command is run, it will start a new local testnet. + ## Manual creation of local testnet -These scripts are used by ./start_local_testnet.sh and may be used to manually +In [Starting the testnet](./README.md#starting-the-testnet), the testnet is started automatically with predetermined parameters (database directory, ports used etc). This section describes some modifications of the local testnet settings, e.g., changing the database directory, or changing the ports used. -Assuming you are happy with the configuration in `vars.env`, -create the testnet directory, genesis state with embedded validators and validator keys with: + +The testnet also contains parameters that are specified in `vars.env`, such as the slot time `SECONDS_PER_SLOT=3` (instead of 12 seconds on mainnet). You may change these parameters to suit your testing purposes. After that, in the `local_testnet` directory, run the following command to create genesis state with embedded validators and validator keys, and also to update the time in `genesis.json`: ```bash ./setup.sh +./setup_time.sh genesis.json ``` Note: The generated genesis validators are embedded into the genesis state as genesis validators and hence do not require manual deposits to activate. @@ -73,17 +120,17 @@ Start a geth node: ``` e.g. ```bash -./geth.sh $HOME/.lighthouse/local-testnet/geth_1 5000 6000 7000 genesis.json +./geth.sh $HOME/.lighthouse/local-testnet/geth_1 7001 6001 5001 genesis.json ``` Start a beacon node: ```bash -./beacon_node.sh +./beacon_node.sh ``` e.g. ```bash -./beacon_node.sh $HOME/.lighthouse/local-testnet/node_1 9000 8000 http://localhost:6000 ~/.lighthouse/local-testnet/geth_1/geth/jwtsecret +./beacon_node.sh $HOME/.lighthouse/local-testnet/node_1 9001 9101 8001 http://localhost:5001 ~/.lighthouse/local-testnet/geth_1/geth/jwtsecret ``` In a new terminal, start the validator client which will attach to the first @@ -94,10 +141,16 @@ beacon node: ``` e.g. to attach to the above created beacon node ```bash -./validator_client.sh $HOME/.lighthouse/local-testnet/node_1 http://localhost:8000 +./validator_client.sh $HOME/.lighthouse/local-testnet/node_1 http://localhost:8001 ``` -You can create additional beacon node and validator client instances with appropriate parameters. +You can create additional geth, beacon node and validator client instances by changing the ports, e.g., for a second geth, beacon node and validator client: + +```bash +./geth.sh $HOME/.lighthouse/local-testnet/geth_2 7002 6002 5002 genesis.json +./beacon_node.sh $HOME/.lighthouse/local-testnet/node_2 9002 9102 8002 http://localhost:5002 ~/.lighthouse/local-testnet/geth_2/geth/jwtsecret +./validator_client.sh $HOME/.lighthouse/local-testnet/node_2 http://localhost:8002 +``` ## Additional Info @@ -109,7 +162,7 @@ instances using the `--datadir` parameter. ### Starting fresh -Delete the current testnet and all related files using. Generally not necessary as `start_local_test.sh` does this each time it starts. +You can delete the current testnet and all related files using the following command. Alternatively, if you wish to start another testnet, doing the steps [Starting the testnet](./README.md#starting-the-testnet) will automatically delete the files and start a fresh local testnet. ```bash ./clean.sh @@ -131,12 +184,12 @@ Update the genesis time to now using: ### Testing builder flow -1. Add builder URL to `BN_ARGS` in `./var.env`, e.g. `--builder http://localhost:8650`. Some mock builder server options: +1. Add builder URL to `BN_ARGS` in `./vars.env`, e.g. `--builder http://localhost:8650`. Some mock builder server options: - [`mock-relay`](https://github.com/realbigsean/mock-relay) - [`dummy-builder`](https://github.com/michaelsproul/dummy_builder) 2. (Optional) Add `--always-prefer-builder-payload` to `BN_ARGS`. 3. The above mock builders do not support non-mainnet presets as of now, and will require setting `SECONDS_PER_SLOT` and `SECONDS_PER_ETH1_BLOCK` to `12` in `./vars.env`. -4. Start the testnet with the following command (the `-p` flag enables the validator client `--builder-proposals` flag: +4. Start the testnet with the following command (the `-p` flag enables the validator client `--builder-proposals` flag): ```bash ./start_local_testnet.sh -p genesis.json ``` diff --git a/scripts/local_testnet/el_bootnode.sh b/scripts/local_testnet/el_bootnode.sh index d73a463f6d..ee437a491c 100755 --- a/scripts/local_testnet/el_bootnode.sh +++ b/scripts/local_testnet/el_bootnode.sh @@ -1,3 +1,3 @@ priv_key="02fd74636e96a8ffac8e7b01b0de8dea94d6bcf4989513b38cf59eb32163ff91" source ./vars.env -$EL_BOOTNODE_BINARY --nodekeyhex $priv_key \ No newline at end of file +exec $EL_BOOTNODE_BINARY --nodekeyhex $priv_key \ No newline at end of file diff --git a/scripts/local_testnet/setup.sh b/scripts/local_testnet/setup.sh index 7e000251a2..d7a6016aa8 100755 --- a/scripts/local_testnet/setup.sh +++ b/scripts/local_testnet/setup.sh @@ -32,7 +32,7 @@ lcli \ --ttd $TTD \ --eth1-block-hash $ETH1_BLOCK_HASH \ --eth1-id $CHAIN_ID \ - --eth1-follow-distance 1 \ + --eth1-follow-distance 128 \ --seconds-per-slot $SECONDS_PER_SLOT \ --seconds-per-eth1-block $SECONDS_PER_ETH1_BLOCK \ --proposer-score-boost "$PROPOSER_SCORE_BOOST" \ diff --git a/scripts/local_testnet/setup_time.sh b/scripts/local_testnet/setup_time.sh new file mode 100755 index 0000000000..21a8ae7ac1 --- /dev/null +++ b/scripts/local_testnet/setup_time.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash + +set -Eeuo pipefail + +source ./vars.env + +# Function to output SLOT_PER_EPOCH for mainnet or minimal +get_spec_preset_value() { + case "$SPEC_PRESET" in + mainnet) echo 32 ;; + minimal) echo 8 ;; + gnosis) echo 16 ;; + *) echo "Unsupported preset: $SPEC_PRESET" >&2; exit 1 ;; + esac +} + +SLOT_PER_EPOCH=$(get_spec_preset_value $SPEC_PRESET) +echo "slot_per_epoch=$SLOT_PER_EPOCH" + +genesis_file=$1 + +# Update future hardforks time in the EL genesis file based on the CL genesis time +GENESIS_TIME=$(lcli pretty-ssz --spec $SPEC_PRESET --testnet-dir $TESTNET_DIR BeaconState $TESTNET_DIR/genesis.ssz | jq | grep -Po 'genesis_time": "\K.*\d') +echo $GENESIS_TIME +CAPELLA_TIME=$((GENESIS_TIME + (CAPELLA_FORK_EPOCH * $SLOT_PER_EPOCH * SECONDS_PER_SLOT))) +echo $CAPELLA_TIME +sed -i 's/"shanghaiTime".*$/"shanghaiTime": '"$CAPELLA_TIME"',/g' $genesis_file +CANCUN_TIME=$((GENESIS_TIME + (DENEB_FORK_EPOCH * $SLOT_PER_EPOCH * SECONDS_PER_SLOT))) +echo $CANCUN_TIME +sed -i 's/"cancunTime".*$/"cancunTime": '"$CANCUN_TIME"',/g' $genesis_file +cat $genesis_file + diff --git a/scripts/local_testnet/start_local_testnet.sh b/scripts/local_testnet/start_local_testnet.sh index c796050bc4..512b1e98d1 100755 --- a/scripts/local_testnet/start_local_testnet.sh +++ b/scripts/local_testnet/start_local_testnet.sh @@ -102,16 +102,8 @@ execute_command_add_PID() { echo "executing: ./setup.sh >> $LOG_DIR/setup.log" ./setup.sh >> $LOG_DIR/setup.log 2>&1 -# Update future hardforks time in the EL genesis file based on the CL genesis time -GENESIS_TIME=$(lcli pretty-ssz --spec $SPEC_PRESET --testnet-dir $TESTNET_DIR BeaconState $TESTNET_DIR/genesis.ssz | jq | grep -Po 'genesis_time": "\K.*\d') -echo $GENESIS_TIME -CAPELLA_TIME=$((GENESIS_TIME + (CAPELLA_FORK_EPOCH * 32 * SECONDS_PER_SLOT))) -echo $CAPELLA_TIME -sed -i 's/"shanghaiTime".*$/"shanghaiTime": '"$CAPELLA_TIME"',/g' $genesis_file -CANCUN_TIME=$((GENESIS_TIME + (DENEB_FORK_EPOCH * 32 * SECONDS_PER_SLOT))) -echo $CANCUN_TIME -sed -i 's/"cancunTime".*$/"cancunTime": '"$CANCUN_TIME"',/g' $genesis_file -cat $genesis_file +# Call setup_time.sh to update future hardforks time in the EL genesis file based on the CL genesis time +./setup_time.sh genesis.json # Delay to let boot_enr.yaml to be created execute_command_add_PID bootnode.log ./bootnode.sh diff --git a/scripts/local_testnet/vars.env b/scripts/local_testnet/vars.env index d04a235497..31274d2c57 100644 --- a/scripts/local_testnet/vars.env +++ b/scripts/local_testnet/vars.env @@ -56,7 +56,7 @@ SPEC_PRESET=mainnet SECONDS_PER_SLOT=3 # Seconds per Eth1 block -SECONDS_PER_ETH1_BLOCK=1 +SECONDS_PER_ETH1_BLOCK=3 # Proposer score boost percentage PROPOSER_SCORE_BOOST=40 From 051c3e842fb5553d3ef3370560b74072c6e07cc6 Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Thu, 9 Nov 2023 16:51:36 +1100 Subject: [PATCH 10/23] Always use a separate database for blobs (#4892) * Always use a separate blobs DB * Add + update tests --- .../overflow_lru_cache.rs | 3 +- .../beacon_chain/tests/op_verification.rs | 3 +- beacon_node/beacon_chain/tests/store_tests.rs | 39 ++++++++++++- beacon_node/client/src/builder.rs | 2 +- beacon_node/client/src/config.rs | 21 ++++--- beacon_node/src/lib.rs | 2 +- beacon_node/store/src/hot_cold_store.rs | 58 ++++++++----------- beacon_node/store/src/metadata.rs | 2 +- database_manager/src/lib.rs | 12 ++-- 9 files changed, 89 insertions(+), 53 deletions(-) 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 e2eef45d25..6033293b82 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 @@ -771,12 +771,13 @@ mod test { ) -> Arc, LevelDB>> { let hot_path = db_path.path().join("hot_db"); let cold_path = db_path.path().join("cold_db"); + let blobs_path = db_path.path().join("blobs_db"); let config = StoreConfig::default(); HotColdDB::open( &hot_path, &cold_path, - None, + &blobs_path, |_, _, _| Ok(()), config, spec, diff --git a/beacon_node/beacon_chain/tests/op_verification.rs b/beacon_node/beacon_chain/tests/op_verification.rs index f4af490710..f6cf40a396 100644 --- a/beacon_node/beacon_chain/tests/op_verification.rs +++ b/beacon_node/beacon_chain/tests/op_verification.rs @@ -29,12 +29,13 @@ fn get_store(db_path: &TempDir) -> Arc { let spec = test_spec::(); let hot_path = db_path.path().join("hot_db"); let cold_path = db_path.path().join("cold_db"); + let blobs_path = db_path.path().join("blobs_db"); let config = StoreConfig::default(); let log = NullLoggerBuilder.build().expect("logger should build"); HotColdDB::open( &hot_path, &cold_path, - None, + &blobs_path, |_, _, _| Ok(()), config, spec, diff --git a/beacon_node/beacon_chain/tests/store_tests.rs b/beacon_node/beacon_chain/tests/store_tests.rs index d3f6428851..c5866ae1e0 100644 --- a/beacon_node/beacon_chain/tests/store_tests.rs +++ b/beacon_node/beacon_chain/tests/store_tests.rs @@ -31,7 +31,7 @@ use store::{ chunked_vector::{chunk_key, Field}, get_key_for_col, iter::{BlockRootsIterator, StateRootsIterator}, - DBColumn, HotColdDB, KeyValueStore, KeyValueStoreOp, LevelDB, StoreConfig, + BlobInfo, DBColumn, HotColdDB, KeyValueStore, KeyValueStoreOp, LevelDB, StoreConfig, }; use tempfile::{tempdir, TempDir}; use tokio::time::sleep; @@ -62,12 +62,13 @@ fn get_store_generic( ) -> Arc, LevelDB>> { let hot_path = db_path.path().join("hot_db"); let cold_path = db_path.path().join("cold_db"); + let blobs_path = db_path.path().join("blobs_db"); let log = test_logger(); HotColdDB::open( &hot_path, &cold_path, - None, + &blobs_path, |_, _, _| Ok(()), config, spec, @@ -3278,6 +3279,40 @@ async fn deneb_prune_blobs_margin_test(margin: u64) { check_blob_existence(&harness, oldest_blob_slot, harness.head_slot(), true); } +/// Check that a database with `blobs_db=false` can be upgraded to `blobs_db=true` before Deneb. +#[tokio::test] +async fn change_to_separate_blobs_db_before_deneb() { + let db_path = tempdir().unwrap(); + let store = get_store(&db_path); + + // Only run this test on forks prior to Deneb. If the blobs database already has blobs, we can't + // move it. + if store.get_chain_spec().deneb_fork_epoch.is_some() { + return; + } + + let init_blob_info = store.get_blob_info(); + assert!( + init_blob_info.blobs_db, + "separate blobs DB should be the default" + ); + + // Change to `blobs_db=false` to emulate legacy Deneb DB. + let legacy_blob_info = BlobInfo { + blobs_db: false, + ..init_blob_info + }; + store + .compare_and_set_blob_info_with_write(init_blob_info.clone(), legacy_blob_info.clone()) + .unwrap(); + assert_eq!(store.get_blob_info(), legacy_blob_info); + + // Re-open the DB and check that `blobs_db` gets changed back to true. + drop(store); + let store = get_store(&db_path); + assert_eq!(store.get_blob_info(), init_blob_info); +} + /// Check that there are blob sidecars (or not) at every slot in the range. fn check_blob_existence( harness: &TestHarness, diff --git a/beacon_node/client/src/builder.rs b/beacon_node/client/src/builder.rs index fe8a56084d..cedf347b9a 100644 --- a/beacon_node/client/src/builder.rs +++ b/beacon_node/client/src/builder.rs @@ -901,7 +901,7 @@ where mut self, hot_path: &Path, cold_path: &Path, - blobs_path: Option, + blobs_path: &Path, config: StoreConfig, log: Logger, ) -> Result { diff --git a/beacon_node/client/src/config.rs b/beacon_node/client/src/config.rs index 492431cd36..20afdb948b 100644 --- a/beacon_node/client/src/config.rs +++ b/beacon_node/client/src/config.rs @@ -10,8 +10,11 @@ use std::fs; use std::path::PathBuf; use std::time::Duration; use types::Graffiti; + /// Default directory name for the freezer database under the top-level data dir. const DEFAULT_FREEZER_DB_DIR: &str = "freezer_db"; +/// Default directory name for the blobs database under the top-level data dir. +const DEFAULT_BLOBS_DB_DIR: &str = "blobs_db"; /// Defines how the client should initialize the `BeaconChain` and other components. #[derive(Debug, Clone, Serialize, Deserialize, Default)] @@ -146,12 +149,19 @@ impl Config { .unwrap_or_else(|| self.default_freezer_db_path()) } + /// Fetch default path to use for the blobs database. + fn default_blobs_db_path(&self) -> PathBuf { + self.get_data_dir().join(DEFAULT_BLOBS_DB_DIR) + } + /// Returns the path to which the client may initialize the on-disk blobs database. /// /// Will attempt to use the user-supplied path from e.g. the CLI, or will default /// to None. - pub fn get_blobs_db_path(&self) -> Option { - self.blobs_db_path.clone() + pub fn get_blobs_db_path(&self) -> PathBuf { + self.blobs_db_path + .clone() + .unwrap_or_else(|| self.default_blobs_db_path()) } /// Get the freezer DB path, creating it if necessary. @@ -160,11 +170,8 @@ impl Config { } /// Get the blobs DB path, creating it if necessary. - pub fn create_blobs_db_path(&self) -> Result, String> { - match self.get_blobs_db_path() { - Some(blobs_db_path) => Ok(Some(ensure_dir_exists(blobs_db_path)?)), - None => Ok(None), - } + pub fn create_blobs_db_path(&self) -> Result { + ensure_dir_exists(self.get_blobs_db_path()) } /// Returns the "modern" path to the data_dir. diff --git a/beacon_node/src/lib.rs b/beacon_node/src/lib.rs index 085a2a7824..cf6d627c30 100644 --- a/beacon_node/src/lib.rs +++ b/beacon_node/src/lib.rs @@ -89,7 +89,7 @@ impl ProductionBeaconNode { .disk_store( &db_path, &freezer_db_path, - blobs_db_path, + &blobs_db_path, store_config, log.clone(), )?; diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index 6e2a2ae583..43e14c3097 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -35,7 +35,7 @@ use state_processing::{ use std::cmp::min; use std::convert::TryInto; use std::marker::PhantomData; -use std::path::{Path, PathBuf}; +use std::path::Path; use std::sync::Arc; use std::time::Duration; use types::blob_sidecar::BlobSidecarList; @@ -61,7 +61,7 @@ pub struct HotColdDB, Cold: ItemStore> { /// Cold database containing compact historical data. pub cold_db: Cold, /// Database containing blobs. If None, store falls back to use `cold_db`. - pub blobs_db: Option, + pub blobs_db: Cold, /// Hot database containing duplicated but quick-to-access recent data. /// /// The hot database also contains all blocks. @@ -138,7 +138,6 @@ pub enum HotColdDBError { MissingExecutionPayload(Hash256), MissingFullBlockExecutionPayloadPruned(Hash256, Slot), MissingAnchorInfo, - MissingPathToBlobsDatabase, BlobsPreviouslyInDefaultStore, HotStateSummaryError(BeaconStateError), RestorePointDecodeError(ssz::DecodeError), @@ -178,7 +177,7 @@ impl HotColdDB, MemoryStore> { anchor_info: RwLock::new(None), blob_info: RwLock::new(BlobInfo::default()), cold_db: MemoryStore::open(), - blobs_db: Some(MemoryStore::open()), + blobs_db: MemoryStore::open(), hot_db: MemoryStore::open(), block_cache: Mutex::new(BlockCache::new(config.block_cache_size)), state_cache: Mutex::new(LruCache::new(config.historic_state_cache_size)), @@ -202,7 +201,7 @@ impl HotColdDB, LevelDB> { pub fn open( hot_path: &Path, cold_path: &Path, - blobs_db_path: Option, + blobs_db_path: &Path, migrate_schema: impl FnOnce(Arc, SchemaVersion, SchemaVersion) -> Result<(), Error>, config: StoreConfig, spec: ChainSpec, @@ -215,7 +214,7 @@ impl HotColdDB, LevelDB> { anchor_info: RwLock::new(None), blob_info: RwLock::new(BlobInfo::default()), cold_db: LevelDB::open(cold_path)?, - blobs_db: None, + blobs_db: LevelDB::open(blobs_db_path)?, hot_db: LevelDB::open(hot_path)?, block_cache: Mutex::new(BlockCache::new(config.block_cache_size)), state_cache: Mutex::new(LruCache::new(config.historic_state_cache_size)), @@ -271,37 +270,29 @@ impl HotColdDB, LevelDB> { Some(blob_info) => { // If the oldest block slot is already set do not allow the blob DB path to be // changed (require manual migration). - if blob_info.oldest_blob_slot.is_some() { - if blobs_db_path.is_some() && !blob_info.blobs_db { - return Err(HotColdDBError::BlobsPreviouslyInDefaultStore.into()); - } else if blobs_db_path.is_none() && blob_info.blobs_db { - return Err(HotColdDBError::MissingPathToBlobsDatabase.into()); - } + if blob_info.oldest_blob_slot.is_some() && !blob_info.blobs_db { + return Err(HotColdDBError::BlobsPreviouslyInDefaultStore.into()); } // Set the oldest blob slot to the Deneb fork slot if it is not yet set. + // Always initialize `blobs_db` to true, we no longer support storing the blobs + // in the freezer DB, because the UX is strictly worse for relocating the DB. let oldest_blob_slot = blob_info.oldest_blob_slot.or(deneb_fork_slot); BlobInfo { oldest_blob_slot, - blobs_db: blobs_db_path.is_some(), + blobs_db: true, } } // First start. None => BlobInfo { // Set the oldest blob slot to the Deneb fork slot if it is not yet set. oldest_blob_slot: deneb_fork_slot, - blobs_db: blobs_db_path.is_some(), + blobs_db: true, }, }; - if new_blob_info.blobs_db { - if let Some(path) = &blobs_db_path { - db.blobs_db = Some(LevelDB::open(path.as_path())?); - } - } db.compare_and_set_blob_info_with_write(<_>::default(), new_blob_info.clone())?; info!( db.log, "Blob DB initialized"; - "separate_db" => new_blob_info.blobs_db, "path" => ?blobs_db_path, "oldest_blob_slot" => ?new_blob_info.oldest_blob_slot, ); @@ -575,8 +566,8 @@ impl, Cold: ItemStore> HotColdDB /// Check if the blobs for a block exists on disk. pub fn blobs_exist(&self, block_root: &Hash256) -> Result { - let blobs_db = self.blobs_db.as_ref().unwrap_or(&self.cold_db); - blobs_db.key_exists(DBColumn::BeaconBlob.into(), block_root.as_bytes()) + self.blobs_db + .key_exists(DBColumn::BeaconBlob.into(), block_root.as_bytes()) } /// Determine whether a block exists in the database. @@ -592,13 +583,12 @@ impl, Cold: ItemStore> HotColdDB .key_delete(DBColumn::BeaconBlock.into(), block_root.as_bytes())?; self.hot_db .key_delete(DBColumn::ExecPayload.into(), block_root.as_bytes())?; - let blobs_db = self.blobs_db.as_ref().unwrap_or(&self.cold_db); - blobs_db.key_delete(DBColumn::BeaconBlob.into(), block_root.as_bytes()) + self.blobs_db + .key_delete(DBColumn::BeaconBlob.into(), block_root.as_bytes()) } pub fn put_blobs(&self, block_root: &Hash256, blobs: BlobSidecarList) -> Result<(), Error> { - let blobs_db = self.blobs_db.as_ref().unwrap_or(&self.cold_db); - blobs_db.put_bytes( + self.blobs_db.put_bytes( DBColumn::BeaconBlob.into(), block_root.as_bytes(), &blobs.as_ssz_bytes(), @@ -988,9 +978,9 @@ impl, Cold: ItemStore> HotColdDB let mut guard = self.block_cache.lock(); let blob_cache_ops = blobs_ops.clone(); - let blobs_db = self.blobs_db.as_ref().unwrap_or(&self.cold_db); // Try to execute blobs store ops. - blobs_db.do_atomically(self.convert_to_kv_batch(blobs_ops)?)?; + self.blobs_db + .do_atomically(self.convert_to_kv_batch(blobs_ops)?)?; let hot_db_cache_ops = hot_db_ops.clone(); // Try to execute hot db store ops. @@ -1018,7 +1008,8 @@ impl, Cold: ItemStore> HotColdDB }; *op = reverse_op; } - blobs_db.do_atomically(self.convert_to_kv_batch(blob_cache_ops)?)?; + self.blobs_db + .do_atomically(self.convert_to_kv_batch(blob_cache_ops)?)?; return Err(e); } @@ -1436,15 +1427,16 @@ impl, Cold: ItemStore> HotColdDB /// Fetch blobs for a given block from the store. pub fn get_blobs(&self, block_root: &Hash256) -> Result>, Error> { - let blobs_db = self.blobs_db.as_ref().unwrap_or(&self.cold_db); - // Check the cache. if let Some(blobs) = self.block_cache.lock().get_blobs(block_root) { metrics::inc_counter(&metrics::BEACON_BLOBS_CACHE_HIT_COUNT); return Ok(Some(blobs.clone())); } - match blobs_db.get_bytes(DBColumn::BeaconBlob.into(), block_root.as_bytes())? { + match self + .blobs_db + .get_bytes(DBColumn::BeaconBlob.into(), block_root.as_bytes())? + { Some(ref blobs_bytes) => { let blobs = BlobSidecarList::from_ssz_bytes(blobs_bytes)?; self.block_cache @@ -1640,7 +1632,7 @@ impl, Cold: ItemStore> HotColdDB }); let blob_info = BlobInfo { oldest_blob_slot, - blobs_db: self.blobs_db.is_some(), + blobs_db: true, }; self.compare_and_set_blob_info(self.get_blob_info(), blob_info) } diff --git a/beacon_node/store/src/metadata.rs b/beacon_node/store/src/metadata.rs index 124c9a2f58..6fef74d7ff 100644 --- a/beacon_node/store/src/metadata.rs +++ b/beacon_node/store/src/metadata.rs @@ -135,7 +135,7 @@ pub struct BlobInfo { /// If the `oldest_blob_slot` is `None` then this means that the Deneb fork epoch is not yet /// known. pub oldest_blob_slot: Option, - /// A separate blobs database is in use. + /// A separate blobs database is in use (deprecated, always `true`). pub blobs_db: bool, } diff --git a/database_manager/src/lib.rs b/database_manager/src/lib.rs index 93654b8dd5..95af4d6382 100644 --- a/database_manager/src/lib.rs +++ b/database_manager/src/lib.rs @@ -210,7 +210,7 @@ pub fn display_db_version( HotColdDB::, LevelDB>::open( &hot_path, &cold_path, - blobs_path, + &blobs_path, |_, from, _| { version = from; Ok(()) @@ -288,7 +288,7 @@ pub fn inspect_db( let db = HotColdDB::, LevelDB>::open( &hot_path, &cold_path, - blobs_path, + &blobs_path, |_, _, _| Ok(()), client_config.store, spec, @@ -410,7 +410,7 @@ pub fn migrate_db( let db = HotColdDB::, LevelDB>::open( &hot_path, &cold_path, - blobs_path, + &blobs_path, |_, db_initial_version, _| { from = db_initial_version; Ok(()) @@ -450,7 +450,7 @@ pub fn prune_payloads( let db = HotColdDB::, LevelDB>::open( &hot_path, &cold_path, - blobs_path, + &blobs_path, |_, _, _| Ok(()), client_config.store, spec.clone(), @@ -476,7 +476,7 @@ pub fn prune_blobs( let db = HotColdDB::, LevelDB>::open( &hot_path, &cold_path, - blobs_path, + &blobs_path, |_, _, _| Ok(()), client_config.store, spec.clone(), @@ -512,7 +512,7 @@ pub fn prune_states( let db = HotColdDB::, LevelDB>::open( &hot_path, &cold_path, - blobs_path, + &blobs_path, |_, _, _| Ok(()), client_config.store, spec.clone(), From e181741d38875da189bc874b6091a081706c4544 Mon Sep 17 00:00:00 2001 From: Zackary Scott <106991885+zack-scott@users.noreply.github.com> Date: Thu, 16 Nov 2023 05:07:48 -0600 Subject: [PATCH 11/23] Fix for issue 4860 - Added in process_justification_and_finalization (#4877) * Added in process_justification_and_finalization Added in process_justification_and_finalization to compute_attestation_rewards_altair to take into account justified attestations when coming out of inactivity leak. Also added in test to check for this edge case. * Added in justification and finalization for compute_attestation_rewards_base * Added in test for altair rewards without inactivity leak --- .../beacon_chain/src/attestation_rewards.rs | 14 +- beacon_node/beacon_chain/tests/rewards.rs | 259 ++++++++++++++++++ 2 files changed, 272 insertions(+), 1 deletion(-) diff --git a/beacon_node/beacon_chain/src/attestation_rewards.rs b/beacon_node/beacon_chain/src/attestation_rewards.rs index 992c7a479e..abd676d738 100644 --- a/beacon_node/beacon_chain/src/attestation_rewards.rs +++ b/beacon_node/beacon_chain/src/attestation_rewards.rs @@ -5,7 +5,9 @@ use participation_cache::ParticipationCache; use safe_arith::SafeArith; use serde_utils::quoted_u64::Quoted; use slog::debug; -use state_processing::per_epoch_processing::altair::process_inactivity_updates; +use state_processing::per_epoch_processing::altair::{ + process_inactivity_updates, process_justification_and_finalization, +}; use state_processing::{ common::altair::BaseRewardPerIncrement, per_epoch_processing::altair::{participation_cache, rewards_and_penalties::get_flag_weight}, @@ -27,6 +29,7 @@ use state_processing::per_epoch_processing::base::rewards_and_penalties::{ }; use state_processing::per_epoch_processing::base::validator_statuses::InclusionInfo; use state_processing::per_epoch_processing::base::{ + process_justification_and_finalization as process_justification_and_finalization_base, TotalBalances, ValidatorStatus, ValidatorStatuses, }; @@ -67,6 +70,13 @@ impl BeaconChain { let mut validator_statuses = ValidatorStatuses::new(&state, spec)?; validator_statuses.process_attestations(&state)?; + process_justification_and_finalization_base( + &state, + &validator_statuses.total_balances, + spec, + )? + .apply_changes_to_state(&mut state); + let ideal_rewards = self.compute_ideal_rewards_base(&state, &validator_statuses.total_balances)?; @@ -125,6 +135,8 @@ impl BeaconChain { // Calculate ideal_rewards let participation_cache = ParticipationCache::new(&state, spec)?; + process_justification_and_finalization(&state, &participation_cache)? + .apply_changes_to_state(&mut state); process_inactivity_updates(&mut state, &participation_cache, spec)?; let previous_epoch = state.previous_epoch(); diff --git a/beacon_node/beacon_chain/tests/rewards.rs b/beacon_node/beacon_chain/tests/rewards.rs index 7c8f01cf55..a78463ef5d 100644 --- a/beacon_node/beacon_chain/tests/rewards.rs +++ b/beacon_node/beacon_chain/tests/rewards.rs @@ -219,6 +219,156 @@ async fn test_verify_attestation_rewards_base_inactivity_leak() { assert_eq!(expected_balances, balances); } +#[tokio::test] +async fn test_verify_attestation_rewards_base_inactivity_leak_justification_epoch() { + 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 mut target_epoch = &spec.min_epochs_to_inactivity_penalty + 2; + + // advance until beginning of epoch N + 2 + harness + .extend_chain( + (E::slots_per_epoch() * (target_epoch + 1)) as usize, + BlockStrategy::OnCanonicalHead, + AttestationStrategy::SomeValidators(half_validators.clone()), + ) + .await; + + // advance to create first justification epoch and get initial balances + harness.extend_slots(E::slots_per_epoch() as usize).await; + target_epoch += 1; + let initial_balances: Vec = harness.get_current_state().balances().clone().into(); + + //assert previous_justified_checkpoint matches 0 as we were in inactivity leak from beginning + assert_eq!( + 0, + harness + .get_current_state() + .previous_justified_checkpoint() + .epoch + .as_u64() + ); + + // extend slots to beginning of epoch N + 1 + harness.extend_slots(E::slots_per_epoch() as usize).await; + + //assert target epoch and previous_justified_checkpoint match + assert_eq!( + target_epoch, + harness + .get_current_state() + .previous_justified_checkpoint() + .epoch + .as_u64() + ); + + // 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 we successfully get ideal rewards for justified epoch out of inactivity leak + assert!(ideal_rewards + .iter() + .all(|reward| reward.head > 0 && reward.target > 0 && reward.source > 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_altair() { + let spec = ForkName::Altair.make_genesis_spec(E::default_spec()); + let harness = get_harness(spec.clone()); + let target_epoch = 0; + + // advance until epoch N + 1 and get initial balances + harness + .extend_slots((E::slots_per_epoch() * (target_epoch + 1)) as usize) + .await; + let initial_balances: Vec = harness.get_current_state().balances().clone().into(); + + // advance until epoch N + 2 and build proposal rewards map + let mut proposal_rewards_map: HashMap = HashMap::new(); + let mut sync_committee_rewards_map: HashMap = HashMap::new(); + for _ in 0..E::slots_per_epoch() { + let state = harness.get_current_state(); + let slot = state.slot() + Slot::new(1); + + // calculate beacon block rewards / penalties + let ((signed_block, _maybe_blob_sidecars), mut state) = + harness.make_block_return_pre_state(state, slot).await; + let beacon_block_reward = harness + .chain + .compute_beacon_block_reward( + signed_block.message(), + signed_block.canonical_root(), + &mut state, + ) + .unwrap(); + + let total_proposer_reward = proposal_rewards_map + .get(&beacon_block_reward.proposer_index) + .unwrap_or(&0u64) + + beacon_block_reward.total; + + proposal_rewards_map.insert(beacon_block_reward.proposer_index, total_proposer_reward); + + // calculate sync committee rewards / penalties + let reward_payload = harness + .chain + .compute_sync_committee_rewards(signed_block.message(), &mut state) + .unwrap(); + + reward_payload.iter().for_each(|reward| { + let mut amount = *sync_committee_rewards_map + .get(&reward.validator_index) + .unwrap_or(&0); + amount += reward.reward; + sync_committee_rewards_map.insert(reward.validator_index, amount); + }); + + harness.extend_slots(1).await; + } + + // 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 ideal rewards are greater than 0 + assert!(ideal_rewards + .iter() + .all(|reward| reward.head > 0 && reward.target > 0 && reward.source > 0)); + + // apply attestation, proposal, and sync committee rewards and penalties to initial balances + let expected_balances = apply_attestation_rewards(&initial_balances, total_rewards); + let expected_balances = apply_beacon_block_rewards(&proposal_rewards_map, expected_balances); + let expected_balances = + apply_sync_committee_rewards(&sync_committee_rewards_map, expected_balances); + + // 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_altair_inactivity_leak() { let spec = ForkName::Altair.make_genesis_spec(E::default_spec()); @@ -313,6 +463,115 @@ async fn test_verify_attestation_rewards_altair_inactivity_leak() { assert_eq!(expected_balances, balances); } +#[tokio::test] +async fn test_verify_attestation_rewards_altair_inactivity_leak_justification_epoch() { + let spec = ForkName::Altair.make_genesis_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 + 1 + let mut target_epoch = &spec.min_epochs_to_inactivity_penalty + 2; + + // advance until beginning of epoch N + 1 + harness + .extend_slots_some_validators( + (E::slots_per_epoch() * (target_epoch + 1)) as usize, + half_validators.clone(), + ) + .await; + + let validator_inactivity_score = harness + .get_current_state() + .get_inactivity_score(VALIDATOR_COUNT - 1) + .unwrap(); + + //assert to ensure we are in inactivity leak + assert_eq!(4, validator_inactivity_score); + + // advance for first justification epoch and get balances + harness.extend_slots(E::slots_per_epoch() as usize).await; + target_epoch += 1; + let initial_balances: Vec = harness.get_current_state().balances().clone().into(); + + // advance until epoch N + 2 and build proposal rewards map + let mut proposal_rewards_map: HashMap = HashMap::new(); + let mut sync_committee_rewards_map: HashMap = HashMap::new(); + for _ in 0..E::slots_per_epoch() { + let state = harness.get_current_state(); + let slot = state.slot() + Slot::new(1); + + // calculate beacon block rewards / penalties + let ((signed_block, _maybe_blob_sidecars), mut state) = + harness.make_block_return_pre_state(state, slot).await; + let beacon_block_reward = harness + .chain + .compute_beacon_block_reward( + signed_block.message(), + signed_block.canonical_root(), + &mut state, + ) + .unwrap(); + + let total_proposer_reward = proposal_rewards_map + .get(&beacon_block_reward.proposer_index) + .unwrap_or(&0u64) + + beacon_block_reward.total; + + proposal_rewards_map.insert(beacon_block_reward.proposer_index, total_proposer_reward); + + // calculate sync committee rewards / penalties + let reward_payload = harness + .chain + .compute_sync_committee_rewards(signed_block.message(), &mut state) + .unwrap(); + + reward_payload.iter().for_each(|reward| { + let mut amount = *sync_committee_rewards_map + .get(&reward.validator_index) + .unwrap_or(&0); + amount += reward.reward; + sync_committee_rewards_map.insert(reward.validator_index, amount); + }); + + harness.extend_slots(1).await; + } + + //assert target epoch and previous_justified_checkpoint match + assert_eq!( + target_epoch, + harness + .get_current_state() + .previous_justified_checkpoint() + .epoch + .as_u64() + ); + + // 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 ideal rewards are greater than 0 + assert!(ideal_rewards + .iter() + .all(|reward| reward.head > 0 && reward.target > 0 && reward.source > 0)); + + // apply attestation, proposal, and sync committee rewards and penalties to initial balances + let expected_balances = apply_attestation_rewards(&initial_balances, total_rewards); + let expected_balances = apply_beacon_block_rewards(&proposal_rewards_map, expected_balances); + let expected_balances = + apply_sync_committee_rewards(&sync_committee_rewards_map, expected_balances); + + // 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()); From d04e361129e6f7fb2ad6fce32c87f782d9ed627e Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Thu, 16 Nov 2023 14:07:58 +0300 Subject: [PATCH 12/23] Fix missed block logs (#4922) --- .../beacon_chain/src/validator_monitor.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/beacon_node/beacon_chain/src/validator_monitor.rs b/beacon_node/beacon_chain/src/validator_monitor.rs index 015cdb1c26..8cea9c0769 100644 --- a/beacon_node/beacon_chain/src/validator_monitor.rs +++ b/beacon_node/beacon_chain/src/validator_monitor.rs @@ -621,19 +621,19 @@ impl ValidatorMonitor { "parent block root" => ?prev_block_root, ); } - } else { - warn!( - self.log, - "Missing validator index"; - "info" => "potentially inconsistency in the validator manager", - "index" => i, - ) } + } else { + warn!( + self.log, + "Missing validator index"; + "info" => "potentially inconsistency in the validator manager", + "index" => i, + ) } } else { debug!( self.log, - "Could not get proposers for from cache"; + "Could not get proposers from cache"; "epoch" => ?slot_epoch ); } From 68e076d60a7d247193a3129500f13c74b5cb73b8 Mon Sep 17 00:00:00 2001 From: Mac L Date: Thu, 16 Nov 2023 22:09:05 +1100 Subject: [PATCH 13/23] Remove legacy database migrations (#4919) * Remove legacy db migrations * Fix tests * Remove migrations to/from v15 * Remove v16 migrations --- beacon_node/beacon_chain/src/schema_change.rs | 98 +------- .../src/schema_change/migration_schema_v12.rs | 222 ------------------ .../src/schema_change/migration_schema_v13.rs | 150 ------------ .../src/schema_change/migration_schema_v14.rs | 118 ---------- .../src/schema_change/migration_schema_v15.rs | 74 ------ .../src/schema_change/migration_schema_v16.rs | 46 ---- beacon_node/beacon_chain/tests/store_tests.rs | 4 +- 7 files changed, 5 insertions(+), 707 deletions(-) delete mode 100644 beacon_node/beacon_chain/src/schema_change/migration_schema_v12.rs delete mode 100644 beacon_node/beacon_chain/src/schema_change/migration_schema_v13.rs delete mode 100644 beacon_node/beacon_chain/src/schema_change/migration_schema_v14.rs delete mode 100644 beacon_node/beacon_chain/src/schema_change/migration_schema_v15.rs delete mode 100644 beacon_node/beacon_chain/src/schema_change/migration_schema_v16.rs diff --git a/beacon_node/beacon_chain/src/schema_change.rs b/beacon_node/beacon_chain/src/schema_change.rs index 88b5682505..e42ee20c48 100644 --- a/beacon_node/beacon_chain/src/schema_change.rs +++ b/beacon_node/beacon_chain/src/schema_change.rs @@ -1,20 +1,14 @@ //! Utilities for managing database schema changes. -mod migration_schema_v12; -mod migration_schema_v13; -mod migration_schema_v14; -mod migration_schema_v15; -mod migration_schema_v16; mod migration_schema_v17; mod migration_schema_v18; -use crate::beacon_chain::{BeaconChainTypes, ETH1_CACHE_DB_KEY}; -use crate::eth1_chain::SszEth1; +use crate::beacon_chain::BeaconChainTypes; use crate::types::ChainSpec; -use slog::{warn, Logger}; +use slog::Logger; use std::sync::Arc; use store::hot_cold_store::{HotColdDB, HotColdDBError}; use store::metadata::{SchemaVersion, CURRENT_SCHEMA_VERSION}; -use store::{Error as StoreError, StoreItem}; +use store::Error as StoreError; /// Migrate the database from one schema version to another, applying all requisite mutations. #[allow(clippy::only_used_in_recursion)] // spec is not used but likely to be used in future @@ -57,92 +51,8 @@ pub fn migrate_schema( } // - // Migrations from before SchemaVersion(11) are deprecated. + // Migrations from before SchemaVersion(16) are deprecated. // - - // Upgrade from v11 to v12 to store richer metadata in the attestation op pool. - (SchemaVersion(11), SchemaVersion(12)) => { - let ops = migration_schema_v12::upgrade_to_v12::(db.clone(), log)?; - db.store_schema_version_atomically(to, ops) - } - // Downgrade from v12 to v11 to drop richer metadata from the attestation op pool. - (SchemaVersion(12), SchemaVersion(11)) => { - let ops = migration_schema_v12::downgrade_from_v12::(db.clone(), log)?; - db.store_schema_version_atomically(to, ops) - } - (SchemaVersion(12), SchemaVersion(13)) => { - let mut ops = vec![]; - if let Some(persisted_eth1_v1) = db.get_item::(Ð1_CACHE_DB_KEY)? { - let upgraded_eth1_cache = - match migration_schema_v13::update_eth1_cache(persisted_eth1_v1) { - Ok(upgraded_eth1) => upgraded_eth1, - Err(e) => { - warn!(log, "Failed to deserialize SszEth1CacheV1"; "error" => ?e); - warn!(log, "Reinitializing eth1 cache"); - migration_schema_v13::reinitialized_eth1_cache_v13( - deposit_contract_deploy_block, - ) - } - }; - ops.push(upgraded_eth1_cache.as_kv_store_op(ETH1_CACHE_DB_KEY)); - } - - db.store_schema_version_atomically(to, ops)?; - - Ok(()) - } - (SchemaVersion(13), SchemaVersion(12)) => { - let mut ops = vec![]; - if let Some(persisted_eth1_v13) = db.get_item::(Ð1_CACHE_DB_KEY)? { - let downgraded_eth1_cache = match migration_schema_v13::downgrade_eth1_cache( - persisted_eth1_v13, - ) { - Ok(Some(downgraded_eth1)) => downgraded_eth1, - Ok(None) => { - warn!(log, "Unable to downgrade eth1 cache from newer version: reinitializing eth1 cache"); - migration_schema_v13::reinitialized_eth1_cache_v1( - deposit_contract_deploy_block, - ) - } - Err(e) => { - warn!(log, "Unable to downgrade eth1 cache from newer version: failed to deserialize SszEth1CacheV13"; "error" => ?e); - warn!(log, "Reinitializing eth1 cache"); - migration_schema_v13::reinitialized_eth1_cache_v1( - deposit_contract_deploy_block, - ) - } - }; - ops.push(downgraded_eth1_cache.as_kv_store_op(ETH1_CACHE_DB_KEY)); - } - - db.store_schema_version_atomically(to, ops)?; - - Ok(()) - } - (SchemaVersion(13), SchemaVersion(14)) => { - let ops = migration_schema_v14::upgrade_to_v14::(db.clone(), log)?; - db.store_schema_version_atomically(to, ops) - } - (SchemaVersion(14), SchemaVersion(13)) => { - let ops = migration_schema_v14::downgrade_from_v14::(db.clone(), log)?; - db.store_schema_version_atomically(to, ops) - } - (SchemaVersion(14), SchemaVersion(15)) => { - let ops = migration_schema_v15::upgrade_to_v15::(db.clone(), log)?; - db.store_schema_version_atomically(to, ops) - } - (SchemaVersion(15), SchemaVersion(14)) => { - let ops = migration_schema_v15::downgrade_from_v15::(db.clone(), log)?; - db.store_schema_version_atomically(to, ops) - } - (SchemaVersion(15), SchemaVersion(16)) => { - let ops = migration_schema_v16::upgrade_to_v16::(db.clone(), log)?; - db.store_schema_version_atomically(to, ops) - } - (SchemaVersion(16), SchemaVersion(15)) => { - let ops = migration_schema_v16::downgrade_from_v16::(db.clone(), log)?; - db.store_schema_version_atomically(to, ops) - } (SchemaVersion(16), SchemaVersion(17)) => { let ops = migration_schema_v17::upgrade_to_v17::(db.clone(), log)?; db.store_schema_version_atomically(to, ops) diff --git a/beacon_node/beacon_chain/src/schema_change/migration_schema_v12.rs b/beacon_node/beacon_chain/src/schema_change/migration_schema_v12.rs deleted file mode 100644 index ab267796e1..0000000000 --- a/beacon_node/beacon_chain/src/schema_change/migration_schema_v12.rs +++ /dev/null @@ -1,222 +0,0 @@ -use crate::beacon_chain::{BeaconChainTypes, FORK_CHOICE_DB_KEY, OP_POOL_DB_KEY}; -use crate::persisted_fork_choice::PersistedForkChoiceV11; -use operation_pool::{PersistedOperationPool, PersistedOperationPoolV12, PersistedOperationPoolV5}; -use slog::{debug, info, Logger}; -use state_processing::{ - common::get_indexed_attestation, per_block_processing::is_valid_indexed_attestation, - VerifyOperation, VerifySignatures, -}; -use std::sync::Arc; -use store::{Error, HotColdDB, KeyValueStoreOp, StoreItem}; - -pub fn upgrade_to_v12( - db: Arc>, - log: Logger, -) -> Result, Error> { - let spec = db.get_chain_spec(); - - // Load a V5 op pool and transform it to V12. - let Some(PersistedOperationPoolV5 { - attestations_v5, - sync_contributions, - attester_slashings_v5, - proposer_slashings_v5, - voluntary_exits_v5, - }) = db.get_item(&OP_POOL_DB_KEY)? - else { - debug!(log, "Nothing to do, no operation pool stored"); - return Ok(vec![]); - }; - - // Load the persisted fork choice so we can grab the state of the justified block and use - // it to verify the stored attestations, slashings and exits. - let fork_choice = db - .get_item::(&FORK_CHOICE_DB_KEY)? - .ok_or_else(|| Error::SchemaMigrationError("fork choice missing from database".into()))?; - let justified_block_root = fork_choice - .fork_choice_store - .unrealized_justified_checkpoint - .root; - let justified_block = db - .get_blinded_block(&justified_block_root)? - .ok_or_else(|| { - Error::SchemaMigrationError(format!( - "unrealized justified block missing for migration: {justified_block_root:?}", - )) - })?; - let justified_state_root = justified_block.state_root(); - let mut state = db - .get_state(&justified_state_root, Some(justified_block.slot()))? - .ok_or_else(|| { - Error::SchemaMigrationError(format!( - "justified state missing for migration: {justified_state_root:?}" - )) - })?; - state.build_all_committee_caches(spec).map_err(|e| { - Error::SchemaMigrationError(format!("unable to build committee caches: {e:?}")) - })?; - - // Re-verify attestations while adding attesting indices. - let attestations = attestations_v5 - .into_iter() - .flat_map(|(_, attestations)| attestations) - .filter_map(|attestation| { - let res = state - .get_beacon_committee(attestation.data.slot, attestation.data.index) - .map_err(Into::into) - .and_then(|committee| get_indexed_attestation(committee.committee, &attestation)) - .and_then(|indexed_attestation| { - is_valid_indexed_attestation( - &state, - &indexed_attestation, - VerifySignatures::True, - spec, - )?; - Ok(indexed_attestation) - }); - - match res { - Ok(indexed) => Some((attestation, indexed.attesting_indices.into())), - Err(e) => { - debug!( - log, - "Dropping attestation on migration"; - "err" => ?e, - "head_block" => ?attestation.data.beacon_block_root, - ); - None - } - } - }) - .collect::>(); - - let attester_slashings = attester_slashings_v5 - .iter() - .filter_map(|(slashing, _)| { - slashing - .clone() - .validate(&state, spec) - .map_err(|e| { - debug!( - log, - "Dropping attester slashing on migration"; - "err" => ?e, - "slashing" => ?slashing, - ); - }) - .ok() - }) - .collect::>(); - - let proposer_slashings = proposer_slashings_v5 - .iter() - .filter_map(|slashing| { - slashing - .clone() - .validate(&state, spec) - .map_err(|e| { - debug!( - log, - "Dropping proposer slashing on migration"; - "err" => ?e, - "slashing" => ?slashing, - ); - }) - .ok() - }) - .collect::>(); - - let voluntary_exits = voluntary_exits_v5 - .iter() - .filter_map(|exit| { - exit.clone() - .validate(&state, spec) - .map_err(|e| { - debug!( - log, - "Dropping voluntary exit on migration"; - "err" => ?e, - "exit" => ?exit, - ); - }) - .ok() - }) - .collect::>(); - - debug!( - log, - "Migrated op pool"; - "attestations" => attestations.len(), - "attester_slashings" => attester_slashings.len(), - "proposer_slashings" => proposer_slashings.len(), - "voluntary_exits" => voluntary_exits.len() - ); - - let v12 = PersistedOperationPool::V12(PersistedOperationPoolV12 { - attestations, - sync_contributions, - attester_slashings, - proposer_slashings, - voluntary_exits, - }); - Ok(vec![v12.as_kv_store_op(OP_POOL_DB_KEY)]) -} - -pub fn downgrade_from_v12( - db: Arc>, - log: Logger, -) -> Result, Error> { - // Load a V12 op pool and transform it to V5. - let Some(PersistedOperationPoolV12:: { - attestations, - sync_contributions, - attester_slashings, - proposer_slashings, - voluntary_exits, - }) = db.get_item(&OP_POOL_DB_KEY)? - else { - debug!(log, "Nothing to do, no operation pool stored"); - return Ok(vec![]); - }; - - info!( - log, - "Dropping attestations from pool"; - "count" => attestations.len(), - ); - - let attester_slashings_v5 = attester_slashings - .into_iter() - .filter_map(|slashing| { - let fork_version = slashing.first_fork_verified_against()?; - Some((slashing.into_inner(), fork_version)) - }) - .collect::>(); - - let proposer_slashings_v5 = proposer_slashings - .into_iter() - .map(|slashing| slashing.into_inner()) - .collect::>(); - - let voluntary_exits_v5 = voluntary_exits - .into_iter() - .map(|exit| exit.into_inner()) - .collect::>(); - - info!( - log, - "Migrated slashings and exits"; - "attester_slashings" => attester_slashings_v5.len(), - "proposer_slashings" => proposer_slashings_v5.len(), - "voluntary_exits" => voluntary_exits_v5.len(), - ); - - let v5 = PersistedOperationPoolV5 { - attestations_v5: vec![], - sync_contributions, - attester_slashings_v5, - proposer_slashings_v5, - voluntary_exits_v5, - }; - Ok(vec![v5.as_kv_store_op(OP_POOL_DB_KEY)]) -} diff --git a/beacon_node/beacon_chain/src/schema_change/migration_schema_v13.rs b/beacon_node/beacon_chain/src/schema_change/migration_schema_v13.rs deleted file mode 100644 index d4ac974603..0000000000 --- a/beacon_node/beacon_chain/src/schema_change/migration_schema_v13.rs +++ /dev/null @@ -1,150 +0,0 @@ -use crate::eth1_chain::SszEth1; -use eth1::{BlockCache, SszDepositCacheV1, SszDepositCacheV13, SszEth1CacheV1, SszEth1CacheV13}; -use ssz::{Decode, Encode}; -use state_processing::common::DepositDataTree; -use store::Error; -use types::DEPOSIT_TREE_DEPTH; - -pub fn update_eth1_cache(persisted_eth1_v1: SszEth1) -> Result { - if persisted_eth1_v1.use_dummy_backend { - // backend_bytes is empty when using dummy backend - return Ok(persisted_eth1_v1); - } - - let SszEth1 { - use_dummy_backend, - backend_bytes, - } = persisted_eth1_v1; - - let ssz_eth1_cache_v1 = SszEth1CacheV1::from_ssz_bytes(&backend_bytes)?; - let SszEth1CacheV1 { - block_cache, - deposit_cache: deposit_cache_v1, - last_processed_block, - } = ssz_eth1_cache_v1; - - let SszDepositCacheV1 { - logs, - leaves, - deposit_contract_deploy_block, - deposit_roots, - } = deposit_cache_v1; - - let deposit_cache_v13 = SszDepositCacheV13 { - logs, - leaves, - deposit_contract_deploy_block, - finalized_deposit_count: 0, - finalized_block_height: deposit_contract_deploy_block.saturating_sub(1), - deposit_tree_snapshot: None, - deposit_roots, - }; - - let ssz_eth1_cache_v13 = SszEth1CacheV13 { - block_cache, - deposit_cache: deposit_cache_v13, - last_processed_block, - }; - - let persisted_eth1_v13 = SszEth1 { - use_dummy_backend, - backend_bytes: ssz_eth1_cache_v13.as_ssz_bytes(), - }; - - Ok(persisted_eth1_v13) -} - -pub fn downgrade_eth1_cache(persisted_eth1_v13: SszEth1) -> Result, Error> { - if persisted_eth1_v13.use_dummy_backend { - // backend_bytes is empty when using dummy backend - return Ok(Some(persisted_eth1_v13)); - } - - let SszEth1 { - use_dummy_backend, - backend_bytes, - } = persisted_eth1_v13; - - let ssz_eth1_cache_v13 = SszEth1CacheV13::from_ssz_bytes(&backend_bytes)?; - let SszEth1CacheV13 { - block_cache, - deposit_cache: deposit_cache_v13, - last_processed_block, - } = ssz_eth1_cache_v13; - - let SszDepositCacheV13 { - logs, - leaves, - deposit_contract_deploy_block, - finalized_deposit_count, - finalized_block_height: _, - deposit_tree_snapshot, - deposit_roots, - } = deposit_cache_v13; - - if finalized_deposit_count == 0 && deposit_tree_snapshot.is_none() { - // This tree was never finalized and can be directly downgraded to v1 without re-initializing - let deposit_cache_v1 = SszDepositCacheV1 { - logs, - leaves, - deposit_contract_deploy_block, - deposit_roots, - }; - let ssz_eth1_cache_v1 = SszEth1CacheV1 { - block_cache, - deposit_cache: deposit_cache_v1, - last_processed_block, - }; - return Ok(Some(SszEth1 { - use_dummy_backend, - backend_bytes: ssz_eth1_cache_v1.as_ssz_bytes(), - })); - } - // deposit cache was finalized; can't downgrade - Ok(None) -} - -pub fn reinitialized_eth1_cache_v13(deposit_contract_deploy_block: u64) -> SszEth1 { - let empty_tree = DepositDataTree::create(&[], 0, DEPOSIT_TREE_DEPTH); - let deposit_cache_v13 = SszDepositCacheV13 { - logs: vec![], - leaves: vec![], - deposit_contract_deploy_block, - finalized_deposit_count: 0, - finalized_block_height: deposit_contract_deploy_block.saturating_sub(1), - deposit_tree_snapshot: empty_tree.get_snapshot(), - deposit_roots: vec![empty_tree.root()], - }; - - let ssz_eth1_cache_v13 = SszEth1CacheV13 { - block_cache: BlockCache::default(), - deposit_cache: deposit_cache_v13, - last_processed_block: None, - }; - - SszEth1 { - use_dummy_backend: false, - backend_bytes: ssz_eth1_cache_v13.as_ssz_bytes(), - } -} - -pub fn reinitialized_eth1_cache_v1(deposit_contract_deploy_block: u64) -> SszEth1 { - let empty_tree = DepositDataTree::create(&[], 0, DEPOSIT_TREE_DEPTH); - let deposit_cache_v1 = SszDepositCacheV1 { - logs: vec![], - leaves: vec![], - deposit_contract_deploy_block, - deposit_roots: vec![empty_tree.root()], - }; - - let ssz_eth1_cache_v1 = SszEth1CacheV1 { - block_cache: BlockCache::default(), - deposit_cache: deposit_cache_v1, - last_processed_block: None, - }; - - SszEth1 { - use_dummy_backend: false, - backend_bytes: ssz_eth1_cache_v1.as_ssz_bytes(), - } -} diff --git a/beacon_node/beacon_chain/src/schema_change/migration_schema_v14.rs b/beacon_node/beacon_chain/src/schema_change/migration_schema_v14.rs deleted file mode 100644 index 52a990dc6e..0000000000 --- a/beacon_node/beacon_chain/src/schema_change/migration_schema_v14.rs +++ /dev/null @@ -1,118 +0,0 @@ -use crate::beacon_chain::{BeaconChainTypes, OP_POOL_DB_KEY}; -use operation_pool::{ - PersistedOperationPool, PersistedOperationPoolV12, PersistedOperationPoolV14, -}; -use slog::{debug, error, info, Logger}; -use slot_clock::SlotClock; -use std::sync::Arc; -use std::time::Duration; -use store::{Error, HotColdDB, KeyValueStoreOp, StoreItem}; -use types::{EthSpec, Hash256, Slot}; - -/// The slot clock isn't usually available before the database is initialized, so we construct a -/// temporary slot clock by reading the genesis state. It should always exist if the database is -/// initialized at a prior schema version, however we still handle the lack of genesis state -/// gracefully. -fn get_slot_clock( - db: &HotColdDB, - log: &Logger, -) -> Result, Error> { - let spec = db.get_chain_spec(); - let Some(genesis_block) = db.get_blinded_block(&Hash256::zero())? else { - error!(log, "Missing genesis block"); - return Ok(None); - }; - let Some(genesis_state) = db.get_state(&genesis_block.state_root(), Some(Slot::new(0)))? else { - error!(log, "Missing genesis state"; "state_root" => ?genesis_block.state_root()); - return Ok(None); - }; - Ok(Some(T::SlotClock::new( - spec.genesis_slot, - Duration::from_secs(genesis_state.genesis_time()), - Duration::from_secs(spec.seconds_per_slot), - ))) -} - -pub fn upgrade_to_v14( - db: Arc>, - log: Logger, -) -> Result, Error> { - // Load a V12 op pool and transform it to V14. - let Some(PersistedOperationPoolV12:: { - attestations, - sync_contributions, - attester_slashings, - proposer_slashings, - voluntary_exits, - }) = db.get_item(&OP_POOL_DB_KEY)? - else { - debug!(log, "Nothing to do, no operation pool stored"); - return Ok(vec![]); - }; - - // initialize with empty vector - let bls_to_execution_changes = vec![]; - let v14 = PersistedOperationPool::V14(PersistedOperationPoolV14 { - attestations, - sync_contributions, - attester_slashings, - proposer_slashings, - voluntary_exits, - bls_to_execution_changes, - }); - Ok(vec![v14.as_kv_store_op(OP_POOL_DB_KEY)]) -} - -pub fn downgrade_from_v14( - db: Arc>, - log: Logger, -) -> Result, Error> { - // We cannot downgrade from V14 once the Capella fork has been reached because there will - // be HistoricalSummaries stored in the database instead of HistoricalRoots and prior versions - // of Lighthouse can't handle that. - if let Some(capella_fork_epoch) = db.get_chain_spec().capella_fork_epoch { - let current_epoch = get_slot_clock::(&db, &log)? - .and_then(|clock| clock.now()) - .map(|slot| slot.epoch(T::EthSpec::slots_per_epoch())) - .ok_or(Error::SlotClockUnavailableForMigration)?; - - if current_epoch >= capella_fork_epoch { - error!( - log, - "Capella already active: v14+ is mandatory"; - "current_epoch" => current_epoch, - "capella_fork_epoch" => capella_fork_epoch, - ); - return Err(Error::UnableToDowngrade); - } - } - - // Load a V14 op pool and transform it to V12. - let Some(PersistedOperationPoolV14:: { - attestations, - sync_contributions, - attester_slashings, - proposer_slashings, - voluntary_exits, - bls_to_execution_changes, - }) = db.get_item(&OP_POOL_DB_KEY)? - else { - debug!(log, "Nothing to do, no operation pool stored"); - return Ok(vec![]); - }; - - info!( - log, - "Dropping bls_to_execution_changes from pool"; - "count" => bls_to_execution_changes.len(), - ); - - let v12 = PersistedOperationPoolV12 { - attestations, - sync_contributions, - attester_slashings, - proposer_slashings, - voluntary_exits, - }; - Ok(vec![v12.as_kv_store_op(OP_POOL_DB_KEY)]) -} diff --git a/beacon_node/beacon_chain/src/schema_change/migration_schema_v15.rs b/beacon_node/beacon_chain/src/schema_change/migration_schema_v15.rs deleted file mode 100644 index 0eb2c5fa3f..0000000000 --- a/beacon_node/beacon_chain/src/schema_change/migration_schema_v15.rs +++ /dev/null @@ -1,74 +0,0 @@ -use crate::beacon_chain::{BeaconChainTypes, OP_POOL_DB_KEY}; -use operation_pool::{ - PersistedOperationPool, PersistedOperationPoolV14, PersistedOperationPoolV15, -}; -use slog::{debug, info, Logger}; -use std::sync::Arc; -use store::{Error, HotColdDB, KeyValueStoreOp, StoreItem}; - -pub fn upgrade_to_v15( - db: Arc>, - log: Logger, -) -> Result, Error> { - // Load a V14 op pool and transform it to V15. - let Some(PersistedOperationPoolV14:: { - attestations, - sync_contributions, - attester_slashings, - proposer_slashings, - voluntary_exits, - bls_to_execution_changes, - }) = db.get_item(&OP_POOL_DB_KEY)? - else { - debug!(log, "Nothing to do, no operation pool stored"); - return Ok(vec![]); - }; - - let v15 = PersistedOperationPool::V15(PersistedOperationPoolV15 { - attestations, - sync_contributions, - attester_slashings, - proposer_slashings, - voluntary_exits, - bls_to_execution_changes, - // Initialize with empty set - capella_bls_change_broadcast_indices: <_>::default(), - }); - Ok(vec![v15.as_kv_store_op(OP_POOL_DB_KEY)]) -} - -pub fn downgrade_from_v15( - db: Arc>, - log: Logger, -) -> Result, Error> { - // Load a V15 op pool and transform it to V14. - let Some(PersistedOperationPoolV15:: { - attestations, - sync_contributions, - attester_slashings, - proposer_slashings, - voluntary_exits, - bls_to_execution_changes, - capella_bls_change_broadcast_indices, - }) = db.get_item(&OP_POOL_DB_KEY)? - else { - debug!(log, "Nothing to do, no operation pool stored"); - return Ok(vec![]); - }; - - info!( - log, - "Forgetting address changes for Capella broadcast"; - "count" => capella_bls_change_broadcast_indices.len(), - ); - - let v14 = PersistedOperationPoolV14 { - attestations, - sync_contributions, - attester_slashings, - proposer_slashings, - voluntary_exits, - bls_to_execution_changes, - }; - Ok(vec![v14.as_kv_store_op(OP_POOL_DB_KEY)]) -} diff --git a/beacon_node/beacon_chain/src/schema_change/migration_schema_v16.rs b/beacon_node/beacon_chain/src/schema_change/migration_schema_v16.rs deleted file mode 100644 index 230573b028..0000000000 --- a/beacon_node/beacon_chain/src/schema_change/migration_schema_v16.rs +++ /dev/null @@ -1,46 +0,0 @@ -use crate::beacon_chain::{BeaconChainTypes, FORK_CHOICE_DB_KEY}; -use crate::persisted_fork_choice::PersistedForkChoiceV11; -use slog::{debug, Logger}; -use std::sync::Arc; -use store::{Error, HotColdDB, KeyValueStoreOp, StoreItem}; - -pub fn upgrade_to_v16( - db: Arc>, - log: Logger, -) -> Result, Error> { - drop_balances_cache::(db, log) -} - -pub fn downgrade_from_v16( - db: Arc>, - log: Logger, -) -> Result, Error> { - drop_balances_cache::(db, log) -} - -/// Drop the balances cache from the fork choice store. -/// -/// There aren't any type-level changes in this schema migration, however the -/// way that we compute the `JustifiedBalances` has changed due to: -/// https://github.com/sigp/lighthouse/pull/3962 -pub fn drop_balances_cache( - db: Arc>, - log: Logger, -) -> Result, Error> { - let mut persisted_fork_choice = db - .get_item::(&FORK_CHOICE_DB_KEY)? - .ok_or_else(|| Error::SchemaMigrationError("fork choice missing from database".into()))?; - - debug!( - log, - "Dropping fork choice balances cache"; - "item_count" => persisted_fork_choice.fork_choice_store.balances_cache.items.len() - ); - - // Drop all items in the balances cache. - persisted_fork_choice.fork_choice_store.balances_cache = <_>::default(); - - let kv_op = persisted_fork_choice.as_kv_store_op(FORK_CHOICE_DB_KEY); - - Ok(vec![kv_op]) -} diff --git a/beacon_node/beacon_chain/tests/store_tests.rs b/beacon_node/beacon_chain/tests/store_tests.rs index c5866ae1e0..9f7199cf3c 100644 --- a/beacon_node/beacon_chain/tests/store_tests.rs +++ b/beacon_node/beacon_chain/tests/store_tests.rs @@ -2967,10 +2967,8 @@ async fn schema_downgrade_to_min_version() { // Can't downgrade beyond V18 once Deneb is reached, for simplicity don't test that // at all if Deneb is enabled. SchemaVersion(18) - } else if harness.spec.capella_fork_epoch.is_some() { - SchemaVersion(14) } else { - SchemaVersion(11) + SchemaVersion(16) }; // Save the slot clock so that the new harness doesn't revert in time. From 6b63d184201e1f72dcae780a81d4a6e50ebcad8f Mon Sep 17 00:00:00 2001 From: Jimmy Chen Date: Sat, 18 Nov 2023 03:55:11 +1100 Subject: [PATCH 14/23] Fix Rust beta compiler warnings (rustc 1.75.0-beta.1 (782883f60 2023-11-12)) (#4932) --- Makefile | 3 +- beacon_node/beacon_chain/src/beacon_chain.rs | 2 +- beacon_node/execution_layer/src/lib.rs | 12 ++----- .../test_utils/execution_block_generator.rs | 9 +++-- .../genesis/src/eth1_genesis_service.rs | 2 +- beacon_node/http_api/src/lib.rs | 35 ++++++++----------- beacon_node/http_metrics/src/metrics.rs | 2 -- .../lighthouse_network/src/discovery/enr.rs | 2 +- .../src/peer_manager/peerdb.rs | 2 +- .../src/rpc/codec/ssz_snappy.rs | 2 +- .../network_beacon_processor/rpc_methods.rs | 2 +- .../network/src/sync/backfill_sync/mod.rs | 2 +- .../network/src/sync/block_lookups/tests.rs | 2 +- .../network/src/sync/range_sync/chain.rs | 2 +- .../slot_clock/src/system_time_slot_clock.rs | 2 -- consensus/merkle_proof/src/lib.rs | 2 +- .../src/sync_aggregator_selection_data.rs | 4 +-- testing/state_transition_vectors/src/exit.rs | 2 +- watch/src/blockprint/mod.rs | 2 +- watch/src/database/mod.rs | 5 +++ 20 files changed, 45 insertions(+), 51 deletions(-) diff --git a/Makefile b/Makefile index e3d889bfea..19988fc2c6 100644 --- a/Makefile +++ b/Makefile @@ -215,7 +215,8 @@ lint: -A clippy::upper-case-acronyms \ -A clippy::vec-init-then-push \ -A clippy::question-mark \ - -A clippy::uninlined-format-args + -A clippy::uninlined-format-args \ + -A clippy::enum_variant_names # Lints the code using Clippy and automatically fix some simple compiler warnings. lint-fix: diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 2fd70056cc..168bbfca49 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -18,7 +18,7 @@ use crate::block_verification::{ use crate::block_verification_types::{ AsBlock, AvailableExecutedBlock, BlockImportData, ExecutedBlock, RpcBlock, }; -pub use crate::canonical_head::{CanonicalHead, CanonicalHeadRwLock}; +pub use crate::canonical_head::CanonicalHead; use crate::chain_config::ChainConfig; use crate::data_availability_checker::{ Availability, AvailabilityCheckError, AvailableBlock, DataAvailabilityChecker, diff --git a/beacon_node/execution_layer/src/lib.rs b/beacon_node/execution_layer/src/lib.rs index 3f75d0042d..3993e442ad 100644 --- a/beacon_node/execution_layer/src/lib.rs +++ b/beacon_node/execution_layer/src/lib.rs @@ -93,21 +93,15 @@ impl TryFrom> for ProvenancedPayload) -> Result { let block_proposal_contents = match value { BuilderBid::Merge(builder_bid) => BlockProposalContents::Payload { - payload: ExecutionPayloadHeader::Merge(builder_bid.header) - .try_into() - .map_err(|_| Error::InvalidPayloadConversion)?, + payload: ExecutionPayloadHeader::Merge(builder_bid.header).into(), block_value: builder_bid.value, }, BuilderBid::Capella(builder_bid) => BlockProposalContents::Payload { - payload: ExecutionPayloadHeader::Capella(builder_bid.header) - .try_into() - .map_err(|_| Error::InvalidPayloadConversion)?, + payload: ExecutionPayloadHeader::Capella(builder_bid.header).into(), block_value: builder_bid.value, }, BuilderBid::Deneb(builder_bid) => BlockProposalContents::PayloadAndBlobs { - payload: ExecutionPayloadHeader::Deneb(builder_bid.header) - .try_into() - .map_err(|_| Error::InvalidPayloadConversion)?, + payload: ExecutionPayloadHeader::Deneb(builder_bid.header).into(), block_value: builder_bid.value, kzg_commitments: builder_bid.blinded_blobs_bundle.commitments, blobs: BlobItems::::try_from_blob_roots( diff --git a/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs b/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs index cf0faf655a..713ebb670c 100644 --- a/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs +++ b/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs @@ -655,14 +655,17 @@ pub fn load_test_blobs_bundle() -> Result<(KzgCommitment, KzgProof, Ok(( commitments - .get(0) + .first() .cloned() .ok_or("commitment missing in test bundle")?, proofs - .get(0) + .first() .cloned() .ok_or("proof missing in test bundle")?, - blobs.get(0).cloned().ok_or("blob missing in test bundle")?, + blobs + .first() + .cloned() + .ok_or("blob missing in test bundle")?, )) } diff --git a/beacon_node/genesis/src/eth1_genesis_service.rs b/beacon_node/genesis/src/eth1_genesis_service.rs index b7134e37c4..fdba9f4741 100644 --- a/beacon_node/genesis/src/eth1_genesis_service.rs +++ b/beacon_node/genesis/src/eth1_genesis_service.rs @@ -1,4 +1,4 @@ -pub use crate::{common::genesis_deposits, interop::interop_genesis_state}; +pub use crate::common::genesis_deposits; pub use eth1::Config as Eth1Config; use eth1::{DepositLog, Eth1Block, Service as Eth1Service}; diff --git a/beacon_node/http_api/src/lib.rs b/beacon_node/http_api/src/lib.rs index 309db204ae..a6f9d9ffce 100644 --- a/beacon_node/http_api/src/lib.rs +++ b/beacon_node/http_api/src/lib.rs @@ -569,12 +569,12 @@ pub fn serve( chain: Arc>| { task_spawner.blocking_json_task(Priority::P1, move || { let (root, execution_optimistic, finalized) = state_id.root(&chain)?; - Ok(root) - .map(api_types::RootData::from) - .map(api_types::GenericResponse::from) - .map(|resp| { - resp.add_execution_optimistic_finalized(execution_optimistic, finalized) - }) + Ok(api_types::GenericResponse::from(api_types::RootData::from( + root, + ))) + .map(|resp| { + resp.add_execution_optimistic_finalized(execution_optimistic, finalized) + }) }) }, ); @@ -1940,8 +1940,8 @@ pub fn serve( .naive_aggregation_pool .read() .iter() - .cloned() - .filter(|att| query_filter(&att.data)), + .filter(|&att| query_filter(&att.data)) + .cloned(), ); Ok(api_types::GenericResponse::from(attestations)) }) @@ -2318,11 +2318,9 @@ pub fn serve( task_spawner.blocking_json_task(Priority::P1, move || { let (rewards, execution_optimistic, finalized) = standard_block_rewards::compute_beacon_block_rewards(chain, block_id)?; - Ok(rewards) - .map(api_types::GenericResponse::from) - .map(|resp| { - resp.add_execution_optimistic_finalized(execution_optimistic, finalized) - }) + Ok(api_types::GenericResponse::from(rewards)).map(|resp| { + resp.add_execution_optimistic_finalized(execution_optimistic, finalized) + }) }) }, ); @@ -2435,8 +2433,7 @@ pub fn serve( let execution_optimistic = chain.is_optimistic_or_invalid_head().unwrap_or_default(); - Ok(attestation_rewards) - .map(api_types::GenericResponse::from) + Ok(api_types::GenericResponse::from(attestation_rewards)) .map(|resp| resp.add_execution_optimistic(execution_optimistic)) }) }, @@ -2462,11 +2459,9 @@ pub fn serve( chain, block_id, validators, log, )?; - Ok(rewards) - .map(api_types::GenericResponse::from) - .map(|resp| { - resp.add_execution_optimistic_finalized(execution_optimistic, finalized) - }) + Ok(api_types::GenericResponse::from(rewards)).map(|resp| { + resp.add_execution_optimistic_finalized(execution_optimistic, finalized) + }) }) }, ); diff --git a/beacon_node/http_metrics/src/metrics.rs b/beacon_node/http_metrics/src/metrics.rs index 785206b757..e6e06caa84 100644 --- a/beacon_node/http_metrics/src/metrics.rs +++ b/beacon_node/http_metrics/src/metrics.rs @@ -4,8 +4,6 @@ use lighthouse_metrics::TextEncoder; use lighthouse_network::prometheus_client::encoding::text::encode; use malloc_utils::scrape_allocator_metrics; -pub use lighthouse_metrics::*; - pub fn gather_prometheus_metrics( ctx: &Context, ) -> std::result::Result { diff --git a/beacon_node/lighthouse_network/src/discovery/enr.rs b/beacon_node/lighthouse_network/src/discovery/enr.rs index 8eacabb4d0..0ec7e2ab7a 100644 --- a/beacon_node/lighthouse_network/src/discovery/enr.rs +++ b/beacon_node/lighthouse_network/src/discovery/enr.rs @@ -1,6 +1,6 @@ //! Helper functions and an extension trait for Ethereum 2 ENRs. -pub use discv5::enr::{self, CombinedKey, EnrBuilder}; +pub use discv5::enr::{CombinedKey, EnrBuilder}; use super::enr_ext::CombinedKeyExt; use super::ENR_FILENAME; diff --git a/beacon_node/lighthouse_network/src/peer_manager/peerdb.rs b/beacon_node/lighthouse_network/src/peer_manager/peerdb.rs index 7157a62721..a6bf3ffecc 100644 --- a/beacon_node/lighthouse_network/src/peer_manager/peerdb.rs +++ b/beacon_node/lighthouse_network/src/peer_manager/peerdb.rs @@ -1489,7 +1489,7 @@ mod tests { assert!(the_best.is_some()); // Consistency check let best_peers = pdb.best_peers_by_status(PeerInfo::is_connected); - assert_eq!(the_best.unwrap(), best_peers.get(0).unwrap().0); + assert_eq!(the_best.unwrap(), best_peers.first().unwrap().0); } #[test] diff --git a/beacon_node/lighthouse_network/src/rpc/codec/ssz_snappy.rs b/beacon_node/lighthouse_network/src/rpc/codec/ssz_snappy.rs index f1b76674ae..6e83cfc86d 100644 --- a/beacon_node/lighthouse_network/src/rpc/codec/ssz_snappy.rs +++ b/beacon_node/lighthouse_network/src/rpc/codec/ssz_snappy.rs @@ -378,7 +378,7 @@ fn handle_error( Ok(None) } } - _ => Err(err).map_err(RPCError::from), + _ => Err(RPCError::from(err)), } } diff --git a/beacon_node/network/src/network_beacon_processor/rpc_methods.rs b/beacon_node/network/src/network_beacon_processor/rpc_methods.rs index 0e6c76e222..f2ca342809 100644 --- a/beacon_node/network/src/network_beacon_processor/rpc_methods.rs +++ b/beacon_node/network/src/network_beacon_processor/rpc_methods.rs @@ -224,7 +224,7 @@ impl NetworkBeaconProcessor { request_id: PeerRequestId, request: BlobsByRootRequest, ) { - let Some(requested_root) = request.blob_ids.get(0).map(|id| id.block_root) else { + let Some(requested_root) = request.blob_ids.first().map(|id| id.block_root) else { // No blob ids requested. return; }; diff --git a/beacon_node/network/src/sync/backfill_sync/mod.rs b/beacon_node/network/src/sync/backfill_sync/mod.rs index e580846976..0d7e7c16c3 100644 --- a/beacon_node/network/src/sync/backfill_sync/mod.rs +++ b/beacon_node/network/src/sync/backfill_sync/mod.rs @@ -929,7 +929,7 @@ impl BackFillSync { .collect::>(); // Sort peers prioritizing unrelated peers with less active requests. priorized_peers.sort_unstable(); - priorized_peers.get(0).map(|&(_, _, peer)| peer) + priorized_peers.first().map(|&(_, _, peer)| peer) }; if let Some(peer) = new_peer { diff --git a/beacon_node/network/src/sync/block_lookups/tests.rs b/beacon_node/network/src/sync/block_lookups/tests.rs index bd1e72ee18..551f1fdae6 100644 --- a/beacon_node/network/src/sync/block_lookups/tests.rs +++ b/beacon_node/network/src/sync/block_lookups/tests.rs @@ -1635,7 +1635,7 @@ mod deneb_only { self } fn invalidate_blobs_too_many(mut self) -> Self { - let first_blob = self.blobs.get(0).expect("blob").clone(); + let first_blob = self.blobs.first().expect("blob").clone(); self.blobs.push(first_blob); self } diff --git a/beacon_node/network/src/sync/range_sync/chain.rs b/beacon_node/network/src/sync/range_sync/chain.rs index 1e5bf09b3a..5a77340e3b 100644 --- a/beacon_node/network/src/sync/range_sync/chain.rs +++ b/beacon_node/network/src/sync/range_sync/chain.rs @@ -885,7 +885,7 @@ impl SyncingChain { .collect::>(); // Sort peers prioritizing unrelated peers with less active requests. priorized_peers.sort_unstable(); - priorized_peers.get(0).map(|&(_, _, peer)| peer) + priorized_peers.first().map(|&(_, _, peer)| peer) }; if let Some(peer) = new_peer { diff --git a/common/slot_clock/src/system_time_slot_clock.rs b/common/slot_clock/src/system_time_slot_clock.rs index c54646fbc6..770132064e 100644 --- a/common/slot_clock/src/system_time_slot_clock.rs +++ b/common/slot_clock/src/system_time_slot_clock.rs @@ -2,8 +2,6 @@ use super::{ManualSlotClock, SlotClock}; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use types::Slot; -pub use std::time::SystemTimeError; - /// Determines the present slot based upon the present system time. #[derive(Clone)] pub struct SystemTimeSlotClock { diff --git a/consensus/merkle_proof/src/lib.rs b/consensus/merkle_proof/src/lib.rs index dc3de71cef..595de86e86 100644 --- a/consensus/merkle_proof/src/lib.rs +++ b/consensus/merkle_proof/src/lib.rs @@ -250,7 +250,7 @@ impl MerkleTree { if deposit_count == (0x1 << level) { return Ok(MerkleTree::Finalized( *finalized_branch - .get(0) + .first() .ok_or(MerkleTreeError::PleaseNotifyTheDevs)?, )); } diff --git a/consensus/types/src/sync_aggregator_selection_data.rs b/consensus/types/src/sync_aggregator_selection_data.rs index 2b60d01b8e..3da130bb06 100644 --- a/consensus/types/src/sync_aggregator_selection_data.rs +++ b/consensus/types/src/sync_aggregator_selection_data.rs @@ -25,11 +25,11 @@ pub struct SyncAggregatorSelectionData { pub subcommittee_index: u64, } +impl SignedRoot for SyncAggregatorSelectionData {} + #[cfg(test)] mod tests { use super::*; ssz_and_tree_hash_tests!(SyncAggregatorSelectionData); } - -impl SignedRoot for SyncAggregatorSelectionData {} diff --git a/testing/state_transition_vectors/src/exit.rs b/testing/state_transition_vectors/src/exit.rs index 3b9235cc4e..29f5c015e3 100644 --- a/testing/state_transition_vectors/src/exit.rs +++ b/testing/state_transition_vectors/src/exit.rs @@ -127,7 +127,7 @@ vectors_and_tests!( ExitTest { block_modifier: Box::new(|_, block| { // Duplicate the exit - let exit = block.body().voluntary_exits().get(0).unwrap().clone(); + let exit = block.body().voluntary_exits().first().unwrap().clone(); block.body_mut().voluntary_exits_mut().push(exit).unwrap(); }), expected: Err(BlockProcessingError::ExitInvalid { diff --git a/watch/src/blockprint/mod.rs b/watch/src/blockprint/mod.rs index b8107e5bf5..532776f425 100644 --- a/watch/src/blockprint/mod.rs +++ b/watch/src/blockprint/mod.rs @@ -17,7 +17,7 @@ pub use config::Config; pub use database::{ get_blockprint_by_root, get_blockprint_by_slot, get_highest_blockprint, get_lowest_blockprint, get_unknown_blockprint, get_validators_clients_at_slot, insert_batch_blockprint, - list_consensus_clients, WatchBlockprint, + WatchBlockprint, }; pub use server::blockprint_routes; diff --git a/watch/src/database/mod.rs b/watch/src/database/mod.rs index b9a7a900a5..841ebe5ee7 100644 --- a/watch/src/database/mod.rs +++ b/watch/src/database/mod.rs @@ -26,24 +26,29 @@ pub use self::error::Error; pub use self::models::{WatchBeaconBlock, WatchCanonicalSlot, WatchProposerInfo, WatchValidator}; pub use self::watch_types::{WatchHash, WatchPK, WatchSlot}; +// Clippy has false positives on these re-exports from Rust 1.75.0-beta.1. +#[allow(unused_imports)] pub use crate::block_rewards::{ get_block_rewards_by_root, get_block_rewards_by_slot, get_highest_block_rewards, get_lowest_block_rewards, get_unknown_block_rewards, insert_batch_block_rewards, WatchBlockRewards, }; +#[allow(unused_imports)] pub use crate::block_packing::{ get_block_packing_by_root, get_block_packing_by_slot, get_highest_block_packing, get_lowest_block_packing, get_unknown_block_packing, insert_batch_block_packing, WatchBlockPacking, }; +#[allow(unused_imports)] pub use crate::suboptimal_attestations::{ get_all_suboptimal_attestations_for_epoch, get_attestation_by_index, get_attestation_by_pubkey, get_highest_attestation, get_lowest_attestation, insert_batch_suboptimal_attestations, WatchAttestation, WatchSuboptimalAttestation, }; +#[allow(unused_imports)] pub use crate::blockprint::{ get_blockprint_by_root, get_blockprint_by_slot, get_highest_blockprint, get_lowest_blockprint, get_unknown_blockprint, get_validators_clients_at_slot, insert_batch_blockprint, From 228180bb35f5fb52e90cc8449bbcf6bc9d45f2c2 Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Mon, 20 Nov 2023 20:46:13 -0800 Subject: [PATCH 15/23] add some more block v3 tests --- beacon_node/http_api/tests/tests.rs | 941 ++++++++++++++++++++++++++++ 1 file changed, 941 insertions(+) diff --git a/beacon_node/http_api/tests/tests.rs b/beacon_node/http_api/tests/tests.rs index bd238a7799..dd7ea1387e 100644 --- a/beacon_node/http_api/tests/tests.rs +++ b/beacon_node/http_api/tests/tests.rs @@ -38,6 +38,8 @@ use types::{ MainnetEthSpec, RelativeEpoch, SelectionProof, SignedRoot, Slot, }; +use eth2::types::ForkVersionedBeaconBlockType::{Blinded, Full}; + type E = MainnetEthSpec; const SECONDS_PER_SLOT: u64 = 12; @@ -3452,6 +3454,46 @@ impl ApiTester { (proposer_index, randao_reveal) } + pub async fn test_payload_v3_respects_registration(self) -> Self { + let slot = self.chain.slot().unwrap(); + let epoch = self.chain.epoch().unwrap(); + + let (proposer_index, randao_reveal) = self.get_test_randao(slot, epoch).await; + + let payload_type = self + .client + .get_validator_blocks_v3::(slot, &randao_reveal, None) + .await + .unwrap(); + + let payload: BlindedPayload = match payload_type { + Blinded(payload) => payload + .data + .block() + .body() + .execution_payload() + .unwrap() + .into(), + Full(_) => panic!("Expecting a blinded payload"), + }; + + let expected_fee_recipient = Address::from_low_u64_be(proposer_index as u64); + assert_eq!(payload.fee_recipient(), expected_fee_recipient); + assert_eq!(payload.gas_limit(), 11_111_111); + + // If this cache is empty, it indicates fallback was not used, so the payload came from the + // mock builder. + assert!(self + .chain + .execution_layer + .as_ref() + .unwrap() + .get_payload_by_root(&payload.tree_hash_root()) + .is_none()); + + self + } + pub async fn test_payload_respects_registration(self) -> Self { let slot = self.chain.slot().unwrap(); let epoch = self.chain.epoch().unwrap(); @@ -3526,6 +3568,50 @@ impl ApiTester { self } + pub async fn test_payload_v3_accepts_mutated_gas_limit(self) -> Self { + // Mutate gas limit. + self.mock_builder + .as_ref() + .unwrap() + .add_operation(Operation::GasLimit(30_000_000)); + + let slot = self.chain.slot().unwrap(); + let epoch = self.chain.epoch().unwrap(); + + let (proposer_index, randao_reveal) = self.get_test_randao(slot, epoch).await; + + let payload_type = self + .client + .get_validator_blocks_v3::(slot, &randao_reveal, None) + .await + .unwrap(); + + let payload: BlindedPayload = match payload_type { + Blinded(payload) => payload + .data + .block() + .body() + .execution_payload() + .unwrap() + .into(), + Full(_) => panic!("Expecting a blinded payload"), + }; + + let expected_fee_recipient = Address::from_low_u64_be(proposer_index as u64); + assert_eq!(payload.fee_recipient(), expected_fee_recipient); + assert_eq!(payload.gas_limit(), 30_000_000); + + // This cache should not be populated because fallback should not have been used. + assert!(self + .chain + .execution_layer + .as_ref() + .unwrap() + .get_payload_by_root(&payload.tree_hash_root()) + .is_none()); + self + } + pub async fn test_payload_accepts_changed_fee_recipient(self) -> Self { let test_fee_recipient = "0x4242424242424242424242424242424242424242" .parse::

() @@ -3567,6 +3653,52 @@ impl ApiTester { self } + pub async fn test_payload_v3_accepts_changed_fee_recipient(self) -> Self { + let test_fee_recipient = "0x4242424242424242424242424242424242424242" + .parse::
() + .unwrap(); + + // Mutate fee recipient. + self.mock_builder + .as_ref() + .unwrap() + .add_operation(Operation::FeeRecipient(test_fee_recipient)); + + let slot = self.chain.slot().unwrap(); + let epoch = self.chain.epoch().unwrap(); + + let (_, randao_reveal) = self.get_test_randao(slot, epoch).await; + + let payload_type = self + .client + .get_validator_blocks_v3::(slot, &randao_reveal, None) + .await + .unwrap(); + + let payload: BlindedPayload = match payload_type { + Blinded(payload) => payload + .data + .block() + .body() + .execution_payload() + .unwrap() + .into(), + Full(_) => panic!("Expecting a blinded payload"), + }; + + assert_eq!(payload.fee_recipient(), test_fee_recipient); + + // This cache should not be populated because fallback should not have been used. + assert!(self + .chain + .execution_layer + .as_ref() + .unwrap() + .get_payload_by_root(&payload.tree_hash_root()) + .is_none()); + self + } + pub async fn test_payload_rejects_invalid_parent_hash(self) -> Self { let invalid_parent_hash = "0x4242424242424242424242424242424242424242424242424242424242424242" @@ -3616,6 +3748,60 @@ impl ApiTester { self } + pub async fn test_payload_v3_rejects_invalid_parent_hash(self) -> Self { + let invalid_parent_hash = + "0x4242424242424242424242424242424242424242424242424242424242424242" + .parse::() + .unwrap(); + + // Mutate parent hash. + self.mock_builder + .as_ref() + .unwrap() + .add_operation(Operation::ParentHash(invalid_parent_hash)); + + let slot = self.chain.slot().unwrap(); + let epoch = self.chain.epoch().unwrap(); + let expected_parent_hash = self + .chain + .head_snapshot() + .beacon_state + .latest_execution_payload_header() + .unwrap() + .block_hash(); + + let (_, randao_reveal) = self.get_test_randao(slot, epoch).await; + + let payload_type = self + .client + .get_validator_blocks_v3::(slot, &randao_reveal, None) + .await + .unwrap(); + + let payload: FullPayload = match payload_type { + Full(payload) => payload + .data + .block() + .body() + .execution_payload() + .unwrap() + .into(), + Blinded(_) => panic!("Expecting a blinded payload"), + }; + + assert_eq!(payload.parent_hash(), expected_parent_hash); + + // If this cache is populated, it indicates fallback to the local EE was correctly used. + assert!(self + .chain + .execution_layer + .as_ref() + .unwrap() + .get_payload_by_root(&payload.tree_hash_root()) + .is_some()); + self + } + pub async fn test_payload_rejects_invalid_prev_randao(self) -> Self { let invalid_prev_randao = "0x4242424242424242424242424242424242424242424242424242424242424242" @@ -3663,6 +3849,58 @@ impl ApiTester { self } + pub async fn test_payload_v3_rejects_invalid_prev_randao(self) -> Self { + let invalid_prev_randao = + "0x4242424242424242424242424242424242424242424242424242424242424242" + .parse::() + .unwrap(); + + // Mutate prev randao. + self.mock_builder + .as_ref() + .unwrap() + .add_operation(Operation::PrevRandao(invalid_prev_randao)); + + let slot = self.chain.slot().unwrap(); + let epoch = self.chain.epoch().unwrap(); + let expected_prev_randao = self + .chain + .canonical_head + .cached_head() + .head_random() + .unwrap(); + let (_, randao_reveal) = self.get_test_randao(slot, epoch).await; + + let payload_type = self + .client + .get_validator_blocks_v3::(slot, &randao_reveal, None) + .await + .unwrap(); + + let payload: FullPayload = match payload_type { + Full(payload) => payload + .data + .block() + .body() + .execution_payload() + .unwrap() + .into(), + Blinded(_) => panic!("Expecting a full payload"), + }; + + assert_eq!(payload.prev_randao(), expected_prev_randao); + + // If this cache is populated, it indicates fallback to the local EE was correctly used. + assert!(self + .chain + .execution_layer + .as_ref() + .unwrap() + .get_payload_by_root(&payload.tree_hash_root()) + .is_some()); + self + } + pub async fn test_payload_rejects_invalid_block_number(self) -> Self { let invalid_block_number = 2; @@ -3710,6 +3948,58 @@ impl ApiTester { self } + pub async fn test_payload_v3_rejects_invalid_block_number(self) -> Self { + let invalid_block_number = 2; + + // Mutate block number. + self.mock_builder + .as_ref() + .unwrap() + .add_operation(Operation::BlockNumber(invalid_block_number)); + + let slot = self.chain.slot().unwrap(); + let epoch = self.chain.epoch().unwrap(); + let expected_block_number = self + .chain + .head_snapshot() + .beacon_state + .latest_execution_payload_header() + .unwrap() + .block_number() + + 1; + + let (_, randao_reveal) = self.get_test_randao(slot, epoch).await; + + let payload_type = self + .client + .get_validator_blocks_v3::(slot, &randao_reveal, None) + .await + .unwrap(); + + let payload: FullPayload = match payload_type { + Full(payload) => payload + .data + .block() + .body() + .execution_payload() + .unwrap() + .into(), + Blinded(_) => panic!("Expecting a full payload"), + }; + + assert_eq!(payload.block_number(), expected_block_number); + + // If this cache is populated, it indicates fallback to the local EE was correctly used. + assert!(self + .chain + .execution_layer + .as_ref() + .unwrap() + .get_payload_by_root(&payload.tree_hash_root()) + .is_some()); + self + } + pub async fn test_payload_rejects_invalid_timestamp(self) -> Self { let invalid_timestamp = 2; @@ -3756,6 +4046,57 @@ impl ApiTester { self } + pub async fn test_payload_v3_rejects_invalid_timestamp(self) -> Self { + let invalid_timestamp = 2; + + // Mutate timestamp. + self.mock_builder + .as_ref() + .unwrap() + .add_operation(Operation::Timestamp(invalid_timestamp)); + + let slot = self.chain.slot().unwrap(); + let epoch = self.chain.epoch().unwrap(); + let min_expected_timestamp = self + .chain + .head_snapshot() + .beacon_state + .latest_execution_payload_header() + .unwrap() + .timestamp(); + + let (_, randao_reveal) = self.get_test_randao(slot, epoch).await; + + let payload_type = self + .client + .get_validator_blocks_v3::(slot, &randao_reveal, None) + .await + .unwrap(); + + let payload: FullPayload = match payload_type { + Full(payload) => payload + .data + .block() + .body() + .execution_payload() + .unwrap() + .into(), + Blinded(_) => panic!("Expecting a blinded payload"), + }; + + assert!(payload.timestamp() > min_expected_timestamp); + + // If this cache is populated, it indicates fallback to the local EE was correctly used. + assert!(self + .chain + .execution_layer + .as_ref() + .unwrap() + .get_payload_by_root(&payload.tree_hash_root()) + .is_some()); + self + } + pub async fn test_payload_rejects_invalid_signature(self) -> Self { self.mock_builder.as_ref().unwrap().invalid_signatures(); @@ -3787,6 +4128,42 @@ impl ApiTester { self } + pub async fn test_payload_v3_rejects_invalid_signature(self) -> Self { + self.mock_builder.as_ref().unwrap().invalid_signatures(); + + let slot = self.chain.slot().unwrap(); + let epoch = self.chain.epoch().unwrap(); + + let (_, randao_reveal) = self.get_test_randao(slot, epoch).await; + + let payload_type = self + .client + .get_validator_blocks_v3::(slot, &randao_reveal, None) + .await + .unwrap(); + + let payload: FullPayload = match payload_type { + Full(payload) => payload + .data + .block() + .body() + .execution_payload() + .unwrap() + .into(), + Blinded(_) => panic!("Expecting a full payload"), + }; + + // If this cache is populated, it indicates fallback to the local EE was correctly used. + assert!(self + .chain + .execution_layer + .as_ref() + .unwrap() + .get_payload_by_root(&payload.tree_hash_root()) + .is_some()); + self + } + pub async fn test_builder_chain_health_skips(self) -> Self { let slot = self.chain.slot().unwrap(); @@ -3825,6 +4202,49 @@ impl ApiTester { self } + pub async fn test_builder_v3_chain_health_skips(self) -> Self { + let slot = self.chain.slot().unwrap(); + + // Since we are proposing this slot, start the count from the previous slot. + let prev_slot = slot - Slot::new(1); + let head_slot = self.chain.canonical_head.cached_head().head_slot(); + let epoch = self.chain.epoch().unwrap(); + + // Inclusive here to make sure we advance one slot past the threshold. + for _ in (prev_slot - head_slot).as_usize()..=self.chain.config.builder_fallback_skips { + self.harness.advance_slot(); + } + + let (_, randao_reveal) = self.get_test_randao(slot, epoch).await; + + let payload_type = self + .client + .get_validator_blocks_v3::(slot, &randao_reveal, None) + .await + .unwrap(); + + let payload: FullPayload = match payload_type { + Full(payload) => payload + .data + .block() + .body() + .execution_payload() + .unwrap() + .into(), + Blinded(_) => panic!("Expecting a full payload"), + }; + + // If this cache is populated, it indicates fallback to the local EE was correctly used. + assert!(self + .chain + .execution_layer + .as_ref() + .unwrap() + .get_payload_by_root(&payload.tree_hash_root()) + .is_some()); + self + } + pub async fn test_builder_chain_health_skips_per_epoch(self) -> Self { // Fill an epoch with `builder_fallback_skips_per_epoch` skip slots. for i in 0..E::slots_per_epoch() { @@ -3900,6 +4320,91 @@ impl ApiTester { self } + pub async fn test_builder_v3_chain_health_skips_per_epoch(self) -> Self { + // Fill an epoch with `builder_fallback_skips_per_epoch` skip slots. + for i in 0..E::slots_per_epoch() { + if i == 0 || i as usize > self.chain.config.builder_fallback_skips_per_epoch { + self.harness + .extend_chain( + 1, + BlockStrategy::OnCanonicalHead, + AttestationStrategy::AllValidators, + ) + .await; + } + self.harness.advance_slot(); + } + + let next_slot = self.chain.slot().unwrap(); + + let (_, randao_reveal) = self + .get_test_randao(next_slot, next_slot.epoch(E::slots_per_epoch())) + .await; + + let payload_type = self + .client + .get_validator_blocks_v3::(next_slot, &randao_reveal, None) + .await + .unwrap(); + + let payload: BlindedPayload = match payload_type { + Blinded(payload) => payload + .data + .block() + .body() + .execution_payload() + .unwrap() + .into(), + Full(_) => panic!("Expecting a blinded payload"), + }; + + // This cache should not be populated because fallback should not have been used. + assert!(self + .chain + .execution_layer + .as_ref() + .unwrap() + .get_payload_by_root(&payload.tree_hash_root()) + .is_none()); + + // Without proposing, advance into the next slot, this should make us cross the threshold + // number of skips, causing us to use the fallback. + self.harness.advance_slot(); + let next_slot = self.chain.slot().unwrap(); + + let (_, randao_reveal) = self + .get_test_randao(next_slot, next_slot.epoch(E::slots_per_epoch())) + .await; + + let payload_type = self + .client + .get_validator_blocks_v3::(next_slot, &randao_reveal, None) + .await + .unwrap(); + + let payload: FullPayload = match payload_type { + Full(payload) => payload + .data + .block() + .body() + .execution_payload() + .unwrap() + .into(), + Blinded(_) => panic!("Expecting a full payload"), + }; + + // If this cache is populated, it indicates fallback to the local EE was correctly used. + assert!(self + .chain + .execution_layer + .as_ref() + .unwrap() + .get_payload_by_root(&payload.tree_hash_root()) + .is_some()); + + self + } + pub async fn test_builder_chain_health_epochs_since_finalization(self) -> Self { let skips = E::slots_per_epoch() * self.chain.config.builder_fallback_epochs_since_finalization as u64; @@ -3990,6 +4495,106 @@ impl ApiTester { self } + pub async fn test_builder_v3_chain_health_epochs_since_finalization(self) -> Self { + let skips = E::slots_per_epoch() + * self.chain.config.builder_fallback_epochs_since_finalization as u64; + + for _ in 0..skips { + self.harness.advance_slot(); + } + + // Fill the next epoch with blocks, should be enough to justify, not finalize. + for _ in 0..E::slots_per_epoch() { + self.harness + .extend_chain( + 1, + BlockStrategy::OnCanonicalHead, + AttestationStrategy::AllValidators, + ) + .await; + self.harness.advance_slot(); + } + + let next_slot = self.chain.slot().unwrap(); + + let (_, randao_reveal) = self + .get_test_randao(next_slot, next_slot.epoch(E::slots_per_epoch())) + .await; + + let payload_type = self + .client + .get_validator_blocks_v3::(next_slot, &randao_reveal, None) + .await + .unwrap(); + + let payload: FullPayload = match payload_type { + Full(payload) => payload + .data + .block() + .body() + .execution_payload() + .unwrap() + .into(), + Blinded(_) => panic!("Expecting a full payload"), + }; + + // If this cache is populated, it indicates fallback to the local EE was correctly used. + assert!(self + .chain + .execution_layer + .as_ref() + .unwrap() + .get_payload_by_root(&payload.tree_hash_root()) + .is_some()); + + // Fill another epoch with blocks, should be enough to finalize. (Sneaky plus 1 because this + // scenario starts at an epoch boundary). + for _ in 0..E::slots_per_epoch() + 1 { + self.harness + .extend_chain( + 1, + BlockStrategy::OnCanonicalHead, + AttestationStrategy::AllValidators, + ) + .await; + self.harness.advance_slot(); + } + + let next_slot = self.chain.slot().unwrap(); + + let (_, randao_reveal) = self + .get_test_randao(next_slot, next_slot.epoch(E::slots_per_epoch())) + .await; + + let payload_type = self + .client + .get_validator_blocks_v3::(next_slot, &randao_reveal, None) + .await + .unwrap(); + + let payload: BlindedPayload = match payload_type { + Blinded(payload) => payload + .data + .block() + .body() + .execution_payload() + .unwrap() + .into(), + Full(_) => panic!("Expecting a blinded payload"), + }; + + // This cache should not be populated because fallback should not have been used. + assert!(self + .chain + .execution_layer + .as_ref() + .unwrap() + .get_payload_by_root(&payload.tree_hash_root()) + .is_none()); + + self + } + pub async fn test_builder_chain_health_optimistic_head(self) -> Self { // Make sure the next payload verification will return optimistic before advancing the chain. self.harness.mock_execution_layer.as_ref().map(|el| { @@ -4037,6 +4642,58 @@ impl ApiTester { self } + pub async fn test_builder_v3_chain_health_optimistic_head(self) -> Self { + // Make sure the next payload verification will return optimistic before advancing the chain. + self.harness.mock_execution_layer.as_ref().map(|el| { + el.server.all_payloads_syncing(true); + el + }); + self.harness + .extend_chain( + 1, + BlockStrategy::OnCanonicalHead, + AttestationStrategy::AllValidators, + ) + .await; + self.harness.advance_slot(); + + let slot = self.chain.slot().unwrap(); + let epoch = self.chain.epoch().unwrap(); + + let (proposer_index, randao_reveal) = self.get_test_randao(slot, epoch).await; + + let payload_type = self + .client + .get_validator_blocks_v3::(slot, &randao_reveal, None) + .await + .unwrap(); + + let payload: FullPayload = match payload_type { + Full(payload) => payload + .data + .block() + .body() + .execution_payload() + .unwrap() + .into(), + Blinded(_) => panic!("Expecting a full payload"), + }; + + let expected_fee_recipient = Address::from_low_u64_be(proposer_index as u64); + assert_eq!(payload.fee_recipient(), expected_fee_recipient); + + // If this cache is populated, it indicates fallback to the local EE was correctly used. + assert!(self + .chain + .execution_layer + .as_ref() + .unwrap() + .get_payload_by_root(&payload.tree_hash_root()) + .is_some()); + + self + } + pub async fn test_payload_rejects_inadequate_builder_threshold(self) -> Self { // Mutate value. self.mock_builder @@ -4074,6 +4731,48 @@ impl ApiTester { self } + pub async fn test_payload_v3_rejects_inadequate_builder_threshold(self) -> Self { + // Mutate value. + self.mock_builder + .as_ref() + .unwrap() + .add_operation(Operation::Value(Uint256::from( + DEFAULT_BUILDER_THRESHOLD_WEI - 1, + ))); + + let slot = self.chain.slot().unwrap(); + let epoch = self.chain.epoch().unwrap(); + + let (_, randao_reveal) = self.get_test_randao(slot, epoch).await; + + let payload_type = self + .client + .get_validator_blocks_v3::(slot, &randao_reveal, None) + .await + .unwrap(); + + let payload: FullPayload = match payload_type { + Full(payload) => payload + .data + .block() + .body() + .execution_payload() + .unwrap() + .into(), + Blinded(_) => panic!("Expecting a full payload"), + }; + + // If this cache is populated, it indicates fallback to the local EE was correctly used. + assert!(self + .chain + .execution_layer + .as_ref() + .unwrap() + .get_payload_by_root(&payload.tree_hash_root()) + .is_some()); + self + } + pub async fn test_builder_payload_chosen_when_more_profitable(self) -> Self { // Mutate value. self.mock_builder @@ -4111,6 +4810,48 @@ impl ApiTester { self } + pub async fn test_builder_payload_v3_chosen_when_more_profitable(self) -> Self { + // Mutate value. + self.mock_builder + .as_ref() + .unwrap() + .add_operation(Operation::Value(Uint256::from( + DEFAULT_MOCK_EL_PAYLOAD_VALUE_WEI + 1, + ))); + + let slot = self.chain.slot().unwrap(); + let epoch = self.chain.epoch().unwrap(); + + let (_, randao_reveal) = self.get_test_randao(slot, epoch).await; + + let payload_type = self + .client + .get_validator_blocks_v3::(slot, &randao_reveal, None) + .await + .unwrap(); + + let payload: BlindedPayload = match payload_type { + Blinded(payload) => payload + .data + .block() + .body() + .execution_payload() + .unwrap() + .into(), + Full(_) => panic!("Expecting a blinded payload"), + }; + + // The builder's payload should've been chosen, so this cache should not be populated + assert!(self + .chain + .execution_layer + .as_ref() + .unwrap() + .get_payload_by_root(&payload.tree_hash_root()) + .is_none()); + self + } + pub async fn test_local_payload_chosen_when_equally_profitable(self) -> Self { // Mutate value. self.mock_builder @@ -4148,6 +4889,48 @@ impl ApiTester { self } + pub async fn test_local_payload_v3_chosen_when_equally_profitable(self) -> Self { + // Mutate value. + self.mock_builder + .as_ref() + .unwrap() + .add_operation(Operation::Value(Uint256::from( + DEFAULT_MOCK_EL_PAYLOAD_VALUE_WEI, + ))); + + let slot = self.chain.slot().unwrap(); + let epoch = self.chain.epoch().unwrap(); + + let (_, randao_reveal) = self.get_test_randao(slot, epoch).await; + + let payload_type = self + .client + .get_validator_blocks_v3::(slot, &randao_reveal, None) + .await + .unwrap(); + + let payload: FullPayload = match payload_type { + Full(payload) => payload + .data + .block() + .body() + .execution_payload() + .unwrap() + .into(), + Blinded(_) => panic!("Expecting a full payload"), + }; + + // The local payload should've been chosen, so this cache should be populated + assert!(self + .chain + .execution_layer + .as_ref() + .unwrap() + .get_payload_by_root(&payload.tree_hash_root()) + .is_some()); + self + } + pub async fn test_local_payload_chosen_when_more_profitable(self) -> Self { // Mutate value. self.mock_builder @@ -4185,6 +4968,48 @@ impl ApiTester { self } + pub async fn test_local_payload_v3_chosen_when_more_profitable(self) -> Self { + // Mutate value. + self.mock_builder + .as_ref() + .unwrap() + .add_operation(Operation::Value(Uint256::from( + DEFAULT_MOCK_EL_PAYLOAD_VALUE_WEI - 1, + ))); + + let slot = self.chain.slot().unwrap(); + let epoch = self.chain.epoch().unwrap(); + + let (_, randao_reveal) = self.get_test_randao(slot, epoch).await; + + let payload_type = self + .client + .get_validator_blocks_v3::(slot, &randao_reveal, None) + .await + .unwrap(); + + let payload: FullPayload = match payload_type { + Full(payload) => payload + .data + .block() + .body() + .execution_payload() + .unwrap() + .into(), + Blinded(_) => panic!("Expecting a full payload"), + }; + + // The local payload should've been chosen, so this cache should be populated + assert!(self + .chain + .execution_layer + .as_ref() + .unwrap() + .get_payload_by_root(&payload.tree_hash_root()) + .is_some()); + self + } + pub async fn test_builder_works_post_capella(self) -> Self { // Ensure builder payload is chosen self.mock_builder @@ -5342,6 +6167,14 @@ async fn post_validator_register_valid() { .await; } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn post_validator_register_valid_v3() { + ApiTester::new_mev_tester() + .await + .test_payload_v3_respects_registration() + .await; +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn post_validator_register_gas_limit_mutation() { ApiTester::new_mev_tester() @@ -5350,6 +6183,14 @@ async fn post_validator_register_gas_limit_mutation() { .await; } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn post_validator_register_gas_limit_mutation_v3() { + ApiTester::new_mev_tester() + .await + .test_payload_v3_accepts_mutated_gas_limit() + .await; +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn post_validator_register_fee_recipient_mutation() { ApiTester::new_mev_tester() @@ -5358,6 +6199,14 @@ async fn post_validator_register_fee_recipient_mutation() { .await; } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn post_validator_register_fee_recipient_mutation_v3() { + ApiTester::new_mev_tester() + .await + .test_payload_v3_accepts_changed_fee_recipient() + .await; +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn get_blinded_block_invalid_parent_hash() { ApiTester::new_mev_tester() @@ -5366,6 +6215,14 @@ async fn get_blinded_block_invalid_parent_hash() { .await; } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn get_full_block_invalid_parent_hash_v3() { + ApiTester::new_mev_tester() + .await + .test_payload_v3_rejects_invalid_parent_hash() + .await; +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn get_blinded_block_invalid_prev_randao() { ApiTester::new_mev_tester() @@ -5374,6 +6231,14 @@ async fn get_blinded_block_invalid_prev_randao() { .await; } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn get_full_block_invalid_prev_randao_v3() { + ApiTester::new_mev_tester() + .await + .test_payload_v3_rejects_invalid_prev_randao() + .await; +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn get_blinded_block_invalid_block_number() { ApiTester::new_mev_tester() @@ -5382,6 +6247,14 @@ async fn get_blinded_block_invalid_block_number() { .await; } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn get_full_block_invalid_block_number_v3() { + ApiTester::new_mev_tester() + .await + .test_payload_v3_rejects_invalid_block_number() + .await; +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn get_blinded_block_invalid_timestamp() { ApiTester::new_mev_tester() @@ -5390,6 +6263,14 @@ async fn get_blinded_block_invalid_timestamp() { .await; } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn get_full_block_invalid_timestamp_v3() { + ApiTester::new_mev_tester() + .await + .test_payload_v3_rejects_invalid_timestamp() + .await; +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn get_blinded_block_invalid_signature() { ApiTester::new_mev_tester() @@ -5398,6 +6279,14 @@ async fn get_blinded_block_invalid_signature() { .await; } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn get_full_block_invalid_signature_v3() { + ApiTester::new_mev_tester() + .await + .test_payload_v3_rejects_invalid_signature() + .await; +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn builder_chain_health_skips() { ApiTester::new_mev_tester() @@ -5406,6 +6295,14 @@ async fn builder_chain_health_skips() { .await; } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn builder_chain_health_skips_v3() { + ApiTester::new_mev_tester() + .await + .test_builder_v3_chain_health_skips() + .await; +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn builder_chain_health_skips_per_epoch() { ApiTester::new_mev_tester() @@ -5414,6 +6311,14 @@ async fn builder_chain_health_skips_per_epoch() { .await; } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn builder_chain_health_skips_per_epoch_v3() { + ApiTester::new_mev_tester() + .await + .test_builder_v3_chain_health_skips_per_epoch() + .await; +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn builder_chain_health_epochs_since_finalization() { ApiTester::new_mev_tester() @@ -5422,6 +6327,14 @@ async fn builder_chain_health_epochs_since_finalization() { .await; } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn builder_chain_health_epochs_since_finalization_v3() { + ApiTester::new_mev_tester() + .await + .test_builder_v3_chain_health_epochs_since_finalization() + .await; +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn builder_chain_health_optimistic_head() { ApiTester::new_mev_tester() @@ -5430,6 +6343,14 @@ async fn builder_chain_health_optimistic_head() { .await; } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn builder_chain_health_optimistic_head_v3() { + ApiTester::new_mev_tester() + .await + .test_builder_v3_chain_health_optimistic_head() + .await; +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn builder_inadequate_builder_threshold() { ApiTester::new_mev_tester() @@ -5438,6 +6359,14 @@ async fn builder_inadequate_builder_threshold() { .await; } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn builder_inadequate_builder_threshold_v3() { + ApiTester::new_mev_tester() + .await + .test_payload_v3_rejects_inadequate_builder_threshold() + .await; +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn builder_payload_chosen_by_profit() { ApiTester::new_mev_tester_no_builder_threshold() @@ -5450,6 +6379,18 @@ async fn builder_payload_chosen_by_profit() { .await; } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn builder_payload_chosen_by_profit_v3() { + ApiTester::new_mev_tester_no_builder_threshold() + .await + .test_builder_payload_v3_chosen_when_more_profitable() + .await + .test_local_payload_v3_chosen_when_equally_profitable() + .await + .test_local_payload_v3_chosen_when_more_profitable() + .await; +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn builder_works_post_capella() { let mut config = ApiTesterConfig { From 24a0a7ffd092d249069ddefbe07572a140f0743d Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Mon, 20 Nov 2023 21:37:04 -0800 Subject: [PATCH 16/23] remove cache check --- beacon_node/http_api/tests/tests.rs | 309 ++++++---------------------- 1 file changed, 64 insertions(+), 245 deletions(-) diff --git a/beacon_node/http_api/tests/tests.rs b/beacon_node/http_api/tests/tests.rs index dd7ea1387e..f8b13406d3 100644 --- a/beacon_node/http_api/tests/tests.rs +++ b/beacon_node/http_api/tests/tests.rs @@ -3481,16 +3481,6 @@ impl ApiTester { assert_eq!(payload.fee_recipient(), expected_fee_recipient); assert_eq!(payload.gas_limit(), 11_111_111); - // If this cache is empty, it indicates fallback was not used, so the payload came from the - // mock builder. - assert!(self - .chain - .execution_layer - .as_ref() - .unwrap() - .get_payload_by_root(&payload.tree_hash_root()) - .is_none()); - self } @@ -3601,14 +3591,6 @@ impl ApiTester { assert_eq!(payload.fee_recipient(), expected_fee_recipient); assert_eq!(payload.gas_limit(), 30_000_000); - // This cache should not be populated because fallback should not have been used. - assert!(self - .chain - .execution_layer - .as_ref() - .unwrap() - .get_payload_by_root(&payload.tree_hash_root()) - .is_none()); self } @@ -3688,14 +3670,6 @@ impl ApiTester { assert_eq!(payload.fee_recipient(), test_fee_recipient); - // This cache should not be populated because fallback should not have been used. - assert!(self - .chain - .execution_layer - .as_ref() - .unwrap() - .get_payload_by_root(&payload.tree_hash_root()) - .is_none()); self } @@ -3791,14 +3765,6 @@ impl ApiTester { assert_eq!(payload.parent_hash(), expected_parent_hash); - // If this cache is populated, it indicates fallback to the local EE was correctly used. - assert!(self - .chain - .execution_layer - .as_ref() - .unwrap() - .get_payload_by_root(&payload.tree_hash_root()) - .is_some()); self } @@ -3890,14 +3856,6 @@ impl ApiTester { assert_eq!(payload.prev_randao(), expected_prev_randao); - // If this cache is populated, it indicates fallback to the local EE was correctly used. - assert!(self - .chain - .execution_layer - .as_ref() - .unwrap() - .get_payload_by_root(&payload.tree_hash_root()) - .is_some()); self } @@ -3989,14 +3947,6 @@ impl ApiTester { assert_eq!(payload.block_number(), expected_block_number); - // If this cache is populated, it indicates fallback to the local EE was correctly used. - assert!(self - .chain - .execution_layer - .as_ref() - .unwrap() - .get_payload_by_root(&payload.tree_hash_root()) - .is_some()); self } @@ -4086,14 +4036,6 @@ impl ApiTester { assert!(payload.timestamp() > min_expected_timestamp); - // If this cache is populated, it indicates fallback to the local EE was correctly used. - assert!(self - .chain - .execution_layer - .as_ref() - .unwrap() - .get_payload_by_root(&payload.tree_hash_root()) - .is_some()); self } @@ -4142,25 +4084,11 @@ impl ApiTester { .await .unwrap(); - let payload: FullPayload = match payload_type { - Full(payload) => payload - .data - .block() - .body() - .execution_payload() - .unwrap() - .into(), + match payload_type { + Full(_) => (), Blinded(_) => panic!("Expecting a full payload"), }; - // If this cache is populated, it indicates fallback to the local EE was correctly used. - assert!(self - .chain - .execution_layer - .as_ref() - .unwrap() - .get_payload_by_root(&payload.tree_hash_root()) - .is_some()); self } @@ -4223,25 +4151,11 @@ impl ApiTester { .await .unwrap(); - let payload: FullPayload = match payload_type { - Full(payload) => payload - .data - .block() - .body() - .execution_payload() - .unwrap() - .into(), + match payload_type { + Full(_) => (), Blinded(_) => panic!("Expecting a full payload"), }; - // If this cache is populated, it indicates fallback to the local EE was correctly used. - assert!(self - .chain - .execution_layer - .as_ref() - .unwrap() - .get_payload_by_root(&payload.tree_hash_root()) - .is_some()); self } @@ -4347,26 +4261,11 @@ impl ApiTester { .await .unwrap(); - let payload: BlindedPayload = match payload_type { - Blinded(payload) => payload - .data - .block() - .body() - .execution_payload() - .unwrap() - .into(), + match payload_type { + Blinded(_) => (), Full(_) => panic!("Expecting a blinded payload"), }; - // This cache should not be populated because fallback should not have been used. - assert!(self - .chain - .execution_layer - .as_ref() - .unwrap() - .get_payload_by_root(&payload.tree_hash_root()) - .is_none()); - // Without proposing, advance into the next slot, this should make us cross the threshold // number of skips, causing us to use the fallback. self.harness.advance_slot(); @@ -4382,26 +4281,11 @@ impl ApiTester { .await .unwrap(); - let payload: FullPayload = match payload_type { - Full(payload) => payload - .data - .block() - .body() - .execution_payload() - .unwrap() - .into(), + match payload_type { + Full(_) => (), Blinded(_) => panic!("Expecting a full payload"), }; - // If this cache is populated, it indicates fallback to the local EE was correctly used. - assert!(self - .chain - .execution_layer - .as_ref() - .unwrap() - .get_payload_by_root(&payload.tree_hash_root()) - .is_some()); - self } @@ -4527,26 +4411,11 @@ impl ApiTester { .await .unwrap(); - let payload: FullPayload = match payload_type { - Full(payload) => payload - .data - .block() - .body() - .execution_payload() - .unwrap() - .into(), + match payload_type { + Full(_) => (), Blinded(_) => panic!("Expecting a full payload"), }; - // If this cache is populated, it indicates fallback to the local EE was correctly used. - assert!(self - .chain - .execution_layer - .as_ref() - .unwrap() - .get_payload_by_root(&payload.tree_hash_root()) - .is_some()); - // Fill another epoch with blocks, should be enough to finalize. (Sneaky plus 1 because this // scenario starts at an epoch boundary). for _ in 0..E::slots_per_epoch() + 1 { @@ -4572,26 +4441,11 @@ impl ApiTester { .await .unwrap(); - let payload: BlindedPayload = match payload_type { - Blinded(payload) => payload - .data - .block() - .body() - .execution_payload() - .unwrap() - .into(), + match payload_type { + Blinded(_) => (), Full(_) => panic!("Expecting a blinded payload"), }; - // This cache should not be populated because fallback should not have been used. - assert!(self - .chain - .execution_layer - .as_ref() - .unwrap() - .get_payload_by_root(&payload.tree_hash_root()) - .is_none()); - self } @@ -4682,15 +4536,6 @@ impl ApiTester { let expected_fee_recipient = Address::from_low_u64_be(proposer_index as u64); assert_eq!(payload.fee_recipient(), expected_fee_recipient); - // If this cache is populated, it indicates fallback to the local EE was correctly used. - assert!(self - .chain - .execution_layer - .as_ref() - .unwrap() - .get_payload_by_root(&payload.tree_hash_root()) - .is_some()); - self } @@ -4751,25 +4596,11 @@ impl ApiTester { .await .unwrap(); - let payload: FullPayload = match payload_type { - Full(payload) => payload - .data - .block() - .body() - .execution_payload() - .unwrap() - .into(), + match payload_type { + Full(_) => (), Blinded(_) => panic!("Expecting a full payload"), }; - // If this cache is populated, it indicates fallback to the local EE was correctly used. - assert!(self - .chain - .execution_layer - .as_ref() - .unwrap() - .get_payload_by_root(&payload.tree_hash_root()) - .is_some()); self } @@ -4830,25 +4661,11 @@ impl ApiTester { .await .unwrap(); - let payload: BlindedPayload = match payload_type { - Blinded(payload) => payload - .data - .block() - .body() - .execution_payload() - .unwrap() - .into(), + match payload_type { + Blinded(_) => (), Full(_) => panic!("Expecting a blinded payload"), }; - // The builder's payload should've been chosen, so this cache should not be populated - assert!(self - .chain - .execution_layer - .as_ref() - .unwrap() - .get_payload_by_root(&payload.tree_hash_root()) - .is_none()); self } @@ -4909,25 +4726,11 @@ impl ApiTester { .await .unwrap(); - let payload: FullPayload = match payload_type { - Full(payload) => payload - .data - .block() - .body() - .execution_payload() - .unwrap() - .into(), + match payload_type { + Full(_) => (), Blinded(_) => panic!("Expecting a full payload"), }; - // The local payload should've been chosen, so this cache should be populated - assert!(self - .chain - .execution_layer - .as_ref() - .unwrap() - .get_payload_by_root(&payload.tree_hash_root()) - .is_some()); self } @@ -4988,25 +4791,11 @@ impl ApiTester { .await .unwrap(); - let payload: FullPayload = match payload_type { - Full(payload) => payload - .data - .block() - .body() - .execution_payload() - .unwrap() - .into(), + match payload_type { + Full(_) => (), Blinded(_) => panic!("Expecting a full payload"), }; - // The local payload should've been chosen, so this cache should be populated - assert!(self - .chain - .execution_layer - .as_ref() - .unwrap() - .get_payload_by_root(&payload.tree_hash_root()) - .is_some()); self } @@ -5059,26 +4848,22 @@ impl ApiTester { let epoch = self.chain.epoch().unwrap(); let (_, randao_reveal) = self.get_test_randao(slot, epoch).await; - let block_contents = self + let payload_type = self .client - .get_validator_blinded_blocks::>(slot, &randao_reveal, None) + .get_validator_blocks_v3::(slot, &randao_reveal, None) .await - .unwrap() - .data; - let (block, maybe_sidecars) = block_contents.deconstruct(); + .unwrap(); + + let block_contents = match payload_type { + Blinded(payload) => payload.data, + Full(_) => panic!("Expecting a blinded payload"), + }; + + let (_, maybe_sidecars) = block_contents.deconstruct(); // Response should contain blob sidecars assert!(maybe_sidecars.is_some()); - // The builder's payload should've been chosen, so this cache should not be populated - let payload: BlindedPayload = block.body().execution_payload().unwrap().into(); - assert!(self - .chain - .execution_layer - .as_ref() - .unwrap() - .get_payload_by_root(&payload.tree_hash_root()) - .is_none()); self } @@ -5122,6 +4907,38 @@ impl ApiTester { .is_some()); self } + + pub async fn test_lighthouse_rejects_invalid_withdrawals_root_v3(self) -> Self { + // Ensure builder payload *would be* chosen + self.mock_builder + .as_ref() + .unwrap() + .add_operation(Operation::Value(Uint256::from( + DEFAULT_MOCK_EL_PAYLOAD_VALUE_WEI + 1, + ))); + // Set withdrawals root to something invalid + self.mock_builder + .as_ref() + .unwrap() + .add_operation(Operation::WithdrawalsRoot(Hash256::repeat_byte(0x42))); + + let slot = self.chain.slot().unwrap(); + let epoch = self.chain.epoch().unwrap(); + let (_, randao_reveal) = self.get_test_randao(slot, epoch).await; + + let payload_type = self + .client + .get_validator_blocks_v3::(slot, &randao_reveal, None) + .await + .unwrap(); + + match payload_type { + Full(_) => (), + Blinded(_) => panic!("Expecting a full payload"), + }; + + self + } #[cfg(target_os = "linux")] pub async fn test_get_lighthouse_health(self) -> Self { @@ -6429,6 +6246,8 @@ async fn builder_works_post_deneb() { .test_post_validator_register_validator() .await .test_builder_works_post_deneb() + .await + .test_lighthouse_rejects_invalid_withdrawals_root_v3() .await; } From 98f159cc1884377279416f17823813bb9507a59d Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Mon, 20 Nov 2023 21:37:58 -0800 Subject: [PATCH 17/23] fmt --- beacon_node/http_api/tests/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beacon_node/http_api/tests/tests.rs b/beacon_node/http_api/tests/tests.rs index f8b13406d3..c0aa4fe58b 100644 --- a/beacon_node/http_api/tests/tests.rs +++ b/beacon_node/http_api/tests/tests.rs @@ -4907,7 +4907,7 @@ impl ApiTester { .is_some()); self } - + pub async fn test_lighthouse_rejects_invalid_withdrawals_root_v3(self) -> Self { // Ensure builder payload *would be* chosen self.mock_builder From b4556a3d621c39cb28ef10df9daec4ee935909b6 Mon Sep 17 00:00:00 2001 From: Alexander Uvizhev Date: Mon, 27 Nov 2023 07:39:37 +0300 Subject: [PATCH 18/23] Broadcast various requests as per #4684 (#4920) * multiple broadcast flags * rewrite with single --broadcast option * satisfy cargo fmt * shorten sync-committee-messages * fix a doc comment and a test * use strum * Add broadcast test to simulator * bring --disable-run-on-all flag back with deprecation notice --- Cargo.lock | 1 + lighthouse/tests/validator_client.rs | 76 ++++++++++++++++--- testing/node_test_rig/Cargo.toml | 2 +- testing/node_test_rig/src/lib.rs | 2 +- testing/simulator/src/eth1_sim.rs | 26 +++++-- testing/simulator/src/local_network.rs | 42 ++++++++++ validator_client/Cargo.toml | 1 + validator_client/src/attestation_service.rs | 5 +- validator_client/src/beacon_node_fallback.rs | 57 +++++++++++--- validator_client/src/block_service.rs | 28 ++++--- validator_client/src/cli.rs | 17 ++++- validator_client/src/config.rs | 30 ++++++-- validator_client/src/duties_service.rs | 5 +- validator_client/src/lib.rs | 5 +- validator_client/src/preparation_service.rs | 5 +- .../src/sync_committee_service.rs | 8 +- 16 files changed, 255 insertions(+), 55 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index af9d0a0ad8..21fea637f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8437,6 +8437,7 @@ dependencies = [ "slashing_protection", "slog", "slot_clock", + "strum", "sysinfo", "system_health", "task_executor", diff --git a/lighthouse/tests/validator_client.rs b/lighthouse/tests/validator_client.rs index a3b878ad21..32866cc4f2 100644 --- a/lighthouse/tests/validator_client.rs +++ b/lighthouse/tests/validator_client.rs @@ -1,4 +1,4 @@ -use validator_client::Config; +use validator_client::{ApiTopic, Config}; use crate::exec::CommandLineTestExec; use bls::{Keypair, PublicKeyBytes}; @@ -493,20 +493,78 @@ fn monitoring_endpoint() { assert_eq!(api_conf.update_period_secs, Some(30)); }); } -#[test] -fn disable_run_on_all_default() { - CommandLineTest::new().run().with_config(|config| { - assert!(!config.disable_run_on_all); - }); -} #[test] -fn disable_run_on_all() { +fn disable_run_on_all_flag() { CommandLineTest::new() .flag("disable-run-on-all", None) .run() .with_config(|config| { - assert!(config.disable_run_on_all); + assert_eq!(config.broadcast_topics, vec![]); + }); + // --broadcast flag takes precedence + CommandLineTest::new() + .flag("disable-run-on-all", None) + .flag("broadcast", Some("attestations")) + .run() + .with_config(|config| { + assert_eq!(config.broadcast_topics, vec![ApiTopic::Attestations]); + }); +} + +#[test] +fn no_broadcast_flag() { + CommandLineTest::new().run().with_config(|config| { + assert_eq!(config.broadcast_topics, vec![ApiTopic::Subscriptions]); + }); +} + +#[test] +fn broadcast_flag() { + // "none" variant + CommandLineTest::new() + .flag("broadcast", Some("none")) + .run() + .with_config(|config| { + assert_eq!(config.broadcast_topics, vec![]); + }); + // "none" with other values is ignored + CommandLineTest::new() + .flag("broadcast", Some("none,sync-committee")) + .run() + .with_config(|config| { + assert_eq!(config.broadcast_topics, vec![ApiTopic::SyncCommittee]); + }); + // Other valid variants + CommandLineTest::new() + .flag("broadcast", Some("blocks, subscriptions")) + .run() + .with_config(|config| { + assert_eq!( + config.broadcast_topics, + vec![ApiTopic::Blocks, ApiTopic::Subscriptions], + ); + }); + // Omitted "subscription" overrides default + CommandLineTest::new() + .flag("broadcast", Some("attestations")) + .run() + .with_config(|config| { + assert_eq!(config.broadcast_topics, vec![ApiTopic::Attestations]); + }); +} + +#[test] +#[should_panic(expected = "Unknown API topic")] +fn wrong_broadcast_flag() { + CommandLineTest::new() + .flag("broadcast", Some("foo, subscriptions")) + .run() + .with_config(|config| { + assert_eq!( + config.broadcast_topics, + vec![ApiTopic::Blocks, ApiTopic::Subscriptions], + ); }); } diff --git a/testing/node_test_rig/Cargo.toml b/testing/node_test_rig/Cargo.toml index 5fe820d15b..4696d8d2f1 100644 --- a/testing/node_test_rig/Cargo.toml +++ b/testing/node_test_rig/Cargo.toml @@ -11,7 +11,7 @@ types = { workspace = true } tempfile = { workspace = true } eth2 = { workspace = true } validator_client = { workspace = true } -validator_dir = { workspace = true } +validator_dir = { workspace = true, features = ["insecure_keys"] } sensitive_url = { workspace = true } execution_layer = { workspace = true } tokio = { workspace = true } diff --git a/testing/node_test_rig/src/lib.rs b/testing/node_test_rig/src/lib.rs index 3f08c83716..6c9af707f5 100644 --- a/testing/node_test_rig/src/lib.rs +++ b/testing/node_test_rig/src/lib.rs @@ -21,7 +21,7 @@ pub use eth2; pub use execution_layer::test_utils::{ Config as MockServerConfig, MockExecutionConfig, MockServer, }; -pub use validator_client::Config as ValidatorConfig; +pub use validator_client::{ApiTopic, Config as ValidatorConfig}; /// The global timeout for HTTP requests to the beacon node. const HTTP_TIMEOUT: Duration = Duration::from_secs(8); diff --git a/testing/simulator/src/eth1_sim.rs b/testing/simulator/src/eth1_sim.rs index 57c944cf1a..953dcf5822 100644 --- a/testing/simulator/src/eth1_sim.rs +++ b/testing/simulator/src/eth1_sim.rs @@ -10,7 +10,8 @@ use futures::prelude::*; use node_test_rig::environment::RuntimeContext; use node_test_rig::{ environment::{EnvironmentBuilder, LoggerConfig}, - testing_client_config, testing_validator_config, ClientConfig, ClientGenesis, ValidatorFiles, + testing_client_config, testing_validator_config, ApiTopic, ClientConfig, ClientGenesis, + ValidatorFiles, }; use rayon::prelude::*; use sensitive_url::SensitiveUrl; @@ -159,10 +160,25 @@ pub fn run_eth1_sim(matches: &ArgMatches) -> Result<(), String> { validator_config.fee_recipient = Some(SUGGESTED_FEE_RECIPIENT.into()); } println!("Adding validator client {}", i); - network_1 - .add_validator_client(validator_config, i, files, i % 2 == 0) - .await - .expect("should add validator"); + + // Enable broadcast on every 4th node. + if i % 4 == 0 { + validator_config.broadcast_topics = ApiTopic::all(); + let beacon_nodes = vec![i, (i + 1) % node_count]; + network_1 + .add_validator_client_with_fallbacks( + validator_config, + i, + beacon_nodes, + files, + ) + .await + } else { + network_1 + .add_validator_client(validator_config, i, files, i % 2 == 0) + .await + } + .expect("should add validator"); }, "vc", ); diff --git a/testing/simulator/src/local_network.rs b/testing/simulator/src/local_network.rs index 1024c46e49..dc8bf0d27d 100644 --- a/testing/simulator/src/local_network.rs +++ b/testing/simulator/src/local_network.rs @@ -270,6 +270,48 @@ impl LocalNetwork { Ok(()) } + pub async fn add_validator_client_with_fallbacks( + &self, + mut validator_config: ValidatorConfig, + validator_index: usize, + beacon_nodes: Vec, + validator_files: ValidatorFiles, + ) -> Result<(), String> { + let context = self + .context + .service_context(format!("validator_{}", validator_index)); + let self_1 = self.clone(); + let mut beacon_node_urls = vec![]; + for beacon_node in beacon_nodes { + let socket_addr = { + let read_lock = self.beacon_nodes.read(); + let beacon_node = read_lock + .get(beacon_node) + .ok_or_else(|| format!("No beacon node for index {}", beacon_node))?; + beacon_node + .client + .http_api_listen_addr() + .expect("Must have http started") + }; + let beacon_node = SensitiveUrl::parse( + format!("http://{}:{}", socket_addr.ip(), socket_addr.port()).as_str(), + ) + .unwrap(); + beacon_node_urls.push(beacon_node); + } + + validator_config.beacon_nodes = beacon_node_urls; + + let validator_client = LocalValidatorClient::production_with_insecure_keypairs( + context, + validator_config, + validator_files, + ) + .await?; + self_1.validator_clients.write().push(validator_client); + Ok(()) + } + /// For all beacon nodes in `Self`, return a HTTP client to access each nodes HTTP API. pub fn remote_nodes(&self) -> Result, String> { let beacon_nodes = self.beacon_nodes.read(); diff --git a/validator_client/Cargo.toml b/validator_client/Cargo.toml index 0b648a8155..90a82b7e3b 100644 --- a/validator_client/Cargo.toml +++ b/validator_client/Cargo.toml @@ -61,3 +61,4 @@ malloc_utils = { workspace = true } sysinfo = { workspace = true } system_health = { path = "../common/system_health" } logging = { workspace = true } +strum = { workspace = true } diff --git a/validator_client/src/attestation_service.rs b/validator_client/src/attestation_service.rs index b5bb6702ae..43b9d60e23 100644 --- a/validator_client/src/attestation_service.rs +++ b/validator_client/src/attestation_service.rs @@ -1,4 +1,4 @@ -use crate::beacon_node_fallback::{BeaconNodeFallback, RequireSynced}; +use crate::beacon_node_fallback::{ApiTopic, BeaconNodeFallback, RequireSynced}; use crate::{ duties_service::{DutiesService, DutyAndProof}, http_metrics::metrics, @@ -433,9 +433,10 @@ impl AttestationService { // Post the attestations to the BN. match self .beacon_nodes - .first_success( + .request( RequireSynced::No, OfflineOnFailure::Yes, + ApiTopic::Attestations, |beacon_node| async move { let _timer = metrics::start_timer_vec( &metrics::ATTESTATION_SERVICE_TIMES, diff --git a/validator_client/src/beacon_node_fallback.rs b/validator_client/src/beacon_node_fallback.rs index 3fce61a55a..23458d327b 100644 --- a/validator_client/src/beacon_node_fallback.rs +++ b/validator_client/src/beacon_node_fallback.rs @@ -7,6 +7,7 @@ use crate::http_metrics::metrics::{inc_counter_vec, ENDPOINT_ERRORS, ENDPOINT_RE use environment::RuntimeContext; use eth2::BeaconNodeHttpClient; use futures::future; +use serde::{Deserialize, Serialize}; use slog::{debug, error, info, warn, Logger}; use slot_clock::SlotClock; use std::fmt; @@ -15,6 +16,7 @@ use std::future::Future; use std::marker::PhantomData; use std::sync::Arc; use std::time::{Duration, Instant}; +use strum::{EnumString, EnumVariantNames}; use tokio::{sync::RwLock, time::sleep}; use types::{ChainSpec, Config, EthSpec}; @@ -330,7 +332,7 @@ impl CandidateBeaconNode { pub struct BeaconNodeFallback { candidates: Vec>, slot_clock: Option, - disable_run_on_all: bool, + broadcast_topics: Vec, spec: ChainSpec, log: Logger, } @@ -338,14 +340,14 @@ pub struct BeaconNodeFallback { impl BeaconNodeFallback { pub fn new( candidates: Vec>, - disable_run_on_all: bool, + broadcast_topics: Vec, spec: ChainSpec, log: Logger, ) -> Self { Self { candidates, slot_clock: None, - disable_run_on_all, + broadcast_topics, spec, log, } @@ -579,7 +581,7 @@ impl BeaconNodeFallback { /// It returns a list of errors along with the beacon node id that failed for `func`. /// Since this ignores the actual result of `func`, this function should only be used for beacon /// node calls whose results we do not care about, only that they completed successfully. - pub async fn run_on_all<'a, F, O, Err, R>( + pub async fn broadcast<'a, F, O, Err, R>( &'a self, require_synced: RequireSynced, offline_on_failure: OfflineOnFailure, @@ -687,11 +689,12 @@ impl BeaconNodeFallback { } /// Call `func` on first beacon node that returns success or on all beacon nodes - /// depending on the value of `disable_run_on_all`. - pub async fn run<'a, F, Err, R>( + /// depending on the `topic` and configuration. + pub async fn request<'a, F, Err, R>( &'a self, require_synced: RequireSynced, offline_on_failure: OfflineOnFailure, + topic: ApiTopic, func: F, ) -> Result<(), Errors> where @@ -699,13 +702,47 @@ impl BeaconNodeFallback { R: Future>, Err: Debug, { - if self.disable_run_on_all { + if self.broadcast_topics.contains(&topic) { + self.broadcast(require_synced, offline_on_failure, func) + .await + } else { self.first_success(require_synced, offline_on_failure, func) .await?; Ok(()) - } else { - self.run_on_all(require_synced, offline_on_failure, func) - .await } } } + +/// Serves as a cue for `BeaconNodeFallback` to tell which requests need to be broadcasted. +#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize, EnumString, EnumVariantNames)] +#[strum(serialize_all = "kebab-case")] +pub enum ApiTopic { + Attestations, + Blocks, + Subscriptions, + SyncCommittee, +} + +impl ApiTopic { + pub fn all() -> Vec { + use ApiTopic::*; + vec![Attestations, Blocks, Subscriptions, SyncCommittee] + } +} + +#[cfg(test)] +mod test { + use super::*; + use std::str::FromStr; + use strum::VariantNames; + + #[test] + fn api_topic_all() { + let all = ApiTopic::all(); + assert_eq!(all.len(), ApiTopic::VARIANTS.len()); + assert!(ApiTopic::VARIANTS + .iter() + .map(|topic| ApiTopic::from_str(topic).unwrap()) + .eq(all.into_iter())); + } +} diff --git a/validator_client/src/block_service.rs b/validator_client/src/block_service.rs index 3518bded9e..fbdfa1d685 100644 --- a/validator_client/src/block_service.rs +++ b/validator_client/src/block_service.rs @@ -1,6 +1,6 @@ use crate::beacon_node_fallback::{Error as FallbackError, Errors}; use crate::{ - beacon_node_fallback::{BeaconNodeFallback, RequireSynced}, + beacon_node_fallback::{ApiTopic, BeaconNodeFallback, RequireSynced}, determine_graffiti, graffiti_file::GraffitiFile, OfflineOnFailure, @@ -147,35 +147,41 @@ pub struct ProposerFallback { impl ProposerFallback { // Try `func` on `self.proposer_nodes` first. If that doesn't work, try `self.beacon_nodes`. - pub async fn first_success_try_proposers_first<'a, F, O, Err, R>( + pub async fn request_proposers_first<'a, F, Err, R>( &'a self, require_synced: RequireSynced, offline_on_failure: OfflineOnFailure, func: F, - ) -> Result> + ) -> Result<(), Errors> where F: Fn(&'a BeaconNodeHttpClient) -> R + Clone, - R: Future>, + R: Future>, Err: Debug, { // If there are proposer nodes, try calling `func` on them and return early if they are successful. if let Some(proposer_nodes) = &self.proposer_nodes { - if let Ok(result) = proposer_nodes - .first_success(require_synced, offline_on_failure, func.clone()) + if proposer_nodes + .request( + require_synced, + offline_on_failure, + ApiTopic::Blocks, + func.clone(), + ) .await + .is_ok() { - return Ok(result); + return Ok(()); } } // If the proposer nodes failed, try on the non-proposer nodes. self.beacon_nodes - .first_success(require_synced, offline_on_failure, func) + .request(require_synced, offline_on_failure, ApiTopic::Blocks, func) .await } // Try `func` on `self.beacon_nodes` first. If that doesn't work, try `self.proposer_nodes`. - pub async fn first_success_try_proposers_last<'a, F, O, Err, R>( + pub async fn request_proposers_last<'a, F, O, Err, R>( &'a self, require_synced: RequireSynced, offline_on_failure: OfflineOnFailure, @@ -476,7 +482,7 @@ impl BlockService { // Try the proposer nodes last, since it's likely that they don't have a // great view of attestations on the network. let block_contents = proposer_fallback - .first_success_try_proposers_last( + .request_proposers_last( RequireSynced::No, OfflineOnFailure::Yes, move |beacon_node| { @@ -569,7 +575,7 @@ impl BlockService { // protect them from DoS attacks and they're most likely to successfully // publish a block. proposer_fallback - .first_success_try_proposers_first( + .request_proposers_first( RequireSynced::No, OfflineOnFailure::Yes, |beacon_node| async { diff --git a/validator_client/src/cli.rs b/validator_client/src/cli.rs index 4ce4035175..69c97dea6b 100644 --- a/validator_client/src/cli.rs +++ b/validator_client/src/cli.rs @@ -26,15 +26,28 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { ) .takes_value(true), ) + // TODO remove this flag in a future release .arg( Arg::with_name("disable-run-on-all") .long("disable-run-on-all") .value_name("DISABLE_RUN_ON_ALL") - .help("By default, Lighthouse publishes attestation, sync committee subscriptions \ + .help("DEPRECATED. Use --broadcast. \ + By default, Lighthouse publishes attestation, sync committee subscriptions \ and proposer preparation messages to all beacon nodes provided in the \ `--beacon-nodes flag`. This option changes that behaviour such that these \ api calls only go out to the first available and synced beacon node") - .takes_value(false) + .takes_value(false), + ) + .arg( + Arg::with_name("broadcast") + .long("broadcast") + .value_name("API_TOPICS") + .help("Comma-separated list of beacon API topics to broadcast to all beacon nodes. \ + Possible values are: none, attestations, blocks, subscriptions, \ + sync-committee. Default (when flag is omitted) is to broadcast \ + subscriptions only." + ) + .takes_value(true), ) .arg( Arg::with_name("validators-dir") diff --git a/validator_client/src/config.rs b/validator_client/src/config.rs index c93fe6c141..26c2bccd3e 100644 --- a/validator_client/src/config.rs +++ b/validator_client/src/config.rs @@ -1,3 +1,4 @@ +use crate::beacon_node_fallback::ApiTopic; use crate::graffiti_file::GraffitiFile; use crate::{http_api, http_metrics}; use clap::ArgMatches; @@ -9,7 +10,7 @@ use directory::{ use eth2::types::Graffiti; use sensitive_url::SensitiveUrl; use serde::{Deserialize, Serialize}; -use slog::{info, Logger}; +use slog::{info, warn, Logger}; use std::fs; use std::net::IpAddr; use std::path::PathBuf; @@ -73,8 +74,8 @@ pub struct Config { /// /// This is *not* recommended in prod and should only be used for testing. pub block_delay: Option, - /// Disables publishing http api requests to all beacon nodes for select api calls. - pub disable_run_on_all: bool, + /// Enables broadcasting of various requests (by topic) to all beacon nodes. + pub broadcast_topics: Vec, /// Enables a service which attempts to measure latency between the VC and BNs. pub enable_latency_measurement_service: bool, /// Defines the number of validators per `validator/register_validator` request sent to the BN. @@ -117,7 +118,7 @@ impl Default for Config { builder_proposals: false, builder_registration_timestamp_override: None, gas_limit: None, - disable_run_on_all: false, + broadcast_topics: vec![ApiTopic::Subscriptions], enable_latency_measurement_service: true, validator_registration_batch_size: 500, } @@ -179,7 +180,6 @@ impl Config { .map_err(|e| format!("Unable to parse proposer node URL: {:?}", e))?; } - config.disable_run_on_all = cli_args.is_present("disable-run-on-all"); config.disable_auto_discover = cli_args.is_present("disable-auto-discover"); config.init_slashing_protection = cli_args.is_present("init-slashing-protection"); config.use_long_timeouts = cli_args.is_present("use-long-timeouts"); @@ -222,6 +222,26 @@ impl Config { config.beacon_nodes_tls_certs = Some(tls_certs.split(',').map(PathBuf::from).collect()); } + if cli_args.is_present("disable-run-on-all") { + warn!( + log, + "The --disable-run-on-all flag is deprecated"; + "msg" => "please use --broadcast instead" + ); + config.broadcast_topics = vec![]; + } + if let Some(broadcast_topics) = cli_args.value_of("broadcast") { + config.broadcast_topics = broadcast_topics + .split(',') + .filter(|t| *t != "none") + .map(|t| { + t.trim() + .parse::() + .map_err(|_| format!("Unknown API topic to broadcast: {t}")) + }) + .collect::>()?; + } + /* * Http API server */ diff --git a/validator_client/src/duties_service.rs b/validator_client/src/duties_service.rs index 9b9105a621..26747f8111 100644 --- a/validator_client/src/duties_service.rs +++ b/validator_client/src/duties_service.rs @@ -8,7 +8,7 @@ mod sync; -use crate::beacon_node_fallback::{BeaconNodeFallback, OfflineOnFailure, RequireSynced}; +use crate::beacon_node_fallback::{ApiTopic, BeaconNodeFallback, OfflineOnFailure, RequireSynced}; use crate::http_metrics::metrics::{get_int_gauge, set_int_gauge, ATTESTATION_DUTY}; use crate::{ block_service::BlockServiceNotification, @@ -700,9 +700,10 @@ async fn poll_beacon_attesters( let subscriptions_ref = &subscriptions; if let Err(e) = duties_service .beacon_nodes - .run( + .request( RequireSynced::No, OfflineOnFailure::Yes, + ApiTopic::Subscriptions, |beacon_node| async move { let _timer = metrics::start_timer_vec( &metrics::DUTIES_SERVICE_TIMES, diff --git a/validator_client/src/lib.rs b/validator_client/src/lib.rs index 6925e285fc..c020c14158 100644 --- a/validator_client/src/lib.rs +++ b/validator_client/src/lib.rs @@ -19,6 +19,7 @@ pub mod http_api; pub mod initialized_validators; pub mod validator_store; +pub use beacon_node_fallback::ApiTopic; pub use cli::cli_app; pub use config::Config; use initialized_validators::InitializedValidators; @@ -369,14 +370,14 @@ impl ProductionValidatorClient { let mut beacon_nodes: BeaconNodeFallback<_, T> = BeaconNodeFallback::new( candidates, - config.disable_run_on_all, + config.broadcast_topics.clone(), context.eth2_config.spec.clone(), log.clone(), ); let mut proposer_nodes: BeaconNodeFallback<_, T> = BeaconNodeFallback::new( proposer_candidates, - config.disable_run_on_all, + config.broadcast_topics.clone(), context.eth2_config.spec.clone(), log.clone(), ); diff --git a/validator_client/src/preparation_service.rs b/validator_client/src/preparation_service.rs index 2d2221680f..7aabc7d5ab 100644 --- a/validator_client/src/preparation_service.rs +++ b/validator_client/src/preparation_service.rs @@ -1,4 +1,4 @@ -use crate::beacon_node_fallback::{BeaconNodeFallback, RequireSynced}; +use crate::beacon_node_fallback::{ApiTopic, BeaconNodeFallback, RequireSynced}; use crate::validator_store::{DoppelgangerStatus, Error as ValidatorStoreError, ValidatorStore}; use crate::OfflineOnFailure; use bls::PublicKeyBytes; @@ -342,9 +342,10 @@ impl PreparationService { let preparation_entries = preparation_data.as_slice(); match self .beacon_nodes - .run( + .request( RequireSynced::No, OfflineOnFailure::Yes, + ApiTopic::Subscriptions, |beacon_node| async move { beacon_node .post_validator_prepare_beacon_proposer(preparation_entries) diff --git a/validator_client/src/sync_committee_service.rs b/validator_client/src/sync_committee_service.rs index 8e904e5dd1..90b62cd3b4 100644 --- a/validator_client/src/sync_committee_service.rs +++ b/validator_client/src/sync_committee_service.rs @@ -1,4 +1,4 @@ -use crate::beacon_node_fallback::{BeaconNodeFallback, RequireSynced}; +use crate::beacon_node_fallback::{ApiTopic, BeaconNodeFallback, RequireSynced}; use crate::{ duties_service::DutiesService, validator_store::{Error as ValidatorStoreError, ValidatorStore}, @@ -299,9 +299,10 @@ impl SyncCommitteeService { .collect::>(); self.beacon_nodes - .first_success( + .request( RequireSynced::No, OfflineOnFailure::Yes, + ApiTopic::SyncCommittee, |beacon_node| async move { beacon_node .post_beacon_pool_sync_committee_signatures(committee_signatures) @@ -594,9 +595,10 @@ impl SyncCommitteeService { if let Err(e) = self .beacon_nodes - .run( + .request( RequireSynced::No, OfflineOnFailure::Yes, + ApiTopic::Subscriptions, |beacon_node| async move { beacon_node .post_validator_sync_committee_subscriptions(subscriptions_slice) From c88cb371a00ccbab85355dcd5efbc2109038207c Mon Sep 17 00:00:00 2001 From: Jimmy Chen Date: Mon, 27 Nov 2023 23:07:40 +1100 Subject: [PATCH 19/23] Remove `MAX_BLOBS_PER_BLOCK` from Holesky config. (#4955) --- .../built_in_network_configs/holesky/config.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/common/eth2_network_config/built_in_network_configs/holesky/config.yaml b/common/eth2_network_config/built_in_network_configs/holesky/config.yaml index 9f4faeb27a..d699b9a39f 100644 --- a/common/eth2_network_config/built_in_network_configs/holesky/config.yaml +++ b/common/eth2_network_config/built_in_network_configs/holesky/config.yaml @@ -111,5 +111,3 @@ MAX_REQUEST_BLOB_SIDECARS: 768 MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: 4096 # `6` BLOB_SIDECAR_SUBNET_COUNT: 6 -# `uint64(6)` -MAX_BLOBS_PER_BLOCK: 6 From 44c1817c2b417ca99cd4d34126f7e26d1b3d3874 Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Tue, 28 Nov 2023 16:00:28 +1100 Subject: [PATCH 20/23] Remove block-delay-ms (#4956) --- lighthouse/tests/validator_client.rs | 18 ------------------ validator_client/src/block_service.rs | 23 +---------------------- validator_client/src/cli.rs | 12 ------------ validator_client/src/config.rs | 13 ------------- validator_client/src/lib.rs | 3 +-- 5 files changed, 2 insertions(+), 67 deletions(-) diff --git a/lighthouse/tests/validator_client.rs b/lighthouse/tests/validator_client.rs index 32866cc4f2..4234de613d 100644 --- a/lighthouse/tests/validator_client.rs +++ b/lighthouse/tests/validator_client.rs @@ -422,24 +422,6 @@ fn no_doppelganger_protection_flag() { .with_config(|config| assert!(!config.enable_doppelganger_protection)); } #[test] -fn block_delay_ms() { - CommandLineTest::new() - .flag("block-delay-ms", Some("2000")) - .run() - .with_config(|config| { - assert_eq!( - config.block_delay, - Some(std::time::Duration::from_millis(2000)) - ) - }); -} -#[test] -fn no_block_delay_ms() { - CommandLineTest::new() - .run() - .with_config(|config| assert_eq!(config.block_delay, None)); -} -#[test] fn no_gas_limit_flag() { CommandLineTest::new() .run() diff --git a/validator_client/src/block_service.rs b/validator_client/src/block_service.rs index fbdfa1d685..47f16fc415 100644 --- a/validator_client/src/block_service.rs +++ b/validator_client/src/block_service.rs @@ -21,7 +21,6 @@ use std::ops::Deref; use std::sync::Arc; use std::time::Duration; use tokio::sync::mpsc; -use tokio::time::sleep; use types::{ AbstractExecPayload, BlindedPayload, BlockType, EthSpec, FullPayload, Graffiti, PublicKeyBytes, Slot, @@ -57,7 +56,6 @@ pub struct BlockServiceBuilder { context: Option>, graffiti: Option, graffiti_file: Option, - block_delay: Option, } impl BlockServiceBuilder { @@ -70,7 +68,6 @@ impl BlockServiceBuilder { context: None, graffiti: None, graffiti_file: None, - block_delay: None, } } @@ -109,11 +106,6 @@ impl BlockServiceBuilder { self } - pub fn block_delay(mut self, block_delay: Option) -> Self { - self.block_delay = block_delay; - self - } - pub fn build(self) -> Result, String> { Ok(BlockService { inner: Arc::new(Inner { @@ -132,7 +124,6 @@ impl BlockServiceBuilder { proposer_nodes: self.proposer_nodes, graffiti: self.graffiti, graffiti_file: self.graffiti_file, - block_delay: self.block_delay, }), }) } @@ -222,7 +213,6 @@ pub struct Inner { context: RuntimeContext, graffiti: Option, graffiti_file: Option, - block_delay: Option, } /// Attempts to produce attestations for any block producer(s) at the start of the epoch. @@ -266,18 +256,7 @@ impl BlockService { executor.spawn( async move { while let Some(notif) = notification_rx.recv().await { - let service = self.clone(); - - if let Some(delay) = service.block_delay { - debug!( - service.context.log(), - "Delaying block production by {}ms", - delay.as_millis() - ); - sleep(delay).await; - } - - service.do_update(notif).await.ok(); + self.do_update(notif).await.ok(); } debug!(log, "Block service shutting down"); }, diff --git a/validator_client/src/cli.rs b/validator_client/src/cli.rs index 69c97dea6b..6957934fb8 100644 --- a/validator_client/src/cli.rs +++ b/validator_client/src/cli.rs @@ -340,16 +340,4 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { .default_value("500") .takes_value(true), ) - /* - * Experimental/development options. - */ - .arg( - Arg::with_name("block-delay-ms") - .long("block-delay-ms") - .value_name("MILLIS") - .hidden(true) - .help("Time to delay block production from the start of the slot. Should only be \ - used for testing.") - .takes_value(true), - ) } diff --git a/validator_client/src/config.rs b/validator_client/src/config.rs index 26c2bccd3e..95d42d6d83 100644 --- a/validator_client/src/config.rs +++ b/validator_client/src/config.rs @@ -14,7 +14,6 @@ use slog::{info, warn, Logger}; use std::fs; use std::net::IpAddr; use std::path::PathBuf; -use std::time::Duration; use types::{Address, GRAFFITI_BYTES_LEN}; pub const DEFAULT_BEACON_NODE: &str = "http://localhost:5052/"; @@ -70,10 +69,6 @@ pub struct Config { /// A list of custom certificates that the validator client will additionally use when /// connecting to a beacon node over SSL/TLS. pub beacon_nodes_tls_certs: Option>, - /// Delay from the start of the slot to wait before publishing a block. - /// - /// This is *not* recommended in prod and should only be used for testing. - pub block_delay: Option, /// Enables broadcasting of various requests (by topic) to all beacon nodes. pub broadcast_topics: Vec, /// Enables a service which attempts to measure latency between the VC and BNs. @@ -114,7 +109,6 @@ impl Default for Config { enable_doppelganger_protection: false, enable_high_validator_count_metrics: false, beacon_nodes_tls_certs: None, - block_delay: None, builder_proposals: false, builder_registration_timestamp_override: None, gas_limit: None, @@ -373,13 +367,6 @@ impl Config { return Err("validator-registration-batch-size cannot be 0".to_string()); } - /* - * Experimental - */ - if let Some(delay_ms) = parse_optional::(cli_args, "block-delay-ms")? { - config.block_delay = Some(Duration::from_millis(delay_ms)); - } - Ok(config) } } diff --git a/validator_client/src/lib.rs b/validator_client/src/lib.rs index c020c14158..d52247df4d 100644 --- a/validator_client/src/lib.rs +++ b/validator_client/src/lib.rs @@ -472,8 +472,7 @@ impl ProductionValidatorClient { .beacon_nodes(beacon_nodes.clone()) .runtime_context(context.service_context("block".into())) .graffiti(config.graffiti) - .graffiti_file(config.graffiti_file.clone()) - .block_delay(config.block_delay); + .graffiti_file(config.graffiti_file.clone()); // If we have proposer nodes, add them to the block service builder. if proposer_nodes_num > 0 { From 8a599ec7dc978f8d2a49100e3d712794b56eba2e Mon Sep 17 00:00:00 2001 From: GeemoCandama <104614073+GeemoCandama@users.noreply.github.com> Date: Tue, 28 Nov 2023 00:14:29 -0600 Subject: [PATCH 21/23] API for `LightClientBootstrap`, `LightClientFinalityUpdate`, `LightClientOptimisticUpdate` and light client events (#3954) * rebase and add comment * conditional test * test * optimistic chould be working now * finality should be working now * try again * try again * clippy fix * add lc bootstrap beacon api * add lc optimistic/finality update to events * fmt * That error isn't occuring on my computer but I think this should fix it * Add missing test file * Update light client types to comply with Altair light client spec. * Fix test compilation * Support deserializing light client structures for the Bellatrix fork * Move `get_light_client_bootstrap` logic to `BeaconChain`. `LightClientBootstrap` API to return `ForkVersionedResponse`. * Misc fixes. - log cleanup - move http_api config mutation to `config::get_config` for consistency - fix light client API responses * Add light client bootstrap API test and fix existing ones. * Fix test for `light-client-server` http api config. * Appease clippy * Efficiency improvement when retrieving beacon state. --------- Co-authored-by: Jimmy Chen --- beacon_node/beacon_chain/src/beacon_chain.rs | 33 +++ beacon_node/beacon_chain/src/errors.rs | 2 + beacon_node/beacon_chain/src/events.rs | 22 ++ ...ght_client_finality_update_verification.rs | 4 +- ...t_client_optimistic_update_verification.rs | 5 +- beacon_node/http_api/src/lib.rs | 197 +++++++++++++++++- beacon_node/http_api/src/test_utils.rs | 1 + beacon_node/http_api/tests/tests.rs | 89 ++++++++ .../src/rpc/codec/ssz_snappy.rs | 7 +- .../lighthouse_network/src/rpc/methods.rs | 10 +- .../src/service/api_types.rs | 3 +- .../network_beacon_processor/rpc_methods.rs | 80 ++----- beacon_node/src/config.rs | 3 + common/eth2/src/lib.rs | 53 +++++ common/eth2/src/types.rs | 26 +++ .../state_processing/src/upgrade/altair.rs | 2 +- consensus/types/src/lib.rs | 4 + consensus/types/src/light_client_bootstrap.rs | 39 +++- .../types/src/light_client_finality_update.rs | 39 +++- consensus/types/src/light_client_header.rs | 26 +++ .../src/light_client_optimistic_update.rs | 30 ++- consensus/types/src/light_client_update.rs | 36 +++- consensus/types/src/sync_committee.rs | 12 +- lighthouse/tests/beacon_node.rs | 20 +- 24 files changed, 632 insertions(+), 111 deletions(-) create mode 100644 consensus/types/src/light_client_header.rs diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 168bbfca49..6b893d6967 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -6446,6 +6446,39 @@ impl BeaconChain { pub fn data_availability_boundary(&self) -> Option { self.data_availability_checker.data_availability_boundary() } + + /// Gets the `LightClientBootstrap` object for a requested block root. + /// + /// Returns `None` when the state or block is not found in the database. + #[allow(clippy::type_complexity)] + pub fn get_light_client_bootstrap( + &self, + block_root: &Hash256, + ) -> Result, ForkName)>, Error> { + let Some((state_root, slot)) = self + .get_blinded_block(block_root)? + .map(|block| (block.state_root(), block.slot())) + else { + return Ok(None); + }; + + let Some(mut state) = self.get_state(&state_root, Some(slot))? else { + return Ok(None); + }; + + let fork_name = state + .fork_name(&self.spec) + .map_err(Error::InconsistentFork)?; + + match fork_name { + ForkName::Altair | ForkName::Merge => { + LightClientBootstrap::from_beacon_state(&mut state) + .map(|bootstrap| Some((bootstrap, fork_name))) + .map_err(Error::LightClientError) + } + ForkName::Base | ForkName::Capella | ForkName::Deneb => Err(Error::UnsupportedFork), + } + } } impl Drop for BeaconChain { diff --git a/beacon_node/beacon_chain/src/errors.rs b/beacon_node/beacon_chain/src/errors.rs index 7c1bb04917..9c1ba06f85 100644 --- a/beacon_node/beacon_chain/src/errors.rs +++ b/beacon_node/beacon_chain/src/errors.rs @@ -221,6 +221,8 @@ pub enum BeaconChainError { ProposerHeadForkChoiceError(fork_choice::Error), UnableToPublish, AvailabilityCheckError(AvailabilityCheckError), + LightClientError(LightClientError), + UnsupportedFork, } easy_from_to!(SlotProcessingError, BeaconChainError); diff --git a/beacon_node/beacon_chain/src/events.rs b/beacon_node/beacon_chain/src/events.rs index 8d3a682794..0e5dfc8059 100644 --- a/beacon_node/beacon_chain/src/events.rs +++ b/beacon_node/beacon_chain/src/events.rs @@ -17,6 +17,8 @@ pub struct ServerSentEventHandler { contribution_tx: Sender>, payload_attributes_tx: Sender>, late_head: Sender>, + light_client_finality_update_tx: Sender>, + light_client_optimistic_update_tx: Sender>, block_reward_tx: Sender>, log: Logger, } @@ -40,6 +42,8 @@ impl ServerSentEventHandler { let (contribution_tx, _) = broadcast::channel(capacity); let (payload_attributes_tx, _) = broadcast::channel(capacity); let (late_head, _) = broadcast::channel(capacity); + let (light_client_finality_update_tx, _) = broadcast::channel(capacity); + let (light_client_optimistic_update_tx, _) = broadcast::channel(capacity); let (block_reward_tx, _) = broadcast::channel(capacity); Self { @@ -53,6 +57,8 @@ impl ServerSentEventHandler { contribution_tx, payload_attributes_tx, late_head, + light_client_finality_update_tx, + light_client_optimistic_update_tx, block_reward_tx, log, } @@ -108,6 +114,14 @@ impl ServerSentEventHandler { .late_head .send(kind) .map(|count| log_count("late head", count)), + EventKind::LightClientFinalityUpdate(_) => self + .light_client_finality_update_tx + .send(kind) + .map(|count| log_count("light client finality update", count)), + EventKind::LightClientOptimisticUpdate(_) => self + .light_client_optimistic_update_tx + .send(kind) + .map(|count| log_count("light client optimistic update", count)), EventKind::BlockReward(_) => self .block_reward_tx .send(kind) @@ -158,6 +172,14 @@ impl ServerSentEventHandler { self.late_head.subscribe() } + pub fn subscribe_light_client_finality_update(&self) -> Receiver> { + self.light_client_finality_update_tx.subscribe() + } + + pub fn subscribe_light_client_optimistic_update(&self) -> Receiver> { + self.light_client_optimistic_update_tx.subscribe() + } + pub fn subscribe_block_reward(&self) -> Receiver> { self.block_reward_tx.subscribe() } diff --git a/beacon_node/beacon_chain/src/light_client_finality_update_verification.rs b/beacon_node/beacon_chain/src/light_client_finality_update_verification.rs index 86cc6695ed..791d63ccfe 100644 --- a/beacon_node/beacon_chain/src/light_client_finality_update_verification.rs +++ b/beacon_node/beacon_chain/src/light_client_finality_update_verification.rs @@ -67,7 +67,7 @@ impl VerifiedLightClientFinalityUpdate { chain: &BeaconChain, seen_timestamp: Duration, ) -> Result { - let gossiped_finality_slot = light_client_finality_update.finalized_header.slot; + let gossiped_finality_slot = light_client_finality_update.finalized_header.beacon.slot; let one_third_slot_duration = Duration::new(chain.spec.seconds_per_slot / 3, 0); let signature_slot = light_client_finality_update.signature_slot; let start_time = chain.slot_clock.start_of(signature_slot); @@ -88,7 +88,7 @@ impl VerifiedLightClientFinalityUpdate { .get_blinded_block(&finalized_block_root)? .ok_or(Error::FailedConstructingUpdate)?; let latest_seen_finality_update_slot = match latest_seen_finality_update.as_ref() { - Some(update) => update.finalized_header.slot, + Some(update) => update.finalized_header.beacon.slot, None => Slot::new(0), }; diff --git a/beacon_node/beacon_chain/src/light_client_optimistic_update_verification.rs b/beacon_node/beacon_chain/src/light_client_optimistic_update_verification.rs index 8b7e644527..374cc9a775 100644 --- a/beacon_node/beacon_chain/src/light_client_optimistic_update_verification.rs +++ b/beacon_node/beacon_chain/src/light_client_optimistic_update_verification.rs @@ -71,7 +71,7 @@ impl VerifiedLightClientOptimisticUpdate { chain: &BeaconChain, seen_timestamp: Duration, ) -> Result { - let gossiped_optimistic_slot = light_client_optimistic_update.attested_header.slot; + let gossiped_optimistic_slot = light_client_optimistic_update.attested_header.beacon.slot; let one_third_slot_duration = Duration::new(chain.spec.seconds_per_slot / 3, 0); let signature_slot = light_client_optimistic_update.signature_slot; let start_time = chain.slot_clock.start_of(signature_slot); @@ -88,7 +88,7 @@ impl VerifiedLightClientOptimisticUpdate { .get_state(&attested_block.state_root(), Some(attested_block.slot()))? .ok_or(Error::FailedConstructingUpdate)?; let latest_seen_optimistic_update_slot = match latest_seen_optimistic_update.as_ref() { - Some(update) => update.attested_header.slot, + Some(update) => update.attested_header.beacon.slot, None => Slot::new(0), }; @@ -114,6 +114,7 @@ impl VerifiedLightClientOptimisticUpdate { // otherwise queue let canonical_root = light_client_optimistic_update .attested_header + .beacon .canonical_root(); if canonical_root != head_block.message().parent_root() { diff --git a/beacon_node/http_api/src/lib.rs b/beacon_node/http_api/src/lib.rs index a6f9d9ffce..5b00a80bdf 100644 --- a/beacon_node/http_api/src/lib.rs +++ b/beacon_node/http_api/src/lib.rs @@ -77,9 +77,10 @@ use tokio_stream::{ use types::{ Attestation, AttestationData, AttestationShufflingId, AttesterSlashing, BeaconStateError, BlindedPayload, CommitteeCache, ConfigAndPreset, Epoch, EthSpec, ForkName, - ProposerPreparationData, ProposerSlashing, RelativeEpoch, SignedAggregateAndProof, - SignedBlsToExecutionChange, SignedContributionAndProof, SignedValidatorRegistrationData, - SignedVoluntaryExit, Slot, SyncCommitteeMessage, SyncContributionData, + ForkVersionedResponse, Hash256, ProposerPreparationData, ProposerSlashing, RelativeEpoch, + SignedAggregateAndProof, SignedBlsToExecutionChange, SignedContributionAndProof, + SignedValidatorRegistrationData, SignedVoluntaryExit, Slot, SyncCommitteeMessage, + SyncContributionData, }; use validator::pubkey_to_validator_index; use version::{ @@ -143,6 +144,7 @@ pub struct Config { pub enable_beacon_processor: bool, #[serde(with = "eth2::types::serde_status_code")] pub duplicate_block_status_code: StatusCode, + pub enable_light_client_server: bool, } impl Default for Config { @@ -159,6 +161,7 @@ impl Default for Config { sse_capacity_multiplier: 1, enable_beacon_processor: true, duplicate_block_status_code: StatusCode::ACCEPTED, + enable_light_client_server: false, } } } @@ -279,6 +282,18 @@ pub fn prometheus_metrics() -> warp::filters::log::Log impl Filter + Clone { + warp::any() + .and_then(move || async move { + if is_enabled { + Ok(()) + } else { + Err(warp::reject::not_found()) + } + }) + .untuple_one() +} + /// Creates a server that will serve requests using information from `ctx`. /// /// The server will shut down gracefully when the `shutdown` future resolves. @@ -2379,6 +2394,164 @@ pub fn serve( }, ); + /* + * beacon/light_client + */ + + let beacon_light_client_path = eth_v1 + .and(warp::path("beacon")) + .and(warp::path("light_client")) + .and(chain_filter.clone()); + + // GET beacon/light_client/bootstrap/{block_root} + let get_beacon_light_client_bootstrap = beacon_light_client_path + .clone() + .and(task_spawner_filter.clone()) + .and(warp::path("bootstrap")) + .and(warp::path::param::().or_else(|_| async { + Err(warp_utils::reject::custom_bad_request( + "Invalid block root value".to_string(), + )) + })) + .and(warp::path::end()) + .and(warp::header::optional::("accept")) + .then( + |chain: Arc>, + task_spawner: TaskSpawner, + block_root: Hash256, + accept_header: Option| { + task_spawner.blocking_response_task(Priority::P1, move || { + let (bootstrap, fork_name) = match chain.get_light_client_bootstrap(&block_root) + { + Ok(Some(res)) => res, + Ok(None) => { + return Err(warp_utils::reject::custom_not_found( + "Light client bootstrap unavailable".to_string(), + )); + } + Err(e) => { + return Err(warp_utils::reject::custom_server_error(format!( + "Unable to obtain LightClientBootstrap instance: {e:?}" + ))); + } + }; + + match accept_header { + Some(api_types::Accept::Ssz) => Response::builder() + .status(200) + .header("Content-Type", "application/octet-stream") + .body(bootstrap.as_ssz_bytes().into()) + .map_err(|e| { + warp_utils::reject::custom_server_error(format!( + "failed to create response: {}", + e + )) + }), + _ => Ok(warp::reply::json(&ForkVersionedResponse { + version: Some(fork_name), + data: bootstrap, + }) + .into_response()), + } + .map(|resp| add_consensus_version_header(resp, fork_name)) + }) + }, + ); + + // GET beacon/light_client/optimistic_update + let get_beacon_light_client_optimistic_update = beacon_light_client_path + .clone() + .and(task_spawner_filter.clone()) + .and(warp::path("optimistic_update")) + .and(warp::path::end()) + .and(warp::header::optional::("accept")) + .then( + |chain: Arc>, + task_spawner: TaskSpawner, + accept_header: Option| { + task_spawner.blocking_response_task(Priority::P1, move || { + let update = chain + .latest_seen_optimistic_update + .lock() + .clone() + .ok_or_else(|| { + warp_utils::reject::custom_not_found( + "No LightClientOptimisticUpdate is available".to_string(), + ) + })?; + + let fork_name = chain + .spec + .fork_name_at_slot::(update.signature_slot); + match accept_header { + Some(api_types::Accept::Ssz) => Response::builder() + .status(200) + .header("Content-Type", "application/octet-stream") + .body(update.as_ssz_bytes().into()) + .map_err(|e| { + warp_utils::reject::custom_server_error(format!( + "failed to create response: {}", + e + )) + }), + _ => Ok(warp::reply::json(&ForkVersionedResponse { + version: Some(fork_name), + data: update, + }) + .into_response()), + } + .map(|resp| add_consensus_version_header(resp, fork_name)) + }) + }, + ); + + // GET beacon/light_client/finality_update + let get_beacon_light_client_finality_update = beacon_light_client_path + .clone() + .and(task_spawner_filter.clone()) + .and(warp::path("finality_update")) + .and(warp::path::end()) + .and(warp::header::optional::("accept")) + .then( + |chain: Arc>, + task_spawner: TaskSpawner, + accept_header: Option| { + task_spawner.blocking_response_task(Priority::P1, move || { + let update = chain + .latest_seen_finality_update + .lock() + .clone() + .ok_or_else(|| { + warp_utils::reject::custom_not_found( + "No LightClientFinalityUpdate is available".to_string(), + ) + })?; + + let fork_name = chain + .spec + .fork_name_at_slot::(update.signature_slot); + match accept_header { + Some(api_types::Accept::Ssz) => Response::builder() + .status(200) + .header("Content-Type", "application/octet-stream") + .body(update.as_ssz_bytes().into()) + .map_err(|e| { + warp_utils::reject::custom_server_error(format!( + "failed to create response: {}", + e + )) + }), + _ => Ok(warp::reply::json(&ForkVersionedResponse { + version: Some(fork_name), + data: update, + }) + .into_response()), + } + .map(|resp| add_consensus_version_header(resp, fork_name)) + }) + }, + ); + /* * beacon/rewards */ @@ -4339,6 +4512,12 @@ pub fn serve( api_types::EventTopic::LateHead => { event_handler.subscribe_late_head() } + api_types::EventTopic::LightClientFinalityUpdate => { + event_handler.subscribe_light_client_finality_update() + } + api_types::EventTopic::LightClientOptimisticUpdate => { + event_handler.subscribe_light_client_optimistic_update() + } api_types::EventTopic::BlockReward => { event_handler.subscribe_block_reward() } @@ -4492,6 +4671,18 @@ pub fn serve( .uor(get_lighthouse_database_info) .uor(get_lighthouse_block_rewards) .uor(get_lighthouse_attestation_performance) + .uor( + enable(ctx.config.enable_light_client_server) + .and(get_beacon_light_client_optimistic_update), + ) + .uor( + enable(ctx.config.enable_light_client_server) + .and(get_beacon_light_client_finality_update), + ) + .uor( + enable(ctx.config.enable_light_client_server) + .and(get_beacon_light_client_bootstrap), + ) .uor(get_lighthouse_block_packing_efficiency) .uor(get_lighthouse_merge_readiness) .uor(get_events) diff --git a/beacon_node/http_api/src/test_utils.rs b/beacon_node/http_api/src/test_utils.rs index fe47e56dc5..bafb573819 100644 --- a/beacon_node/http_api/src/test_utils.rs +++ b/beacon_node/http_api/src/test_utils.rs @@ -209,6 +209,7 @@ pub async fn create_api_server( enabled: true, listen_port: port, data_dir: std::path::PathBuf::from(DEFAULT_ROOT_DIR), + enable_light_client_server: true, ..Config::default() }, chain: Some(chain), diff --git a/beacon_node/http_api/tests/tests.rs b/beacon_node/http_api/tests/tests.rs index c0aa4fe58b..39e6225065 100644 --- a/beacon_node/http_api/tests/tests.rs +++ b/beacon_node/http_api/tests/tests.rs @@ -1646,6 +1646,59 @@ impl ApiTester { self } + pub async fn test_get_beacon_light_client_bootstrap(self) -> Self { + let block_id = BlockId(CoreBlockId::Finalized); + let (block_root, _, _) = block_id.root(&self.chain).unwrap(); + let (block, _, _) = block_id.full_block(&self.chain).await.unwrap(); + + let result = match self + .client + .get_light_client_bootstrap::(block_root) + .await + { + Ok(result) => result.unwrap().data, + Err(e) => panic!("query failed incorrectly: {e:?}"), + }; + + let expected = block.slot(); + assert_eq!(result.header.beacon.slot, expected); + + self + } + + pub async fn test_get_beacon_light_client_optimistic_update(self) -> Self { + // get_beacon_light_client_optimistic_update returns Ok(None) on 404 NOT FOUND + let result = match self + .client + .get_beacon_light_client_optimistic_update::() + .await + { + Ok(result) => result.map(|res| res.data), + Err(e) => panic!("query failed incorrectly: {e:?}"), + }; + + let expected = self.chain.latest_seen_optimistic_update.lock().clone(); + assert_eq!(result, expected); + + self + } + + pub async fn test_get_beacon_light_client_finality_update(self) -> Self { + let result = match self + .client + .get_beacon_light_client_finality_update::() + .await + { + Ok(result) => result.map(|res| res.data), + Err(e) => panic!("query failed incorrectly: {e:?}"), + }; + + let expected = self.chain.latest_seen_finality_update.lock().clone(); + assert_eq!(result, expected); + + self + } + pub async fn test_get_beacon_pool_attestations(self) -> Self { let result = self .client @@ -5701,6 +5754,42 @@ async fn node_get() { .await; } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn get_light_client_bootstrap() { + let config = ApiTesterConfig { + spec: ForkName::Altair.make_genesis_spec(E::default_spec()), + ..<_>::default() + }; + ApiTester::new_from_config(config) + .await + .test_get_beacon_light_client_bootstrap() + .await; +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn get_light_client_optimistic_update() { + let config = ApiTesterConfig { + spec: ForkName::Altair.make_genesis_spec(E::default_spec()), + ..<_>::default() + }; + ApiTester::new_from_config(config) + .await + .test_get_beacon_light_client_optimistic_update() + .await; +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn get_light_client_finality_update() { + let config = ApiTesterConfig { + spec: ForkName::Altair.make_genesis_spec(E::default_spec()), + ..<_>::default() + }; + ApiTester::new_from_config(config) + .await + .test_get_beacon_light_client_finality_update() + .await; +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn get_validator_duties_early() { ApiTester::new() diff --git a/beacon_node/lighthouse_network/src/rpc/codec/ssz_snappy.rs b/beacon_node/lighthouse_network/src/rpc/codec/ssz_snappy.rs index 6e83cfc86d..787c3dcb7a 100644 --- a/beacon_node/lighthouse_network/src/rpc/codec/ssz_snappy.rs +++ b/beacon_node/lighthouse_network/src/rpc/codec/ssz_snappy.rs @@ -15,11 +15,10 @@ use std::io::{Read, Write}; use std::marker::PhantomData; use std::sync::Arc; use tokio_util::codec::{Decoder, Encoder}; -use types::{light_client_bootstrap::LightClientBootstrap, BlobSidecar}; use types::{ - EthSpec, ForkContext, ForkName, Hash256, SignedBeaconBlock, SignedBeaconBlockAltair, - SignedBeaconBlockBase, SignedBeaconBlockCapella, SignedBeaconBlockDeneb, - SignedBeaconBlockMerge, + BlobSidecar, EthSpec, ForkContext, ForkName, Hash256, LightClientBootstrap, SignedBeaconBlock, + SignedBeaconBlockAltair, SignedBeaconBlockBase, SignedBeaconBlockCapella, + SignedBeaconBlockDeneb, SignedBeaconBlockMerge, }; use unsigned_varint::codec::Uvi; diff --git a/beacon_node/lighthouse_network/src/rpc/methods.rs b/beacon_node/lighthouse_network/src/rpc/methods.rs index 438c2c7544..627c871c47 100644 --- a/beacon_node/lighthouse_network/src/rpc/methods.rs +++ b/beacon_node/lighthouse_network/src/rpc/methods.rs @@ -17,8 +17,8 @@ use superstruct::superstruct; use types::blob_sidecar::BlobIdentifier; use types::consts::deneb::MAX_BLOBS_PER_BLOCK; use types::{ - blob_sidecar::BlobSidecar, light_client_bootstrap::LightClientBootstrap, Epoch, EthSpec, - Hash256, SignedBeaconBlock, Slot, + blob_sidecar::BlobSidecar, Epoch, EthSpec, Hash256, LightClientBootstrap, SignedBeaconBlock, + Slot, }; /// Maximum number of blocks in a single request. @@ -571,7 +571,11 @@ impl std::fmt::Display for RPCResponse { RPCResponse::Pong(ping) => write!(f, "Pong: {}", ping.data), RPCResponse::MetaData(metadata) => write!(f, "Metadata: {}", metadata.seq_number()), RPCResponse::LightClientBootstrap(bootstrap) => { - write!(f, "LightClientBootstrap Slot: {}", bootstrap.header.slot) + write!( + f, + "LightClientBootstrap Slot: {}", + bootstrap.header.beacon.slot + ) } } } diff --git a/beacon_node/lighthouse_network/src/service/api_types.rs b/beacon_node/lighthouse_network/src/service/api_types.rs index 6ede0666a3..96c9d28332 100644 --- a/beacon_node/lighthouse_network/src/service/api_types.rs +++ b/beacon_node/lighthouse_network/src/service/api_types.rs @@ -1,8 +1,7 @@ use std::sync::Arc; use libp2p::swarm::ConnectionId; -use types::light_client_bootstrap::LightClientBootstrap; -use types::{BlobSidecar, EthSpec, SignedBeaconBlock}; +use types::{BlobSidecar, EthSpec, LightClientBootstrap, SignedBeaconBlock}; use crate::rpc::methods::{BlobsByRangeRequest, BlobsByRootRequest}; use crate::rpc::{ diff --git a/beacon_node/network/src/network_beacon_processor/rpc_methods.rs b/beacon_node/network/src/network_beacon_processor/rpc_methods.rs index f2ca342809..430e0571b7 100644 --- a/beacon_node/network/src/network_beacon_processor/rpc_methods.rs +++ b/beacon_node/network/src/network_beacon_processor/rpc_methods.rs @@ -18,9 +18,7 @@ use std::sync::Arc; use task_executor::TaskExecutor; use tokio_stream::StreamExt; use types::blob_sidecar::BlobIdentifier; -use types::{ - light_client_bootstrap::LightClientBootstrap, Epoch, EthSpec, ForkName, Hash256, Slot, -}; +use types::{Epoch, EthSpec, ForkName, Hash256, Slot}; impl NetworkBeaconProcessor { /* Auxiliary functions */ @@ -304,66 +302,32 @@ impl NetworkBeaconProcessor { request: LightClientBootstrapRequest, ) { let block_root = request.root; - let state_root = match self.chain.get_blinded_block(&block_root) { - Ok(signed_block) => match signed_block { - Some(signed_block) => signed_block.state_root(), - None => { - self.send_error_response( - peer_id, - RPCResponseErrorCode::ResourceUnavailable, - "Bootstrap not available".into(), - request_id, - ); - return; - } - }, - Err(_) => { - self.send_error_response( - peer_id, - RPCResponseErrorCode::ResourceUnavailable, - "Bootstrap not available".into(), - request_id, - ); - return; - } - }; - let mut beacon_state = match self.chain.get_state(&state_root, None) { - Ok(beacon_state) => match beacon_state { - Some(state) => state, - None => { - self.send_error_response( - peer_id, - RPCResponseErrorCode::ResourceUnavailable, - "Bootstrap not available".into(), - request_id, - ); - return; - } - }, - Err(_) => { - self.send_error_response( - peer_id, - RPCResponseErrorCode::ResourceUnavailable, - "Bootstrap not available".into(), - request_id, - ); - return; - } - }; - let Ok(bootstrap) = LightClientBootstrap::from_beacon_state(&mut beacon_state) else { - self.send_error_response( + match self.chain.get_light_client_bootstrap(&block_root) { + Ok(Some((bootstrap, _))) => self.send_response( + peer_id, + Response::LightClientBootstrap(bootstrap), + request_id, + ), + Ok(None) => self.send_error_response( peer_id, RPCResponseErrorCode::ResourceUnavailable, "Bootstrap not available".into(), request_id, - ); - return; + ), + Err(e) => { + self.send_error_response( + peer_id, + RPCResponseErrorCode::ResourceUnavailable, + "Bootstrap not available".into(), + request_id, + ); + error!(self.log, "Error getting LightClientBootstrap instance"; + "block_root" => ?block_root, + "peer" => %peer_id, + "error" => ?e + ) + } }; - self.send_response( - peer_id, - Response::LightClientBootstrap(bootstrap), - request_id, - ) } /// Handle a `BlocksByRange` request from the peer. diff --git a/beacon_node/src/config.rs b/beacon_node/src/config.rs index 609626ae88..9ceab47f33 100644 --- a/beacon_node/src/config.rs +++ b/beacon_node/src/config.rs @@ -151,6 +151,9 @@ pub fn get_config( client_config.http_api.duplicate_block_status_code = parse_required(cli_args, "http-duplicate-block-status")?; + + client_config.http_api.enable_light_client_server = + cli_args.is_present("light-client-server"); } if let Some(cache_size) = clap_utils::parse_optional(cli_args, "shuffling-cache-size")? { diff --git a/common/eth2/src/lib.rs b/common/eth2/src/lib.rs index 043c0f197e..7ed1c5c540 100644 --- a/common/eth2/src/lib.rs +++ b/common/eth2/src/lib.rs @@ -667,6 +667,59 @@ impl BeaconNodeHttpClient { self.get_opt(path).await } + /// `GET beacon/light_client/bootstrap` + /// + /// Returns `Ok(None)` on a 404 error. + pub async fn get_light_client_bootstrap( + &self, + block_root: Hash256, + ) -> Result>>, Error> { + let mut path = self.eth_path(V1)?; + + path.path_segments_mut() + .map_err(|()| Error::InvalidUrl(self.server.clone()))? + .push("beacon") + .push("light_client") + .push("bootstrap") + .push(&format!("{:?}", block_root)); + + self.get_opt(path).await + } + + /// `GET beacon/light_client/optimistic_update` + /// + /// Returns `Ok(None)` on a 404 error. + pub async fn get_beacon_light_client_optimistic_update( + &self, + ) -> Result>>, Error> { + let mut path = self.eth_path(V1)?; + + path.path_segments_mut() + .map_err(|()| Error::InvalidUrl(self.server.clone()))? + .push("beacon") + .push("light_client") + .push("optimistic_update"); + + self.get_opt(path).await + } + + /// `GET beacon/light_client/finality_update` + /// + /// Returns `Ok(None)` on a 404 error. + pub async fn get_beacon_light_client_finality_update( + &self, + ) -> Result>>, Error> { + let mut path = self.eth_path(V1)?; + + path.path_segments_mut() + .map_err(|()| Error::InvalidUrl(self.server.clone()))? + .push("beacon") + .push("light_client") + .push("finality_update"); + + self.get_opt(path).await + } + /// `GET beacon/headers?slot,parent_root` /// /// Returns `Ok(None)` on a 404 error. diff --git a/common/eth2/src/types.rs b/common/eth2/src/types.rs index c2d85a31d3..dea8b2bf56 100644 --- a/common/eth2/src/types.rs +++ b/common/eth2/src/types.rs @@ -1047,6 +1047,8 @@ pub enum EventKind { ChainReorg(SseChainReorg), ContributionAndProof(Box>), LateHead(SseLateHead), + LightClientFinalityUpdate(Box>), + LightClientOptimisticUpdate(Box>), #[cfg(feature = "lighthouse")] BlockReward(BlockReward), PayloadAttributes(VersionedSsePayloadAttributes), @@ -1065,6 +1067,8 @@ impl EventKind { EventKind::ContributionAndProof(_) => "contribution_and_proof", EventKind::PayloadAttributes(_) => "payload_attributes", EventKind::LateHead(_) => "late_head", + EventKind::LightClientFinalityUpdate(_) => "light_client_finality_update", + EventKind::LightClientOptimisticUpdate(_) => "light_client_optimistic_update", #[cfg(feature = "lighthouse")] EventKind::BlockReward(_) => "block_reward", } @@ -1127,6 +1131,22 @@ impl EventKind { ServerError::InvalidServerSentEvent(format!("Payload Attributes: {:?}", e)) })?, )), + "light_client_finality_update" => Ok(EventKind::LightClientFinalityUpdate( + serde_json::from_str(data).map_err(|e| { + ServerError::InvalidServerSentEvent(format!( + "Light Client Finality Update: {:?}", + e + )) + })?, + )), + "light_client_optimistic_update" => Ok(EventKind::LightClientOptimisticUpdate( + serde_json::from_str(data).map_err(|e| { + ServerError::InvalidServerSentEvent(format!( + "Light Client Optimistic Update: {:?}", + e + )) + })?, + )), #[cfg(feature = "lighthouse")] "block_reward" => Ok(EventKind::BlockReward(serde_json::from_str(data).map_err( |e| ServerError::InvalidServerSentEvent(format!("Block Reward: {:?}", e)), @@ -1158,6 +1178,8 @@ pub enum EventTopic { ContributionAndProof, LateHead, PayloadAttributes, + LightClientFinalityUpdate, + LightClientOptimisticUpdate, #[cfg(feature = "lighthouse")] BlockReward, } @@ -1177,6 +1199,8 @@ impl FromStr for EventTopic { "contribution_and_proof" => Ok(EventTopic::ContributionAndProof), "payload_attributes" => Ok(EventTopic::PayloadAttributes), "late_head" => Ok(EventTopic::LateHead), + "light_client_finality_update" => Ok(EventTopic::LightClientFinalityUpdate), + "light_client_optimistic_update" => Ok(EventTopic::LightClientOptimisticUpdate), #[cfg(feature = "lighthouse")] "block_reward" => Ok(EventTopic::BlockReward), _ => Err("event topic cannot be parsed.".to_string()), @@ -1197,6 +1221,8 @@ impl fmt::Display for EventTopic { EventTopic::ContributionAndProof => write!(f, "contribution_and_proof"), EventTopic::PayloadAttributes => write!(f, "payload_attributes"), EventTopic::LateHead => write!(f, "late_head"), + EventTopic::LightClientFinalityUpdate => write!(f, "light_client_finality_update"), + EventTopic::LightClientOptimisticUpdate => write!(f, "light_client_optimistic_update"), #[cfg(feature = "lighthouse")] EventTopic::BlockReward => write!(f, "block_reward"), } diff --git a/consensus/state_processing/src/upgrade/altair.rs b/consensus/state_processing/src/upgrade/altair.rs index 26b1192bc1..5bb4f0bd59 100644 --- a/consensus/state_processing/src/upgrade/altair.rs +++ b/consensus/state_processing/src/upgrade/altair.rs @@ -54,7 +54,7 @@ pub fn upgrade_to_altair( VariableList::new(vec![ParticipationFlags::default(); pre.validators.len()])?; let inactivity_scores = VariableList::new(vec![0; pre.validators.len()])?; - let temp_sync_committee = Arc::new(SyncCommittee::temporary()?); + let temp_sync_committee = Arc::new(SyncCommittee::temporary()); // Where possible, use something like `mem::take` to move fields from behind the &mut // reference. For other fields that don't have a good default value, use `clone`. diff --git a/consensus/types/src/lib.rs b/consensus/types/src/lib.rs index 26541a188c..0f284bde9d 100644 --- a/consensus/types/src/lib.rs +++ b/consensus/types/src/lib.rs @@ -99,6 +99,7 @@ pub mod slot_data; pub mod sqlite; pub mod blob_sidecar; +pub mod light_client_header; pub mod sidecar; pub mod signed_blob; @@ -154,8 +155,11 @@ pub use crate::fork_versioned_response::{ForkVersionDeserialize, ForkVersionedRe pub use crate::graffiti::{Graffiti, GRAFFITI_BYTES_LEN}; pub use crate::historical_batch::HistoricalBatch; pub use crate::indexed_attestation::IndexedAttestation; +pub use crate::light_client_bootstrap::LightClientBootstrap; pub use crate::light_client_finality_update::LightClientFinalityUpdate; +pub use crate::light_client_header::LightClientHeader; pub use crate::light_client_optimistic_update::LightClientOptimisticUpdate; +pub use crate::light_client_update::{Error as LightClientError, LightClientUpdate}; pub use crate::participation_flags::ParticipationFlags; pub use crate::participation_list::ParticipationList; pub use crate::payload::{ diff --git a/consensus/types/src/light_client_bootstrap.rs b/consensus/types/src/light_client_bootstrap.rs index 1d70456d73..616aced483 100644 --- a/consensus/types/src/light_client_bootstrap.rs +++ b/consensus/types/src/light_client_bootstrap.rs @@ -1,10 +1,13 @@ -use super::{BeaconBlockHeader, BeaconState, EthSpec, FixedVector, Hash256, SyncCommittee}; -use crate::{light_client_update::*, test_utils::TestRandom}; -use serde::{Deserialize, Serialize}; +use super::{BeaconState, EthSpec, FixedVector, Hash256, SyncCommittee}; +use crate::{ + light_client_update::*, test_utils::TestRandom, ForkName, ForkVersionDeserialize, + LightClientHeader, +}; +use serde::{Deserialize, Deserializer, Serialize}; +use serde_json::Value; use ssz_derive::{Decode, Encode}; use std::sync::Arc; use test_random_derive::TestRandom; -use tree_hash::TreeHash; /// A LightClientBootstrap is the initializer we send over to lightclient nodes /// that are trying to generate their basic storage when booting up. @@ -22,8 +25,8 @@ use tree_hash::TreeHash; #[serde(bound = "T: EthSpec")] #[arbitrary(bound = "T: EthSpec")] pub struct LightClientBootstrap { - /// Requested beacon block header. - pub header: BeaconBlockHeader, + /// The requested beacon block header. + pub header: LightClientHeader, /// The `SyncCommittee` used in the requested period. pub current_sync_committee: Arc>, /// Merkle proof for sync committee @@ -33,17 +36,37 @@ pub struct LightClientBootstrap { impl LightClientBootstrap { pub fn from_beacon_state(beacon_state: &mut BeaconState) -> Result { let mut header = beacon_state.latest_block_header().clone(); - header.state_root = beacon_state.tree_hash_root(); + header.state_root = beacon_state.update_tree_hash_cache()?; let current_sync_committee_branch = beacon_state.compute_merkle_proof(CURRENT_SYNC_COMMITTEE_INDEX)?; Ok(LightClientBootstrap { - header, + header: header.into(), current_sync_committee: beacon_state.current_sync_committee()?.clone(), current_sync_committee_branch: FixedVector::new(current_sync_committee_branch)?, }) } } +impl ForkVersionDeserialize for LightClientBootstrap { + fn deserialize_by_fork<'de, D: Deserializer<'de>>( + value: Value, + fork_name: ForkName, + ) -> Result { + match fork_name { + ForkName::Altair | ForkName::Merge => { + Ok(serde_json::from_value::>(value) + .map_err(serde::de::Error::custom))? + } + ForkName::Base | ForkName::Capella | ForkName::Deneb => { + Err(serde::de::Error::custom(format!( + "LightClientBootstrap failed to deserialize: unsupported fork '{}'", + fork_name + ))) + } + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/consensus/types/src/light_client_finality_update.rs b/consensus/types/src/light_client_finality_update.rs index 30f337fb2b..87601b8156 100644 --- a/consensus/types/src/light_client_finality_update.rs +++ b/consensus/types/src/light_client_finality_update.rs @@ -1,9 +1,12 @@ use super::{ - BeaconBlockHeader, EthSpec, FixedVector, Hash256, SignedBeaconBlock, SignedBlindedBeaconBlock, - Slot, SyncAggregate, + EthSpec, FixedVector, Hash256, SignedBeaconBlock, SignedBlindedBeaconBlock, Slot, SyncAggregate, }; -use crate::{light_client_update::*, test_utils::TestRandom, BeaconState, ChainSpec}; -use serde::{Deserialize, Serialize}; +use crate::{ + light_client_update::*, test_utils::TestRandom, BeaconState, ChainSpec, ForkName, + ForkVersionDeserialize, LightClientHeader, +}; +use serde::{Deserialize, Deserializer, Serialize}; +use serde_json::Value; use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; use tree_hash::TreeHash; @@ -25,9 +28,9 @@ use tree_hash::TreeHash; #[arbitrary(bound = "T: EthSpec")] pub struct LightClientFinalityUpdate { /// The last `BeaconBlockHeader` from the last attested block by the sync committee. - pub attested_header: BeaconBlockHeader, + pub attested_header: LightClientHeader, /// The last `BeaconBlockHeader` from the last attested finalized block (end of epoch). - pub finalized_header: BeaconBlockHeader, + pub finalized_header: LightClientHeader, /// Merkle proof attesting finalized header. pub finality_branch: FixedVector, /// current sync aggreggate @@ -68,8 +71,8 @@ impl LightClientFinalityUpdate { let finality_branch = attested_state.compute_merkle_proof(FINALIZED_ROOT_INDEX)?; Ok(Self { - attested_header, - finalized_header, + attested_header: attested_header.into(), + finalized_header: finalized_header.into(), finality_branch: FixedVector::new(finality_branch)?, sync_aggregate: sync_aggregate.clone(), signature_slot: block.slot(), @@ -77,6 +80,26 @@ impl LightClientFinalityUpdate { } } +impl ForkVersionDeserialize for LightClientFinalityUpdate { + fn deserialize_by_fork<'de, D: Deserializer<'de>>( + value: Value, + fork_name: ForkName, + ) -> Result { + match fork_name { + ForkName::Altair | ForkName::Merge => Ok(serde_json::from_value::< + LightClientFinalityUpdate, + >(value) + .map_err(serde::de::Error::custom))?, + ForkName::Base | ForkName::Capella | ForkName::Deneb => { + Err(serde::de::Error::custom(format!( + "LightClientFinalityUpdate failed to deserialize: unsupported fork '{}'", + fork_name + ))) + } + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/consensus/types/src/light_client_header.rs b/consensus/types/src/light_client_header.rs new file mode 100644 index 0000000000..8fe31f7af8 --- /dev/null +++ b/consensus/types/src/light_client_header.rs @@ -0,0 +1,26 @@ +use crate::test_utils::TestRandom; +use crate::BeaconBlockHeader; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use test_random_derive::TestRandom; + +#[derive( + Debug, + Clone, + PartialEq, + Serialize, + Deserialize, + Encode, + Decode, + TestRandom, + arbitrary::Arbitrary, +)] +pub struct LightClientHeader { + pub beacon: BeaconBlockHeader, +} + +impl From for LightClientHeader { + fn from(beacon: BeaconBlockHeader) -> Self { + LightClientHeader { beacon } + } +} diff --git a/consensus/types/src/light_client_optimistic_update.rs b/consensus/types/src/light_client_optimistic_update.rs index fbb0558ece..d883d735f3 100644 --- a/consensus/types/src/light_client_optimistic_update.rs +++ b/consensus/types/src/light_client_optimistic_update.rs @@ -1,8 +1,10 @@ -use super::{BeaconBlockHeader, EthSpec, Slot, SyncAggregate}; +use super::{EthSpec, ForkName, ForkVersionDeserialize, Slot, SyncAggregate}; +use crate::light_client_header::LightClientHeader; use crate::{ light_client_update::Error, test_utils::TestRandom, BeaconState, ChainSpec, SignedBeaconBlock, }; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Deserializer, Serialize}; +use serde_json::Value; use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; use tree_hash::TreeHash; @@ -24,7 +26,7 @@ use tree_hash::TreeHash; #[arbitrary(bound = "T: EthSpec")] pub struct LightClientOptimisticUpdate { /// The last `BeaconBlockHeader` from the last attested block by the sync committee. - pub attested_header: BeaconBlockHeader, + pub attested_header: LightClientHeader, /// current sync aggreggate pub sync_aggregate: SyncAggregate, /// Slot of the sync aggregated singature @@ -53,13 +55,33 @@ impl LightClientOptimisticUpdate { let mut attested_header = attested_state.latest_block_header().clone(); attested_header.state_root = attested_state.tree_hash_root(); Ok(Self { - attested_header, + attested_header: attested_header.into(), sync_aggregate: sync_aggregate.clone(), signature_slot: block.slot(), }) } } +impl ForkVersionDeserialize for LightClientOptimisticUpdate { + fn deserialize_by_fork<'de, D: Deserializer<'de>>( + value: Value, + fork_name: ForkName, + ) -> Result { + match fork_name { + ForkName::Altair | ForkName::Merge => Ok(serde_json::from_value::< + LightClientOptimisticUpdate, + >(value) + .map_err(serde::de::Error::custom))?, + ForkName::Base | ForkName::Capella | ForkName::Deneb => { + Err(serde::de::Error::custom(format!( + "LightClientOptimisticUpdate failed to deserialize: unsupported fork '{}'", + fork_name + ))) + } + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/consensus/types/src/light_client_update.rs b/consensus/types/src/light_client_update.rs index 6e53e14c99..718cd7553f 100644 --- a/consensus/types/src/light_client_update.rs +++ b/consensus/types/src/light_client_update.rs @@ -1,7 +1,11 @@ use super::{BeaconBlockHeader, EthSpec, FixedVector, Hash256, Slot, SyncAggregate, SyncCommittee}; -use crate::{beacon_state, test_utils::TestRandom, BeaconBlock, BeaconState, ChainSpec}; +use crate::{ + beacon_state, test_utils::TestRandom, BeaconBlock, BeaconState, ChainSpec, ForkName, + ForkVersionDeserialize, LightClientHeader, +}; use safe_arith::ArithError; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Deserializer, Serialize}; +use serde_json::Value; use ssz_derive::{Decode, Encode}; use ssz_types::typenum::{U5, U6}; use std::sync::Arc; @@ -67,13 +71,13 @@ impl From for Error { #[arbitrary(bound = "T: EthSpec")] pub struct LightClientUpdate { /// The last `BeaconBlockHeader` from the last attested block by the sync committee. - pub attested_header: BeaconBlockHeader, + pub attested_header: LightClientHeader, /// The `SyncCommittee` used in the next period. pub next_sync_committee: Arc>, /// Merkle proof for next sync committee pub next_sync_committee_branch: FixedVector, /// The last `BeaconBlockHeader` from the last attested finalized block (end of epoch). - pub finalized_header: BeaconBlockHeader, + pub finalized_header: LightClientHeader, /// Merkle proof attesting finalized header. pub finality_branch: FixedVector, /// current sync aggreggate @@ -128,10 +132,10 @@ impl LightClientUpdate { attested_state.compute_merkle_proof(NEXT_SYNC_COMMITTEE_INDEX)?; let finality_branch = attested_state.compute_merkle_proof(FINALIZED_ROOT_INDEX)?; Ok(Self { - attested_header, + attested_header: attested_header.into(), next_sync_committee: attested_state.next_sync_committee()?.clone(), next_sync_committee_branch: FixedVector::new(next_sync_committee_branch)?, - finalized_header, + finalized_header: finalized_header.into(), finality_branch: FixedVector::new(finality_branch)?, sync_aggregate: sync_aggregate.clone(), signature_slot: block.slot(), @@ -139,6 +143,26 @@ impl LightClientUpdate { } } +impl ForkVersionDeserialize for LightClientUpdate { + fn deserialize_by_fork<'de, D: Deserializer<'de>>( + value: Value, + fork_name: ForkName, + ) -> Result { + match fork_name { + ForkName::Altair | ForkName::Merge => { + Ok(serde_json::from_value::>(value) + .map_err(serde::de::Error::custom))? + } + ForkName::Base | ForkName::Capella | ForkName::Deneb => { + Err(serde::de::Error::custom(format!( + "LightClientUpdate failed to deserialize: unsupported fork '{}'", + fork_name + ))) + } + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/consensus/types/src/sync_committee.rs b/consensus/types/src/sync_committee.rs index 0bcf505f25..b42a000bb0 100644 --- a/consensus/types/src/sync_committee.rs +++ b/consensus/types/src/sync_committee.rs @@ -1,5 +1,4 @@ use crate::test_utils::TestRandom; -use crate::typenum::Unsigned; use crate::{EthSpec, FixedVector, SyncSubnetId}; use bls::PublicKeyBytes; use safe_arith::{ArithError, SafeArith}; @@ -46,14 +45,11 @@ pub struct SyncCommittee { impl SyncCommittee { /// Create a temporary sync committee that should *never* be included in a legitimate consensus object. - pub fn temporary() -> Result { - Ok(Self { - pubkeys: FixedVector::new(vec![ - PublicKeyBytes::empty(); - T::SyncCommitteeSize::to_usize() - ])?, + pub fn temporary() -> Self { + Self { + pubkeys: FixedVector::from_elem(PublicKeyBytes::empty()), aggregate_pubkey: PublicKeyBytes::empty(), - }) + } } /// Return the pubkeys in this `SyncCommittee` for the given `subcommittee_index`. diff --git a/lighthouse/tests/beacon_node.rs b/lighthouse/tests/beacon_node.rs index c8e064e224..5f75cb1acf 100644 --- a/lighthouse/tests/beacon_node.rs +++ b/lighthouse/tests/beacon_node.rs @@ -2346,7 +2346,10 @@ fn sync_eth1_chain_disable_deposit_contract_sync_flag() { fn light_client_server_default() { CommandLineTest::new() .run_with_zero_port() - .with_config(|config| assert_eq!(config.network.enable_light_client_server, false)); + .with_config(|config| { + assert_eq!(config.network.enable_light_client_server, false); + assert_eq!(config.http_api.enable_light_client_server, false); + }); } #[test] @@ -2354,7 +2357,20 @@ fn light_client_server_enabled() { CommandLineTest::new() .flag("light-client-server", None) .run_with_zero_port() - .with_config(|config| assert_eq!(config.network.enable_light_client_server, true)); + .with_config(|config| { + assert_eq!(config.network.enable_light_client_server, true); + }); +} + +#[test] +fn light_client_http_server_enabled() { + CommandLineTest::new() + .flag("http", None) + .flag("light-client-server", None) + .run_with_zero_port() + .with_config(|config| { + assert_eq!(config.http_api.enable_light_client_server, true); + }); } #[test] From 86163d94f2183c7c3968b3d5300bc82fc15caa1b Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 29 Nov 2023 10:04:29 +1100 Subject: [PATCH 22/23] Add `lcli mock-el` (#4587) * Track time to early attester cache * Add debug log for early attester cache * Add mock-el command to lcli * Revert beacon chain changes * Tidy, fix compilation errors * Add required flag * Default to SYNCING response * Hide flag --- Cargo.lock | 2 ++ lcli/Cargo.toml | 2 ++ lcli/src/main.rs | 58 ++++++++++++++++++++++++++++++++++++++++++ lcli/src/mock_el.rs | 62 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 124 insertions(+) create mode 100644 lcli/src/mock_el.rs diff --git a/Cargo.lock b/Cargo.lock index 21fea637f0..9c1b591349 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3875,7 +3875,9 @@ dependencies = [ "eth2_wallet", "ethereum_hashing", "ethereum_ssz", + "execution_layer", "genesis", + "hex", "int_to_bytes", "lighthouse_network", "lighthouse_version", diff --git a/lcli/Cargo.toml b/lcli/Cargo.toml index 854f718c59..d6c3ff197a 100644 --- a/lcli/Cargo.toml +++ b/lcli/Cargo.toml @@ -43,6 +43,8 @@ beacon_chain = { workspace = true } store = { workspace = true } malloc_utils = { workspace = true } rayon = { workspace = true } +execution_layer = { workspace = true } +hex = { workspace = true } [package.metadata.cargo-udeps.ignore] normal = ["malloc_utils"] diff --git a/lcli/src/main.rs b/lcli/src/main.rs index 2aa855474f..17fafe6ec1 100644 --- a/lcli/src/main.rs +++ b/lcli/src/main.rs @@ -11,6 +11,7 @@ mod indexed_attestations; mod insecure_validators; mod interop_genesis; mod mnemonic_validators; +mod mock_el; mod new_testnet; mod parse_ssz; mod replace_state_pubkeys; @@ -891,6 +892,61 @@ fn main() { .help("Number of repeat runs, useful for benchmarking."), ) ) + .subcommand( + SubCommand::with_name("mock-el") + .about("Creates a mock execution layer server. This is NOT SAFE and should only \ + be used for testing and development on testnets. Do not use in production. Do not \ + use on mainnet. It cannot perform validator duties.") + .arg( + Arg::with_name("jwt-output-path") + .long("jwt-output-path") + .value_name("PATH") + .takes_value(true) + .required(true) + .help("Path to write the JWT secret."), + ) + .arg( + Arg::with_name("listen-address") + .long("listen-address") + .value_name("IP_ADDRESS") + .takes_value(true) + .help("The server will listen on this address.") + .default_value("127.0.0.1") + ) + .arg( + Arg::with_name("listen-port") + .long("listen-port") + .value_name("PORT") + .takes_value(true) + .help("The server will listen on this port.") + .default_value("8551") + ) + .arg( + Arg::with_name("all-payloads-valid") + .long("all-payloads-valid") + .takes_value(true) + .help("Controls the response to newPayload and forkchoiceUpdated. \ + Set to 'true' to return VALID. Set to 'false' to return SYNCING.") + .default_value("false") + .hidden(true) + ) + .arg( + Arg::with_name("shanghai-time") + .long("shanghai-time") + .value_name("UNIX_TIMESTAMP") + .takes_value(true) + .help("The payload timestamp that enables Shanghai. Defaults to the mainnet value.") + .default_value("1681338479") + ) + .arg( + Arg::with_name("cancun-time") + .long("cancun-time") + .value_name("UNIX_TIMESTAMP") + .takes_value(true) + .help("The payload timestamp that enables Cancun. No default is provided \ + until Cancun is triggered on mainnet.") + ) + ) .get_matches(); let result = matches @@ -1032,6 +1088,8 @@ fn run( state_root::run::(env, network_config, matches) .map_err(|e| format!("Failed to run state-root command: {}", e)) } + ("mock-el", Some(matches)) => mock_el::run::(env, matches) + .map_err(|e| format!("Failed to run mock-el command: {}", e)), (other, _) => Err(format!("Unknown subcommand {}. See --help.", other)), } } diff --git a/lcli/src/mock_el.rs b/lcli/src/mock_el.rs new file mode 100644 index 0000000000..094e23c3b4 --- /dev/null +++ b/lcli/src/mock_el.rs @@ -0,0 +1,62 @@ +use clap::ArgMatches; +use clap_utils::{parse_optional, parse_required}; +use environment::Environment; +use execution_layer::{ + auth::JwtKey, + test_utils::{ + Config, MockExecutionConfig, MockServer, DEFAULT_JWT_SECRET, DEFAULT_TERMINAL_BLOCK, + }, +}; +use std::net::Ipv4Addr; +use std::path::PathBuf; +use types::*; + +pub fn run(mut env: Environment, matches: &ArgMatches) -> Result<(), String> { + let jwt_path: PathBuf = parse_required(matches, "jwt-output-path")?; + let listen_addr: Ipv4Addr = parse_required(matches, "listen-address")?; + let listen_port: u16 = parse_required(matches, "listen-port")?; + let all_payloads_valid: bool = parse_required(matches, "all-payloads-valid")?; + let shanghai_time = parse_required(matches, "shanghai-time")?; + let cancun_time = parse_optional(matches, "cancun-time")?; + + let handle = env.core_context().executor.handle().unwrap(); + let spec = &T::default_spec(); + let jwt_key = JwtKey::from_slice(&DEFAULT_JWT_SECRET).unwrap(); + std::fs::write(jwt_path, hex::encode(DEFAULT_JWT_SECRET)).unwrap(); + + let config = MockExecutionConfig { + server_config: Config { + listen_addr, + listen_port, + }, + jwt_key, + terminal_difficulty: spec.terminal_total_difficulty, + terminal_block: DEFAULT_TERMINAL_BLOCK, + terminal_block_hash: spec.terminal_block_hash, + shanghai_time: Some(shanghai_time), + cancun_time, + }; + let kzg = None; + let server: MockServer = MockServer::new_with_config(&handle, config, kzg); + + if all_payloads_valid { + eprintln!( + "Using --all-payloads-valid=true can be dangerous. \ + Never use this flag when operating validators." + ); + // Indicate that all payloads are valid. + server.all_payloads_valid(); + } + + eprintln!( + "This tool is for TESTING PURPOSES ONLY. Do not use in production or on mainnet. \ + It cannot perform validator duties. It may cause nodes to follow an invalid chain." + ); + eprintln!("Server listening on {}:{}", listen_addr, listen_port); + + let shutdown_reason = env.block_until_shutdown_requested()?; + + eprintln!("Shutting down: {:?}", shutdown_reason); + + Ok(()) +} From 43d98153d64ba3bc02d8e51e474d41dbcad4b400 Mon Sep 17 00:00:00 2001 From: ethDreamer <37123614+ethDreamer@users.noreply.github.com> Date: Tue, 28 Nov 2023 22:20:12 -0600 Subject: [PATCH 23/23] Refactor & Fix Bugs in Payload Selection Logic (#4950) * Refactor & Fix Bugs in Payload Selection Logic * Fix lint * Update beacon_node/execution_layer/src/lib.rs * Finish renaming function --- beacon_node/execution_layer/src/lib.rs | 588 +++++++++++++------------ 1 file changed, 298 insertions(+), 290 deletions(-) diff --git a/beacon_node/execution_layer/src/lib.rs b/beacon_node/execution_layer/src/lib.rs index 3993e442ad..07fdf6414c 100644 --- a/beacon_node/execution_layer/src/lib.rs +++ b/beacon_node/execution_layer/src/lib.rs @@ -320,6 +320,7 @@ pub struct BuilderParams { pub chain_health: ChainHealth, } +#[derive(PartialEq)] pub enum ChainHealth { Healthy, Unhealthy(FailedCondition), @@ -327,7 +328,7 @@ pub enum ChainHealth { PreMerge, } -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub enum FailedCondition { Skips, SkipsPerEpoch, @@ -928,6 +929,72 @@ impl ExecutionLayer { } } + /// Fetches local and builder paylaods concurrently, Logs and returns results. + async fn fetch_builder_and_local_payloads( + &self, + builder: &BuilderHttpClient, + parent_hash: ExecutionBlockHash, + builder_params: &BuilderParams, + payload_attributes: &PayloadAttributes, + forkchoice_update_params: ForkchoiceUpdateParameters, + current_fork: ForkName, + ) -> ( + Result>>, builder_client::Error>, + Result, Error>, + ) { + let slot = builder_params.slot; + let pubkey = &builder_params.pubkey; + + info!( + self.log(), + "Requesting blinded header from connected builder"; + "slot" => ?slot, + "pubkey" => ?pubkey, + "parent_hash" => ?parent_hash, + ); + + // Wait for the builder *and* local EL to produce a payload (or return an error). + let ((relay_result, relay_duration), (local_result, local_duration)) = tokio::join!( + timed_future(metrics::GET_BLINDED_PAYLOAD_BUILDER, async { + builder + .get_builder_header::(slot, parent_hash, pubkey) + .await + }), + timed_future(metrics::GET_BLINDED_PAYLOAD_LOCAL, async { + self.get_full_payload_caching( + parent_hash, + payload_attributes, + forkchoice_update_params, + current_fork, + ) + .await + .and_then(|local_result_type| match local_result_type { + GetPayloadResponseType::Full(payload) => Ok(payload), + GetPayloadResponseType::Blinded(_) => Err(Error::PayloadTypeMismatch), + }) + }) + ); + + info!( + self.log(), + "Requested blinded execution payload"; + "relay_fee_recipient" => match &relay_result { + Ok(Some(r)) => format!("{:?}", r.data.message.header().fee_recipient()), + Ok(None) => "empty response".to_string(), + Err(_) => "request failed".to_string(), + }, + "relay_response_ms" => relay_duration.as_millis(), + "local_fee_recipient" => match &local_result { + Ok(get_payload_response) => format!("{:?}", get_payload_response.fee_recipient()), + Err(_) => "request failed".to_string() + }, + "local_response_ms" => local_duration.as_millis(), + "parent_hash" => ?parent_hash, + ); + + (relay_result, local_result) + } + async fn determine_and_fetch_payload( &self, parent_hash: ExecutionBlockHash, @@ -937,250 +1004,24 @@ impl ExecutionLayer { current_fork: ForkName, spec: &ChainSpec, ) -> Result>, Error> { - if let Some(builder) = self.builder() { - let slot = builder_params.slot; - let pubkey = builder_params.pubkey; + let Some(builder) = self.builder() else { + // no builder.. return local payload + return self + .get_full_payload_caching( + parent_hash, + payload_attributes, + forkchoice_update_params, + current_fork, + ) + .await + .and_then(GetPayloadResponseType::try_into) + .map(ProvenancedPayload::Local); + }; + + // check chain health + if builder_params.chain_health != ChainHealth::Healthy { + // chain is unhealthy, gotta use local payload match builder_params.chain_health { - ChainHealth::Healthy => { - info!( - self.log(), - "Requesting blinded header from connected builder"; - "slot" => ?slot, - "pubkey" => ?pubkey, - "parent_hash" => ?parent_hash, - ); - - // Wait for the builder *and* local EL to produce a payload (or return an error). - let ((relay_result, relay_duration), (local_result_type, local_duration)) = tokio::join!( - timed_future(metrics::GET_BLINDED_PAYLOAD_BUILDER, async { - builder - .get_builder_header::(slot, parent_hash, &pubkey) - .await - }), - timed_future(metrics::GET_BLINDED_PAYLOAD_LOCAL, async { - self.get_full_payload_caching( - parent_hash, - payload_attributes, - forkchoice_update_params, - current_fork, - ) - .await - }) - ); - - let local_result = match local_result_type? { - GetPayloadResponseType::Full(payload) => Ok(payload), - GetPayloadResponseType::Blinded(_) => Err(Error::PayloadTypeMismatch), - }; - - info!( - self.log(), - "Requested blinded execution payload"; - "relay_fee_recipient" => match &relay_result { - Ok(Some(r)) => format!("{:?}", r.data.message.header().fee_recipient()), - Ok(None) => "empty response".to_string(), - Err(_) => "request failed".to_string(), - }, - "relay_response_ms" => relay_duration.as_millis(), - "local_fee_recipient" => match &local_result { - Ok(get_payload_response) => format!("{:?}", get_payload_response.fee_recipient()), - Err(_) => "request failed".to_string() - }, - "local_response_ms" => local_duration.as_millis(), - "parent_hash" => ?parent_hash, - ); - - return match (relay_result, local_result) { - (Err(e), Ok(local)) => { - warn!( - self.log(), - "Builder error when requesting payload"; - "info" => "falling back to local execution client", - "relay_error" => ?e, - "local_block_hash" => ?local.block_hash(), - "parent_hash" => ?parent_hash, - ); - Ok(ProvenancedPayload::Local(BlockProposalContentsType::Full( - local.try_into()?, - ))) - } - (Ok(None), Ok(local)) => { - info!( - self.log(), - "Builder did not return a payload"; - "info" => "falling back to local execution client", - "local_block_hash" => ?local.block_hash(), - "parent_hash" => ?parent_hash, - ); - Ok(ProvenancedPayload::Local(BlockProposalContentsType::Full( - local.try_into()?, - ))) - } - (Ok(Some(relay)), Ok(local)) => { - let header = &relay.data.message.header(); - - info!( - self.log(), - "Received local and builder payloads"; - "relay_block_hash" => ?header.block_hash(), - "local_block_hash" => ?local.block_hash(), - "parent_hash" => ?parent_hash, - ); - - let relay_value = relay.data.message.value(); - let local_value = *local.block_value(); - - if !self.inner.always_prefer_builder_payload { - if local_value >= *relay_value { - info!( - self.log(), - "Local block is more profitable than relay block"; - "local_block_value" => %local_value, - "relay_value" => %relay_value - ); - return Ok(ProvenancedPayload::Local( - BlockProposalContentsType::Full(local.try_into()?), - )); - } else if local.should_override_builder().unwrap_or(false) { - let percentage_difference = - percentage_difference_u256(local_value, *relay_value); - if percentage_difference.map_or(false, |percentage| { - percentage - < self - .inner - .ignore_builder_override_suggestion_threshold - }) { - info!( - self.log(), - "Using local payload because execution engine suggested we ignore builder payload"; - "local_block_value" => %local_value, - "relay_value" => %relay_value - ); - return Ok(ProvenancedPayload::Local( - BlockProposalContentsType::Full(local.try_into()?), - )); - } - } else { - info!( - self.log(), - "Relay block is more profitable than local block"; - "local_block_value" => %local_value, - "relay_value" => %relay_value - ); - } - } - - match verify_builder_bid( - &relay, - parent_hash, - payload_attributes, - Some(local.block_number()), - self.inner.builder_profit_threshold, - current_fork, - spec, - ) { - Ok(()) => Ok(ProvenancedPayload::try_from(relay.data.message)?), - Err(reason) if !reason.payload_invalid() => { - info!( - self.log(), - "Builder payload ignored"; - "info" => "using local payload", - "reason" => %reason, - "relay_block_hash" => ?header.block_hash(), - "parent_hash" => ?parent_hash, - ); - Ok(ProvenancedPayload::Local(BlockProposalContentsType::Full( - local.try_into()?, - ))) - } - Err(reason) => { - metrics::inc_counter_vec( - &metrics::EXECUTION_LAYER_GET_PAYLOAD_BUILDER_REJECTIONS, - &[reason.as_ref().as_ref()], - ); - warn!( - self.log(), - "Builder returned invalid payload"; - "info" => "using local payload", - "reason" => %reason, - "relay_block_hash" => ?header.block_hash(), - "parent_hash" => ?parent_hash, - ); - Ok(ProvenancedPayload::Local(BlockProposalContentsType::Full( - local.try_into()?, - ))) - } - } - } - (Ok(Some(relay)), Err(local_error)) => { - let header = &relay.data.message.header(); - - info!( - self.log(), - "Received builder payload with local error"; - "relay_block_hash" => ?header.block_hash(), - "local_error" => ?local_error, - "parent_hash" => ?parent_hash, - ); - - match verify_builder_bid( - &relay, - parent_hash, - payload_attributes, - None, - self.inner.builder_profit_threshold, - current_fork, - spec, - ) { - Ok(()) => Ok(ProvenancedPayload::try_from(relay.data.message)?), - // If the payload is valid then use it. The local EE failed - // to produce a payload so we have no alternative. - Err(e) if !e.payload_invalid() => { - Ok(ProvenancedPayload::try_from(relay.data.message)?) - } - Err(reason) => { - metrics::inc_counter_vec( - &metrics::EXECUTION_LAYER_GET_PAYLOAD_BUILDER_REJECTIONS, - &[reason.as_ref().as_ref()], - ); - crit!( - self.log(), - "Builder returned invalid payload"; - "info" => "no local payload either - unable to propose block", - "reason" => %reason, - "relay_block_hash" => ?header.block_hash(), - "parent_hash" => ?parent_hash, - ); - Err(Error::CannotProduceHeader) - } - } - } - (Err(relay_error), Err(local_error)) => { - crit!( - self.log(), - "Unable to produce execution payload"; - "info" => "the local EL and builder both failed - unable to propose block", - "relay_error" => ?relay_error, - "local_error" => ?local_error, - "parent_hash" => ?parent_hash, - ); - - Err(Error::CannotProduceHeader) - } - (Ok(None), Err(local_error)) => { - crit!( - self.log(), - "Unable to produce execution payload"; - "info" => "the local EL failed and the builder returned nothing - \ - the block proposal will be missed", - "local_error" => ?local_error, - "parent_hash" => ?parent_hash, - ); - - Err(Error::CannotProduceHeader) - } - }; - } ChainHealth::Unhealthy(condition) => info!( self.log(), "Chain is unhealthy, using local payload"; @@ -1196,17 +1037,220 @@ impl ExecutionLayer { "info" => "the local execution engine is syncing and the builder network \ cannot safely be used - unable to propose block" ), + ChainHealth::Healthy => crit!( + self.log(), + "got healthy but also not healthy.. this shouldn't happen!" + ), + } + return self + .get_full_payload_caching( + parent_hash, + payload_attributes, + forkchoice_update_params, + current_fork, + ) + .await + .and_then(GetPayloadResponseType::try_into) + .map(ProvenancedPayload::Local); + } + + let (relay_result, local_result) = self + .fetch_builder_and_local_payloads( + builder.as_ref(), + parent_hash, + &builder_params, + payload_attributes, + forkchoice_update_params, + current_fork, + ) + .await; + + match (relay_result, local_result) { + (Err(e), Ok(local)) => { + warn!( + self.log(), + "Builder error when requesting payload"; + "info" => "falling back to local execution client", + "relay_error" => ?e, + "local_block_hash" => ?local.block_hash(), + "parent_hash" => ?parent_hash, + ); + Ok(ProvenancedPayload::Local(BlockProposalContentsType::Full( + local.try_into()?, + ))) + } + (Ok(None), Ok(local)) => { + info!( + self.log(), + "Builder did not return a payload"; + "info" => "falling back to local execution client", + "local_block_hash" => ?local.block_hash(), + "parent_hash" => ?parent_hash, + ); + Ok(ProvenancedPayload::Local(BlockProposalContentsType::Full( + local.try_into()?, + ))) + } + (Err(relay_error), Err(local_error)) => { + crit!( + self.log(), + "Unable to produce execution payload"; + "info" => "the local EL and builder both failed - unable to propose block", + "relay_error" => ?relay_error, + "local_error" => ?local_error, + "parent_hash" => ?parent_hash, + ); + + Err(Error::CannotProduceHeader) + } + (Ok(None), Err(local_error)) => { + crit!( + self.log(), + "Unable to produce execution payload"; + "info" => "the local EL failed and the builder returned nothing - \ + the block proposal will be missed", + "local_error" => ?local_error, + "parent_hash" => ?parent_hash, + ); + + Err(Error::CannotProduceHeader) + } + (Ok(Some(relay)), Ok(local)) => { + let header = &relay.data.message.header(); + + info!( + self.log(), + "Received local and builder payloads"; + "relay_block_hash" => ?header.block_hash(), + "local_block_hash" => ?local.block_hash(), + "parent_hash" => ?parent_hash, + ); + + // check relay payload validity + if let Err(reason) = verify_builder_bid( + &relay, + parent_hash, + payload_attributes, + Some(local.block_number()), + current_fork, + spec, + ) { + // relay payload invalid -> return local + metrics::inc_counter_vec( + &metrics::EXECUTION_LAYER_GET_PAYLOAD_BUILDER_REJECTIONS, + &[reason.as_ref().as_ref()], + ); + warn!( + self.log(), + "Builder returned invalid payload"; + "info" => "using local payload", + "reason" => %reason, + "relay_block_hash" => ?header.block_hash(), + "parent_hash" => ?parent_hash, + ); + return Ok(ProvenancedPayload::Local(BlockProposalContentsType::Full( + local.try_into()?, + ))); + } + + if self.inner.always_prefer_builder_payload { + return ProvenancedPayload::try_from(relay.data.message); + } + + let relay_value = *relay.data.message.value(); + let local_value = *local.block_value(); + + if local_value >= relay_value { + info!( + self.log(), + "Local block is more profitable than relay block"; + "local_block_value" => %local_value, + "relay_value" => %relay_value + ); + return Ok(ProvenancedPayload::Local(BlockProposalContentsType::Full( + local.try_into()?, + ))); + } + + if relay_value < self.inner.builder_profit_threshold { + info!( + self.log(), + "Builder payload ignored"; + "info" => "using local payload", + "reason" => format!("payload value of {} does not meet user-configured profit-threshold of {}", relay_value, self.inner.builder_profit_threshold), + "relay_block_hash" => ?header.block_hash(), + "parent_hash" => ?parent_hash, + ); + return Ok(ProvenancedPayload::Local(BlockProposalContentsType::Full( + local.try_into()?, + ))); + } + + if local.should_override_builder().unwrap_or(false) { + let percentage_difference = + percentage_difference_u256(local_value, relay_value); + if percentage_difference.map_or(false, |percentage| { + percentage < self.inner.ignore_builder_override_suggestion_threshold + }) { + info!( + self.log(), + "Using local payload because execution engine suggested we ignore builder payload"; + "local_block_value" => %local_value, + "relay_value" => %relay_value + ); + return Ok(ProvenancedPayload::Local(BlockProposalContentsType::Full( + local.try_into()?, + ))); + } + } + + info!( + self.log(), + "Relay block is more profitable than local block"; + "local_block_value" => %local_value, + "relay_value" => %relay_value + ); + + Ok(ProvenancedPayload::try_from(relay.data.message)?) + } + (Ok(Some(relay)), Err(local_error)) => { + let header = &relay.data.message.header(); + + info!( + self.log(), + "Received builder payload with local error"; + "relay_block_hash" => ?header.block_hash(), + "local_error" => ?local_error, + "parent_hash" => ?parent_hash, + ); + + match verify_builder_bid( + &relay, + parent_hash, + payload_attributes, + None, + current_fork, + spec, + ) { + Ok(()) => Ok(ProvenancedPayload::try_from(relay.data.message)?), + Err(reason) => { + metrics::inc_counter_vec( + &metrics::EXECUTION_LAYER_GET_PAYLOAD_BUILDER_REJECTIONS, + &[reason.as_ref().as_ref()], + ); + crit!( + self.log(), + "Builder returned invalid payload"; + "info" => "no local payload either - unable to propose block", + "reason" => %reason, + "relay_block_hash" => ?header.block_hash(), + "parent_hash" => ?parent_hash, + ); + Err(Error::CannotProduceHeader) + } + } } } - self.get_full_payload_caching( - parent_hash, - payload_attributes, - forkchoice_update_params, - current_fork, - ) - .await - .and_then(GetPayloadResponseType::try_into) - .map(ProvenancedPayload::Local) } /// Get a full payload and cache its result in the execution layer's payload cache. @@ -2027,10 +2071,6 @@ impl ExecutionLayer { #[derive(AsRefStr)] #[strum(serialize_all = "snake_case")] enum InvalidBuilderPayload { - LowValue { - profit_threshold: Uint256, - payload_value: Uint256, - }, ParentHash { payload: ExecutionBlockHash, expected: ExecutionBlockHash, @@ -2061,34 +2101,9 @@ enum InvalidBuilderPayload { }, } -impl InvalidBuilderPayload { - /// Returns `true` if a payload is objectively invalid and should never be included on chain. - fn payload_invalid(&self) -> bool { - match self { - // A low-value payload isn't invalid, it should just be avoided if possible. - InvalidBuilderPayload::LowValue { .. } => false, - InvalidBuilderPayload::ParentHash { .. } => true, - InvalidBuilderPayload::PrevRandao { .. } => true, - InvalidBuilderPayload::Timestamp { .. } => true, - InvalidBuilderPayload::BlockNumber { .. } => true, - InvalidBuilderPayload::Fork { .. } => true, - InvalidBuilderPayload::Signature { .. } => true, - InvalidBuilderPayload::WithdrawalsRoot { .. } => true, - } - } -} - impl fmt::Display for InvalidBuilderPayload { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - InvalidBuilderPayload::LowValue { - profit_threshold, - payload_value, - } => write!( - f, - "payload value of {} does not meet user-configured profit-threshold of {}", - payload_value, profit_threshold - ), InvalidBuilderPayload::ParentHash { payload, expected } => { write!(f, "payload block hash was {} not {}", payload, expected) } @@ -2132,13 +2147,11 @@ fn verify_builder_bid( parent_hash: ExecutionBlockHash, payload_attributes: &PayloadAttributes, block_number: Option, - profit_threshold: Uint256, current_fork: ForkName, spec: &ChainSpec, ) -> Result<(), Box> { let is_signature_valid = bid.data.verify_signature(spec); let header = &bid.data.message.header(); - let payload_value = bid.data.message.value(); // Avoid logging values that we can't represent with our Prometheus library. let payload_value_gwei = bid.data.message.value() / 1_000_000_000; @@ -2157,12 +2170,7 @@ fn verify_builder_bid( .map(|withdrawals| Withdrawals::::from(withdrawals).tree_hash_root()); let payload_withdrawals_root = header.withdrawals_root().ok().copied(); - if *payload_value < profit_threshold { - Err(Box::new(InvalidBuilderPayload::LowValue { - profit_threshold, - payload_value: *payload_value, - })) - } else if header.parent_hash() != parent_hash { + if header.parent_hash() != parent_hash { Err(Box::new(InvalidBuilderPayload::ParentHash { payload: header.parent_hash(), expected: parent_hash,