mirror of
https://github.com/sigp/lighthouse.git
synced 2026-04-26 01:03:40 +00:00
Revert bad blocks on missed fork (#2529)
## Issue Addressed Closes #2526 ## Proposed Changes If the head block fails to decode on start up, do two things: 1. Revert all blocks between the head and the most recent hard fork (to `fork_slot - 1`). 2. Reset fork choice so that it contains the new head, and all blocks back to the new head's finalized checkpoint. ## Additional Info I tweaked some of the beacon chain test harness stuff in order to make it generic enough to test with a non-zero slot clock on start-up. In the process I consolidated all the various `new_` methods into a single generic one which will hopefully serve all future uses 🤞
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
use crate::beacon_chain::{BEACON_CHAIN_DB_KEY, ETH1_CACHE_DB_KEY, OP_POOL_DB_KEY};
|
||||
use crate::eth1_chain::{CachingEth1Backend, SszEth1};
|
||||
use crate::fork_revert::{reset_fork_choice_to_finalization, revert_to_fork_boundary};
|
||||
use crate::head_tracker::HeadTracker;
|
||||
use crate::migrate::{BackgroundMigrator, MigratorConfig};
|
||||
use crate::persisted_beacon_chain::PersistedBeaconChain;
|
||||
@@ -19,7 +20,7 @@ use futures::channel::mpsc::Sender;
|
||||
use operation_pool::{OperationPool, PersistedOperationPool};
|
||||
use parking_lot::RwLock;
|
||||
use slasher::Slasher;
|
||||
use slog::{crit, info, Logger};
|
||||
use slog::{crit, error, info, Logger};
|
||||
use slot_clock::{SlotClock, TestingSlotClock};
|
||||
use std::marker::PhantomData;
|
||||
use std::sync::Arc;
|
||||
@@ -135,6 +136,11 @@ where
|
||||
self
|
||||
}
|
||||
|
||||
/// Get a reference to the builder's spec.
|
||||
pub fn get_spec(&self) -> &ChainSpec {
|
||||
&self.spec
|
||||
}
|
||||
|
||||
/// Sets the maximum number of blocks that will be skipped when processing
|
||||
/// some consensus messages.
|
||||
///
|
||||
@@ -320,10 +326,10 @@ where
|
||||
|
||||
let fc_store = BeaconForkChoiceStore::get_forkchoice_store(store, &genesis);
|
||||
|
||||
let fork_choice = ForkChoice::from_genesis(
|
||||
let fork_choice = ForkChoice::from_anchor(
|
||||
fc_store,
|
||||
genesis.beacon_block_root,
|
||||
&genesis.beacon_block.deconstruct().0,
|
||||
&genesis.beacon_block,
|
||||
&genesis.beacon_state,
|
||||
)
|
||||
.map_err(|e| format!("Unable to build initialize ForkChoice: {:?}", e))?;
|
||||
@@ -356,6 +362,13 @@ where
|
||||
self
|
||||
}
|
||||
|
||||
/// Fetch a reference to the slot clock.
|
||||
///
|
||||
/// Can be used for mutation during testing due to `SlotClock`'s internal mutability.
|
||||
pub fn get_slot_clock(&self) -> Option<&TSlotClock> {
|
||||
self.slot_clock.as_ref()
|
||||
}
|
||||
|
||||
/// Sets a `Sender` to allow the beacon chain to send shutdown signals.
|
||||
pub fn shutdown_sender(mut self, sender: Sender<ShutdownReason>) -> Self {
|
||||
self.shutdown_sender = Some(sender);
|
||||
@@ -427,6 +440,7 @@ where
|
||||
let mut validator_monitor = self
|
||||
.validator_monitor
|
||||
.ok_or("Cannot build without a validator monitor")?;
|
||||
let head_tracker = Arc::new(self.head_tracker.unwrap_or_default());
|
||||
|
||||
let current_slot = if slot_clock
|
||||
.is_prior_to_genesis()
|
||||
@@ -437,20 +451,57 @@ where
|
||||
slot_clock.now().ok_or("Unable to read slot")?
|
||||
};
|
||||
|
||||
let head_block_root = fork_choice
|
||||
let initial_head_block_root = fork_choice
|
||||
.get_head(current_slot)
|
||||
.map_err(|e| format!("Unable to get fork choice head: {:?}", e))?;
|
||||
|
||||
let head_block = store
|
||||
.get_block(&head_block_root)
|
||||
.map_err(|e| descriptive_db_error("head block", &e))?
|
||||
.ok_or("Head block not found in store")?;
|
||||
// Try to decode the head block according to the current fork, if that fails, try
|
||||
// to backtrack to before the most recent fork.
|
||||
let (head_block_root, head_block, head_reverted) =
|
||||
match store.get_block(&initial_head_block_root) {
|
||||
Ok(Some(block)) => (initial_head_block_root, block, false),
|
||||
Ok(None) => return Err("Head block not found in store".into()),
|
||||
Err(StoreError::SszDecodeError(_)) => {
|
||||
error!(
|
||||
log,
|
||||
"Error decoding head block";
|
||||
"message" => "This node has likely missed a hard fork. \
|
||||
It will try to revert the invalid blocks and keep running, \
|
||||
but any stray blocks and states will not be deleted. \
|
||||
Long-term you should consider re-syncing this node."
|
||||
);
|
||||
let (block_root, block) = revert_to_fork_boundary(
|
||||
current_slot,
|
||||
initial_head_block_root,
|
||||
store.clone(),
|
||||
&self.spec,
|
||||
&log,
|
||||
)?;
|
||||
|
||||
// Update head tracker.
|
||||
head_tracker.register_block(block_root, block.parent_root(), block.slot());
|
||||
(block_root, block, true)
|
||||
}
|
||||
Err(e) => return Err(descriptive_db_error("head block", &e)),
|
||||
};
|
||||
|
||||
let head_state_root = head_block.state_root();
|
||||
let head_state = store
|
||||
.get_state(&head_state_root, Some(head_block.slot()))
|
||||
.map_err(|e| descriptive_db_error("head state", &e))?
|
||||
.ok_or("Head state not found in store")?;
|
||||
|
||||
// If the head reverted then we need to reset fork choice using the new head's finalized
|
||||
// checkpoint.
|
||||
if head_reverted {
|
||||
fork_choice = reset_fork_choice_to_finalization(
|
||||
head_block_root,
|
||||
&head_state,
|
||||
store.clone(),
|
||||
&self.spec,
|
||||
)?;
|
||||
}
|
||||
|
||||
let mut canonical_head = BeaconSnapshot {
|
||||
beacon_block_root: head_block_root,
|
||||
beacon_block: head_block,
|
||||
@@ -541,7 +592,7 @@ where
|
||||
genesis_state_root,
|
||||
fork_choice: RwLock::new(fork_choice),
|
||||
event_handler: self.event_handler,
|
||||
head_tracker: Arc::new(self.head_tracker.unwrap_or_default()),
|
||||
head_tracker,
|
||||
snapshot_cache: TimeoutRwLock::new(SnapshotCache::new(
|
||||
DEFAULT_SNAPSHOT_CACHE_SIZE,
|
||||
canonical_head,
|
||||
|
||||
Reference in New Issue
Block a user