Make re-org strat more cautious and add more config (#4151)

## Proposed Changes

This change attempts to prevent failed re-orgs by:

1. Lowering the re-org cutoff from 2s to 1s. This is informed by a failed re-org attempted by @yorickdowne's node. The failed block was requested in the 1.5-2s window due to a Vouch failure, and failed to propagate to the majority of the network before the attestation deadline at 4s.
2. Allow users to adjust their re-org cutoff depending on observed network conditions and their risk profile. The static 2 second cutoff was too rigid.
3. Add a `--proposer-reorg-disallowed-offsets` flag which can be used to prohibit reorgs at certain slots. This is intended to help workaround an issue whereby reorging blocks at slot 1 are currently taking ~1.6s to propagate on gossip rather than ~500ms. This is suspected to be due to a cache miss in current versions of Prysm, which should be fixed in their next release.

## Additional Info

I'm of two minds about removing the `shuffling_stable` check which checks for blocks at slot 0 in the epoch. If we removed it users would be able to configure Lighthouse to try reorging at slot 0, which likely wouldn't work very well due to interactions with the proposer index cache. I think we could leave it for now and revisit it later.
This commit is contained in:
Michael Sproul
2023-04-13 07:05:01 +00:00
parent 00cf5fc184
commit b90c0c3fb1
12 changed files with 218 additions and 18 deletions

View File

@@ -50,6 +50,7 @@ pub enum Error {
block_root: Hash256,
parent_root: Hash256,
},
InvalidEpochOffset(u64),
Arith(ArithError),
}

View File

@@ -8,8 +8,8 @@ mod ssz_container;
pub use crate::justified_balances::JustifiedBalances;
pub use crate::proto_array::{calculate_committee_fraction, InvalidationOperation};
pub use crate::proto_array_fork_choice::{
Block, DoNotReOrg, ExecutionStatus, ProposerHeadError, ProposerHeadInfo, ProtoArrayForkChoice,
ReOrgThreshold,
Block, DisallowedReOrgOffsets, DoNotReOrg, ExecutionStatus, ProposerHeadError,
ProposerHeadInfo, ProtoArrayForkChoice, ReOrgThreshold,
};
pub use error::Error;

View File

@@ -250,6 +250,9 @@ pub enum DoNotReOrg {
ParentDistance,
HeadDistance,
ShufflingUnstable,
DisallowedOffset {
offset: u64,
},
JustificationAndFinalizationNotCompetitive,
ChainNotFinalizing {
epochs_since_finalization: u64,
@@ -271,6 +274,9 @@ impl std::fmt::Display for DoNotReOrg {
Self::ParentDistance => write!(f, "parent too far from head"),
Self::HeadDistance => write!(f, "head too far from current slot"),
Self::ShufflingUnstable => write!(f, "shuffling unstable at epoch boundary"),
Self::DisallowedOffset { offset } => {
write!(f, "re-orgs disabled at offset {offset}")
}
Self::JustificationAndFinalizationNotCompetitive => {
write!(f, "justification or finalization not competitive")
}
@@ -304,6 +310,31 @@ impl std::fmt::Display for DoNotReOrg {
#[serde(transparent)]
pub struct ReOrgThreshold(pub u64);
/// New-type for disallowed re-org slots.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(transparent)]
pub struct DisallowedReOrgOffsets {
// Vecs are faster than hashmaps for small numbers of items.
offsets: Vec<u64>,
}
impl Default for DisallowedReOrgOffsets {
fn default() -> Self {
DisallowedReOrgOffsets { offsets: vec![0] }
}
}
impl DisallowedReOrgOffsets {
pub fn new<E: EthSpec>(offsets: Vec<u64>) -> Result<Self, Error> {
for &offset in &offsets {
if offset >= E::slots_per_epoch() {
return Err(Error::InvalidEpochOffset(offset));
}
}
Ok(Self { offsets })
}
}
#[derive(PartialEq)]
pub struct ProtoArrayForkChoice {
pub(crate) proto_array: ProtoArray,
@@ -460,6 +491,7 @@ impl ProtoArrayForkChoice {
canonical_head: Hash256,
justified_balances: &JustifiedBalances,
re_org_threshold: ReOrgThreshold,
disallowed_offsets: &DisallowedReOrgOffsets,
max_epochs_since_finalization: Epoch,
) -> Result<ProposerHeadInfo, ProposerHeadError<Error>> {
let info = self.get_proposer_head_info::<E>(
@@ -467,6 +499,7 @@ impl ProtoArrayForkChoice {
canonical_head,
justified_balances,
re_org_threshold,
disallowed_offsets,
max_epochs_since_finalization,
)?;
@@ -501,6 +534,7 @@ impl ProtoArrayForkChoice {
canonical_head: Hash256,
justified_balances: &JustifiedBalances,
re_org_threshold: ReOrgThreshold,
disallowed_offsets: &DisallowedReOrgOffsets,
max_epochs_since_finalization: Epoch,
) -> Result<ProposerHeadInfo, ProposerHeadError<Error>> {
let mut nodes = self
@@ -545,6 +579,12 @@ impl ProtoArrayForkChoice {
return Err(DoNotReOrg::ShufflingUnstable.into());
}
// Check allowed slot offsets.
let offset = (re_org_block_slot % E::slots_per_epoch()).as_u64();
if disallowed_offsets.offsets.contains(&offset) {
return Err(DoNotReOrg::DisallowedOffset { offset }.into());
}
// Check FFG.
let ffg_competitive = parent_node.unrealized_justified_checkpoint
== head_node.unrealized_justified_checkpoint