Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[Merged by Bors] - Enable proposer boost re-orging #2860

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
647622c
Implement proposer boost re-orging
michaelsproul Dec 17, 2021
833fa62
Merge remote-tracking branch 'origin/unstable' into proposer-boost-or…
michaelsproul Mar 4, 2022
7e52eb3
Add --block-delay-ms and tweak local testnets
michaelsproul Mar 4, 2022
1c8cd67
Make max delay for re-org dynamic
michaelsproul Mar 4, 2022
e43d6be
CLI flag tests
michaelsproul Mar 4, 2022
980d59c
Merge remote-tracking branch 'origin/unstable' into proposer-boost-or…
michaelsproul May 4, 2022
a962295
Don't re-org on the first slot of the epoch
michaelsproul May 4, 2022
7e6a188
Start writing tests (WIP)
michaelsproul May 4, 2022
0cc3605
Merge remote-tracking branch 'origin/unstable' into proposer-boost-or…
michaelsproul Aug 15, 2022
142c0c5
Fix merge snafu
michaelsproul Aug 15, 2022
dd227fc
Suppress fork choice updates for EL support!
michaelsproul Aug 16, 2022
1b51a7b
Basic proposer re-org test
michaelsproul Aug 17, 2022
4d2aaff
Merge remote-tracking branch 'origin/unstable' into proposer-boost-or…
michaelsproul Sep 19, 2022
caecc9a
Refine re-org conditions and extend tests
michaelsproul Sep 20, 2022
77b068d
Merge remote-tracking branch 'origin/unstable' into proposer-boost-or…
michaelsproul Sep 20, 2022
4728ac3
Participation check, justified balance abstraction
michaelsproul Oct 4, 2022
940d5df
Merge remote-tracking branch 'origin/unstable' into proposer-boost-or…
michaelsproul Oct 4, 2022
ab41479
Fix participation check
michaelsproul Oct 10, 2022
7d6364a
Allow forking in execution block generator
pawanjay176 Oct 18, 2021
94df5a1
Fix and test fcU timing
michaelsproul Oct 11, 2022
e184a4d
Fix tests, configurable prepare-payload-lookahead
michaelsproul Oct 12, 2022
589bebf
Fix clippy
michaelsproul Oct 12, 2022
2b9d8e6
Don't override if we aren't the proposer
michaelsproul Oct 13, 2022
d421765
Resolve most remainings FIXMEs
michaelsproul Oct 13, 2022
1e22209
Fix async lint
michaelsproul Oct 14, 2022
fc8f6b2
Moar tests
michaelsproul Oct 17, 2022
158355d
Check head weight 500ms before re-org slot start
michaelsproul Oct 17, 2022
5e5712d
Merge remote-tracking branch 'origin/unstable' into proposer-boost-or…
michaelsproul Oct 18, 2022
113fbb4
Fix PoW fork test
michaelsproul Oct 18, 2022
df2b3f7
Test slot distance conditions
michaelsproul Oct 18, 2022
a416f9c
Clean up CLI
michaelsproul Oct 18, 2022
aca3ac1
Merge remote-tracking branch 'origin/unstable' into proposer-boost-or…
michaelsproul Oct 18, 2022
725de69
Self-review
michaelsproul Oct 18, 2022
f30f2fe
Address Sean's review comments
michaelsproul Oct 18, 2022
be51e24
Consolidate checks and make them short-circuit
michaelsproul Oct 28, 2022
dc64078
Merge remote-tracking branch 'origin/unstable' into proposer-boost-or…
michaelsproul Oct 28, 2022
f62bbbd
Merge remote-tracking branch 'origin/unstable' into proposer-boost-or…
michaelsproul Oct 30, 2022
3705350
Fix proto array error handling
michaelsproul Oct 30, 2022
68bd325
Add metrics
michaelsproul Oct 31, 2022
2b61626
More metrics
michaelsproul Oct 31, 2022
bec0f59
Metric for block proc fork choice
michaelsproul Oct 31, 2022
e0f8a2c
Add docs
michaelsproul Oct 31, 2022
aa0d85e
Update some comments
michaelsproul Nov 10, 2022
244aa4f
Merge remote-tracking branch 'origin/unstable' into proposer-boost-or…
michaelsproul Nov 10, 2022
ef915a4
Merge remote-tracking branch 'origin/unstable' into proposer-boost-or…
michaelsproul Nov 28, 2022
7b81b2d
Finality distance circuit-breaker
michaelsproul Nov 28, 2022
4cb7aa5
Tests for finality/no-finality
michaelsproul Dec 9, 2022
dbdd4a8
Merge remote-tracking branch 'origin/unstable' into proposer-boost-or…
michaelsproul Dec 9, 2022
bc47bb0
Fix bug in proposer shuffling determination
michaelsproul Dec 12, 2022
357bc97
Update book
michaelsproul Dec 12, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/custom/clippy.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ async-wrapper-methods = [
"task_executor::TaskExecutor::spawn_blocking_handle",
"warp_utils::task::blocking_task",
"warp_utils::task::blocking_json_task",
"beacon_chain::beacon_chain::BeaconChain::spawn_blocking_handle",
"validator_client::http_api::blocking_signed_json_task",
"execution_layer::test_utils::MockServer::new",
"execution_layer::test_utils::MockServer::new_with_config",
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

643 changes: 508 additions & 135 deletions beacon_node/beacon_chain/src/beacon_chain.rs

Large diffs are not rendered by default.

53 changes: 24 additions & 29 deletions beacon_node/beacon_chain/src/beacon_fork_choice_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
use crate::{metrics, BeaconSnapshot};
use derivative::Derivative;
use fork_choice::ForkChoiceStore;
use proto_array::JustifiedBalances;
use safe_arith::ArithError;
use ssz_derive::{Decode, Encode};
use std::collections::BTreeSet;
use std::marker::PhantomData;
Expand All @@ -31,6 +33,7 @@ pub enum Error {
MissingState(Hash256),
InvalidPersistedBytes(ssz::DecodeError),
BeaconStateError(BeaconStateError),
Arith(ArithError),
}

impl From<BeaconStateError> for Error {
Expand All @@ -39,27 +42,15 @@ impl From<BeaconStateError> for Error {
}
}

impl From<ArithError> for Error {
fn from(e: ArithError) -> Self {
Error::Arith(e)
}
}

/// The number of validator balance sets that are cached within `BalancesCache`.
const MAX_BALANCE_CACHE_SIZE: usize = 4;

/// Returns the effective balances for every validator in the given `state`.
///
/// Any validator who is not active in the epoch of the given `state` is assigned a balance of
/// zero.
pub fn get_effective_balances<T: EthSpec>(state: &BeaconState<T>) -> Vec<u64> {
state
.validators()
.iter()
.map(|validator| {
if validator.is_active_at(state.current_epoch()) {
validator.effective_balance
} else {
0
}
})
.collect()
}

#[superstruct(
variants(V8),
variant_attributes(derive(PartialEq, Clone, Debug, Encode, Decode)),
Expand Down Expand Up @@ -113,7 +104,7 @@ impl BalancesCache {
let item = CacheItem {
block_root: epoch_boundary_root,
epoch,
balances: get_effective_balances(state),
balances: JustifiedBalances::from_justified_state(state)?.effective_balances,
};

if self.items.len() == MAX_BALANCE_CACHE_SIZE {
Expand Down Expand Up @@ -152,7 +143,7 @@ pub struct BeaconForkChoiceStore<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<
time: Slot,
finalized_checkpoint: Checkpoint,
justified_checkpoint: Checkpoint,
justified_balances: Vec<u64>,
justified_balances: JustifiedBalances,
best_justified_checkpoint: Checkpoint,
unrealized_justified_checkpoint: Checkpoint,
unrealized_finalized_checkpoint: Checkpoint,
Expand Down Expand Up @@ -181,7 +172,7 @@ where
pub fn get_forkchoice_store(
store: Arc<HotColdDB<E, Hot, Cold>>,
anchor: &BeaconSnapshot<E>,
) -> Self {
) -> Result<Self, Error> {
let anchor_state = &anchor.beacon_state;
let mut anchor_block_header = anchor_state.latest_block_header().clone();
if anchor_block_header.state_root == Hash256::zero() {
Expand All @@ -194,21 +185,22 @@ where
root: anchor_root,
};
let finalized_checkpoint = justified_checkpoint;
let justified_balances = JustifiedBalances::from_justified_state(anchor_state)?;

Self {
Ok(Self {
store,
balances_cache: <_>::default(),
time: anchor_state.slot(),
justified_checkpoint,
justified_balances: anchor_state.balances().clone().into(),
paulhauner marked this conversation as resolved.
Show resolved Hide resolved
justified_balances,
finalized_checkpoint,
best_justified_checkpoint: justified_checkpoint,
unrealized_justified_checkpoint: justified_checkpoint,
unrealized_finalized_checkpoint: finalized_checkpoint,
proposer_boost_root: Hash256::zero(),
equivocating_indices: BTreeSet::new(),
_phantom: PhantomData,
}
})
}

/// Save the current state of `Self` to a `PersistedForkChoiceStore` which can be stored to the
Expand All @@ -219,7 +211,7 @@ where
time: self.time,
finalized_checkpoint: self.finalized_checkpoint,
justified_checkpoint: self.justified_checkpoint,
justified_balances: self.justified_balances.clone(),
justified_balances: self.justified_balances.effective_balances.clone(),
best_justified_checkpoint: self.best_justified_checkpoint,
unrealized_justified_checkpoint: self.unrealized_justified_checkpoint,
unrealized_finalized_checkpoint: self.unrealized_finalized_checkpoint,
Expand All @@ -233,13 +225,15 @@ where
persisted: PersistedForkChoiceStore,
store: Arc<HotColdDB<E, Hot, Cold>>,
) -> Result<Self, Error> {
let justified_balances =
JustifiedBalances::from_effective_balances(persisted.justified_balances)?;
Ok(Self {
store,
balances_cache: persisted.balances_cache,
time: persisted.time,
finalized_checkpoint: persisted.finalized_checkpoint,
justified_checkpoint: persisted.justified_checkpoint,
justified_balances: persisted.justified_balances,
justified_balances,
best_justified_checkpoint: persisted.best_justified_checkpoint,
unrealized_justified_checkpoint: persisted.unrealized_justified_checkpoint,
unrealized_finalized_checkpoint: persisted.unrealized_finalized_checkpoint,
Expand Down Expand Up @@ -279,7 +273,7 @@ where
&self.justified_checkpoint
}

fn justified_balances(&self) -> &[u64] {
fn justified_balances(&self) -> &JustifiedBalances {
&self.justified_balances
}

Expand Down Expand Up @@ -314,8 +308,9 @@ where
self.justified_checkpoint.root,
self.justified_checkpoint.epoch,
) {
// NOTE: could avoid this re-calculation by introducing a `PersistedCacheItem`.
metrics::inc_counter(&metrics::BALANCES_CACHE_HITS);
self.justified_balances = balances;
self.justified_balances = JustifiedBalances::from_effective_balances(balances)?;
} else {
metrics::inc_counter(&metrics::BALANCES_CACHE_MISSES);
let justified_block = self
Expand All @@ -332,7 +327,7 @@ where
.map_err(Error::FailedToReadState)?
.ok_or_else(|| Error::MissingState(justified_block.state_root()))?;

self.justified_balances = get_effective_balances(&state);
self.justified_balances = JustifiedBalances::from_justified_state(&state)?;
}

Ok(())
Expand Down
26 changes: 22 additions & 4 deletions beacon_node/beacon_chain/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use fork_choice::{ForkChoice, ResetPayloadStatuses};
use futures::channel::mpsc::Sender;
use operation_pool::{OperationPool, PersistedOperationPool};
use parking_lot::RwLock;
use proto_array::ReOrgThreshold;
use slasher::Slasher;
use slog::{crit, error, info, Logger};
use slot_clock::{SlotClock, TestingSlotClock};
Expand All @@ -31,8 +32,8 @@ use std::time::Duration;
use store::{Error as StoreError, HotColdDB, ItemStore, KeyValueStoreOp};
use task_executor::{ShutdownReason, TaskExecutor};
use types::{
BeaconBlock, BeaconState, ChainSpec, Checkpoint, EthSpec, Graffiti, Hash256, PublicKeyBytes,
Signature, SignedBeaconBlock, Slot,
BeaconBlock, BeaconState, ChainSpec, Checkpoint, Epoch, EthSpec, Graffiti, Hash256,
PublicKeyBytes, Signature, SignedBeaconBlock, Slot,
};

/// An empty struct used to "witness" all the `BeaconChainTypes` traits. It has no user-facing
Expand Down Expand Up @@ -159,6 +160,21 @@ where
self
}

/// Sets the proposer re-org threshold.
pub fn proposer_re_org_threshold(mut self, threshold: Option<ReOrgThreshold>) -> Self {
self.chain_config.re_org_threshold = threshold;
self
}

/// Sets the proposer re-org max epochs since finalization.
pub fn proposer_re_org_max_epochs_since_finalization(
mut self,
epochs_since_finalization: Epoch,
) -> Self {
self.chain_config.re_org_max_epochs_since_finalization = epochs_since_finalization;
self
}

/// Sets the store (database).
///
/// Should generally be called early in the build chain.
Expand Down Expand Up @@ -358,7 +374,8 @@ where
let (genesis, updated_builder) = self.set_genesis_state(beacon_state)?;
self = updated_builder;

let fc_store = BeaconForkChoiceStore::get_forkchoice_store(store, &genesis);
let fc_store = BeaconForkChoiceStore::get_forkchoice_store(store, &genesis)
.map_err(|e| format!("Unable to initialize fork choice store: {e:?}"))?;
let current_slot = None;

let fork_choice = ForkChoice::from_anchor(
Expand Down Expand Up @@ -476,7 +493,8 @@ where
beacon_state: weak_subj_state,
};

let fc_store = BeaconForkChoiceStore::get_forkchoice_store(store, &snapshot);
let fc_store = BeaconForkChoiceStore::get_forkchoice_store(store, &snapshot)
.map_err(|e| format!("Unable to initialize fork choice store: {e:?}"))?;

let current_slot = Some(snapshot.beacon_block.slot());
let fork_choice = ForkChoice::from_anchor(
Expand Down
31 changes: 29 additions & 2 deletions beacon_node/beacon_chain/src/canonical_head.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@
use crate::persisted_fork_choice::PersistedForkChoice;
use crate::{
beacon_chain::{
BeaconForkChoice, BeaconStore, BLOCK_PROCESSING_CACHE_LOCK_TIMEOUT, FORK_CHOICE_DB_KEY,
BeaconForkChoice, BeaconStore, OverrideForkchoiceUpdate,
BLOCK_PROCESSING_CACHE_LOCK_TIMEOUT, FORK_CHOICE_DB_KEY,
},
block_times_cache::BlockTimesCache,
events::ServerSentEventHandler,
Expand Down Expand Up @@ -114,6 +115,11 @@ impl<E: EthSpec> CachedHead<E> {
self.snapshot.beacon_block_root
}

/// Returns the root of the parent of the head block.
pub fn parent_block_root(&self) -> Hash256 {
self.snapshot.beacon_block.parent_root()
}

/// Returns root of the `BeaconState` at the head of the beacon chain.
///
/// ## Note
Expand Down Expand Up @@ -146,6 +152,21 @@ impl<E: EthSpec> CachedHead<E> {
Ok(root)
}

/// Returns the randao mix for the parent of the block at the head of the chain.
///
/// This is useful for re-orging the current head. The parent's RANDAO value is read from
/// the head's execution payload because it is unavailable in the beacon state's RANDAO mixes
/// array after being overwritten by the head block's RANDAO mix.
///
/// This will error if the head block is not execution-enabled (post Bellatrix).
pub fn parent_random(&self) -> Result<Hash256, BeaconStateError> {
self.snapshot
.beacon_block
.message()
.execution_payload()
.map(|payload| payload.prev_randao())
}

/// Returns the active validator count for the current epoch of the head state.
///
/// Should only return `None` if the caches have not been built on the head state (this should
Expand Down Expand Up @@ -765,6 +786,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
new_cached_head: &CachedHead<T::EthSpec>,
new_head_proto_block: ProtoBlock,
) -> Result<(), Error> {
let _timer = metrics::start_timer(&metrics::FORK_CHOICE_AFTER_NEW_HEAD_TIMES);
let old_snapshot = &old_cached_head.snapshot;
let new_snapshot = &new_cached_head.snapshot;
let new_head_is_optimistic = new_head_proto_block
Expand Down Expand Up @@ -902,6 +924,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
new_view: ForkChoiceView,
finalized_proto_block: ProtoBlock,
) -> Result<(), Error> {
let _timer = metrics::start_timer(&metrics::FORK_CHOICE_AFTER_FINALIZATION_TIMES);
let new_snapshot = &new_cached_head.snapshot;
let finalized_block_is_optimistic = finalized_proto_block
.execution_status
Expand Down Expand Up @@ -1124,7 +1147,11 @@ fn spawn_execution_layer_updates<T: BeaconChainTypes>(
}

if let Err(e) = chain
.update_execution_engine_forkchoice(current_slot, forkchoice_update_params)
.update_execution_engine_forkchoice(
current_slot,
forkchoice_update_params,
OverrideForkchoiceUpdate::Yes,
)
.await
{
crit!(
Expand Down
25 changes: 23 additions & 2 deletions beacon_node/beacon_chain/src/chain_config.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
pub use proto_array::CountUnrealizedFull;
pub use proto_array::{CountUnrealizedFull, ReOrgThreshold};
use serde_derive::{Deserialize, Serialize};
use types::Checkpoint;
use std::time::Duration;
use types::{Checkpoint, Epoch};

pub const DEFAULT_RE_ORG_THRESHOLD: ReOrgThreshold = ReOrgThreshold(20);
pub const DEFAULT_RE_ORG_MAX_EPOCHS_SINCE_FINALIZATION: Epoch = Epoch::new(2);
pub const DEFAULT_FORK_CHOICE_BEFORE_PROPOSAL_TIMEOUT: u64 = 250;

/// Default fraction of a slot lookahead for payload preparation (12/3 = 4 seconds on mainnet).
pub const DEFAULT_PREPARE_PAYLOAD_LOOKAHEAD_FACTOR: u32 = 3;

/// Fraction of a slot lookahead for fork choice in the state advance timer (500ms on mainnet).
pub const FORK_CHOICE_LOOKAHEAD_FACTOR: u32 = 24;

#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
pub struct ChainConfig {
/// Maximum number of slots to skip when importing a consensus message (e.g., block,
Expand All @@ -21,6 +30,10 @@ pub struct ChainConfig {
pub enable_lock_timeouts: bool,
/// The max size of a message that can be sent over the network.
pub max_network_size: usize,
/// Maximum percentage of committee weight at which to attempt re-orging the canonical head.
pub re_org_threshold: Option<ReOrgThreshold>,
/// Maximum number of epochs since finalization for attempting a proposer re-org.
pub re_org_max_epochs_since_finalization: Epoch,
/// Number of milliseconds to wait for fork choice before proposing a block.
///
/// If set to 0 then block proposal will not wait for fork choice at all.
Expand All @@ -47,6 +60,11 @@ pub struct ChainConfig {
pub count_unrealized_full: CountUnrealizedFull,
/// Optionally set timeout for calls to checkpoint sync endpoint.
pub checkpoint_sync_url_timeout: u64,
/// The offset before the start of a proposal slot at which payload attributes should be sent.
///
/// Low values are useful for execution engines which don't improve their payload after the
/// first call, and high values are useful for ensuring the EL is given ample notice.
pub prepare_payload_lookahead: Duration,
}

impl Default for ChainConfig {
Expand All @@ -57,6 +75,8 @@ impl Default for ChainConfig {
reconstruct_historic_states: false,
enable_lock_timeouts: true,
max_network_size: 10 * 1_048_576, // 10M
re_org_threshold: Some(DEFAULT_RE_ORG_THRESHOLD),
re_org_max_epochs_since_finalization: DEFAULT_RE_ORG_MAX_EPOCHS_SINCE_FINALIZATION,
fork_choice_before_proposal_timeout_ms: DEFAULT_FORK_CHOICE_BEFORE_PROPOSAL_TIMEOUT,
// Builder fallback configs that are set in `clap` will override these.
builder_fallback_skips: 3,
Expand All @@ -68,6 +88,7 @@ impl Default for ChainConfig {
paranoid_block_proposal: false,
count_unrealized_full: CountUnrealizedFull::default(),
checkpoint_sync_url_timeout: 60,
prepare_payload_lookahead: Duration::from_secs(4),
}
}
}
4 changes: 3 additions & 1 deletion beacon_node/beacon_chain/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ pub enum BeaconChainError {
MissingPersistedForkChoice,
CommitteePromiseFailed(oneshot_broadcast::Error),
MaxCommitteePromises(usize),
ProposerHeadForkChoiceError(fork_choice::Error<proto_array::Error>),
}

easy_from_to!(SlotProcessingError, BeaconChainError);
Expand Down Expand Up @@ -234,6 +235,7 @@ pub enum BlockProductionError {
UnableToProduceAtSlot(Slot),
SlotProcessingError(SlotProcessingError),
BlockProcessingError(BlockProcessingError),
ForkChoiceError(ForkChoiceError),
Eth1ChainError(Eth1ChainError),
BeaconStateError(BeaconStateError),
StateAdvanceError(StateAdvanceError),
Expand All @@ -252,7 +254,6 @@ pub enum BlockProductionError {
FailedToReadFinalizedBlock(store::Error),
MissingFinalizedBlock(Hash256),
BlockTooLarge(usize),
ForkChoiceError(BeaconChainError),
ShuttingDown,
MissingSyncAggregate,
MissingExecutionPayload,
Expand All @@ -265,3 +266,4 @@ easy_from_to!(BeaconStateError, BlockProductionError);
easy_from_to!(SlotProcessingError, BlockProductionError);
easy_from_to!(Eth1ChainError, BlockProductionError);
easy_from_to!(StateAdvanceError, BlockProductionError);
easy_from_to!(ForkChoiceError, BlockProductionError);
Loading