Skip to content

Commit 0a53a24

Browse files
Merge branch 'release-v7.0.0' into reject-attestations-prior-to-split
2 parents 5c56a83 + 27aabe8 commit 0a53a24

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+1278
-158
lines changed

beacon_node/beacon_chain/src/attestation_rewards.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,10 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
4747
.state_root_at_slot(state_slot)?
4848
.ok_or(BeaconChainError::NoStateForSlot(state_slot))?;
4949

50+
// This branch is reached from the HTTP API. We assume the user wants
51+
// to cache states so that future calls are faster.
5052
let state = self
51-
.get_state(&state_root, Some(state_slot))?
53+
.get_state(&state_root, Some(state_slot), true)?
5254
.ok_or(BeaconChainError::MissingBeaconState(state_root))?;
5355

5456
if state.fork_name_unchecked().altair_enabled() {

beacon_node/beacon_chain/src/attester_cache.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,8 +325,10 @@ impl AttesterCache {
325325
return Ok(value);
326326
}
327327

328+
// We use `cache_state = true` here because if we are attesting to the state it's likely
329+
// to be recent and useful for other things.
328330
let mut state: BeaconState<T::EthSpec> = chain
329-
.get_state(&state_root, None)?
331+
.get_state(&state_root, None, true)?
330332
.ok_or(Error::MissingBeaconState(state_root))?;
331333

332334
if state.slot() > slot {

beacon_node/beacon_chain/src/beacon_chain.rs

Lines changed: 74 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ use crate::light_client_optimistic_update_verification::{
4242
Error as LightClientOptimisticUpdateError, VerifiedLightClientOptimisticUpdate,
4343
};
4444
use crate::light_client_server_cache::LightClientServerCache;
45-
use crate::migrate::BackgroundMigrator;
45+
use crate::migrate::{BackgroundMigrator, ManualFinalizationNotification};
4646
use crate::naive_aggregation_pool::{
4747
AggregatedAttestationMap, Error as NaiveAggregationError, NaiveAggregationPool,
4848
SyncContributionAggregateMap,
@@ -118,8 +118,8 @@ use std::sync::Arc;
118118
use std::time::Duration;
119119
use store::iter::{BlockRootsIterator, ParentRootBlockIterator, StateRootsIterator};
120120
use store::{
121-
BlobSidecarListFromRoot, DatabaseBlock, Error as DBError, HotColdDB, KeyValueStore,
122-
KeyValueStoreOp, StoreItem, StoreOp,
121+
BlobSidecarListFromRoot, DatabaseBlock, Error as DBError, HotColdDB, HotStateSummary,
122+
KeyValueStore, KeyValueStoreOp, StoreItem, StoreOp,
123123
};
124124
use task_executor::{ShutdownReason, TaskExecutor};
125125
use tokio::sync::oneshot;
@@ -815,8 +815,10 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
815815
let block = self
816816
.get_blinded_block(&block_root)?
817817
.ok_or(Error::MissingBeaconBlock(block_root))?;
818+
// This method is only used in tests, so we may as well cache states to make CI go brr.
819+
// TODO(release-v7) move this method out of beacon chain and into `store_tests`` or something equivalent.
818820
let state = self
819-
.get_state(&block.state_root(), Some(block.slot()))?
821+
.get_state(&block.state_root(), Some(block.slot()), true)?
820822
.ok_or_else(|| Error::MissingBeaconState(block.state_root()))?;
821823
let iter = BlockRootsIterator::owned(&self.store, state);
822824
Ok(std::iter::once(Ok((block_root, block.slot())))
@@ -1343,8 +1345,9 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
13431345
&self,
13441346
state_root: &Hash256,
13451347
slot: Option<Slot>,
1348+
update_cache: bool,
13461349
) -> Result<Option<BeaconState<T::EthSpec>>, Error> {
1347-
Ok(self.store.get_state(state_root, slot)?)
1350+
Ok(self.store.get_state(state_root, slot, update_cache)?)
13481351
}
13491352

13501353
/// Return the sync committee at `slot + 1` from the canonical chain.
@@ -1519,8 +1522,14 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
15191522
})?
15201523
.ok_or(Error::NoStateForSlot(slot))?;
15211524

1525+
// This branch is mostly reached from the HTTP API when doing analysis, or in niche
1526+
// situations when producing a block. In the HTTP API case we assume the user wants
1527+
// to cache states so that future calls are faster, and that if the cache is
1528+
// struggling due to non-finality that they will dial down inessential calls. In the
1529+
// block proposal case we want to cache the state so that we can process the block
1530+
// quickly after it has been signed.
15221531
Ok(self
1523-
.get_state(&state_root, Some(slot))?
1532+
.get_state(&state_root, Some(slot), true)?
15241533
.ok_or(Error::NoStateForSlot(slot))?)
15251534
}
15261535
}
@@ -1702,6 +1711,41 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
17021711
}
17031712
}
17041713

1714+
pub fn manually_finalize_state(
1715+
&self,
1716+
state_root: Hash256,
1717+
checkpoint: Checkpoint,
1718+
) -> Result<(), Error> {
1719+
let HotStateSummary {
1720+
slot,
1721+
latest_block_root,
1722+
..
1723+
} = self
1724+
.store
1725+
.load_hot_state_summary(&state_root)
1726+
.map_err(BeaconChainError::DBError)?
1727+
.ok_or(BeaconChainError::MissingHotStateSummary(state_root))?;
1728+
1729+
if slot != checkpoint.epoch.start_slot(T::EthSpec::slots_per_epoch())
1730+
|| latest_block_root != *checkpoint.root
1731+
{
1732+
return Err(BeaconChainError::InvalidCheckpoint {
1733+
state_root,
1734+
checkpoint,
1735+
});
1736+
}
1737+
1738+
let notif = ManualFinalizationNotification {
1739+
state_root: state_root.into(),
1740+
checkpoint,
1741+
head_tracker: self.head_tracker.clone(),
1742+
genesis_block_root: self.genesis_block_root,
1743+
};
1744+
1745+
self.store_migrator.process_manual_finalization(notif);
1746+
Ok(())
1747+
}
1748+
17051749
/// Returns an aggregated `Attestation`, if any, that has a matching `attestation.data`.
17061750
///
17071751
/// The attestation will be obtained from `self.naive_aggregation_pool`.
@@ -2857,6 +2901,15 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
28572901
chain_segment: Vec<RpcBlock<T::EthSpec>>,
28582902
notify_execution_layer: NotifyExecutionLayer,
28592903
) -> ChainSegmentResult {
2904+
for block in chain_segment.iter() {
2905+
if let Err(error) = self.check_invalid_block_roots(block.block_root()) {
2906+
return ChainSegmentResult::Failed {
2907+
imported_blocks: vec![],
2908+
error,
2909+
};
2910+
}
2911+
}
2912+
28602913
let mut imported_blocks = vec![];
28612914

28622915
// Filter uninteresting blocks from the chain segment in a blocking task.
@@ -3340,6 +3393,15 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
33403393
self.remove_notified(&block_root, r)
33413394
}
33423395

3396+
/// Check for known and configured invalid block roots before processing.
3397+
pub fn check_invalid_block_roots(&self, block_root: Hash256) -> Result<(), BlockError> {
3398+
if self.config.invalid_block_roots.contains(&block_root) {
3399+
Err(BlockError::KnownInvalidExecutionPayload(block_root))
3400+
} else {
3401+
Ok(())
3402+
}
3403+
}
3404+
33433405
/// Returns `Ok(block_root)` if the given `unverified_block` was successfully verified and
33443406
/// imported into the chain.
33453407
///
@@ -6898,9 +6960,11 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
68986960
})?;
68996961
let beacon_state_root = beacon_block.state_root();
69006962

6963+
// This branch is reached from the HTTP API. We assume the user wants
6964+
// to cache states so that future calls are faster.
69016965
let mut beacon_state = self
69026966
.store
6903-
.get_state(&beacon_state_root, Some(beacon_block.slot()))?
6967+
.get_state(&beacon_state_root, Some(beacon_block.slot()), true)?
69046968
.ok_or_else(|| {
69056969
Error::DBInconsistent(format!("Missing state {:?}", beacon_state_root))
69066970
})?;
@@ -7052,8 +7116,10 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
70527116

70537117
if signed_beacon_block.slot() % T::EthSpec::slots_per_epoch() == 0 {
70547118
let block = self.get_blinded_block(&block_hash).unwrap().unwrap();
7119+
// This branch is reached from the HTTP API. We assume the user wants
7120+
// to cache states so that future calls are faster.
70557121
let state = self
7056-
.get_state(&block.state_root(), Some(block.slot()))
7122+
.get_state(&block.state_root(), Some(block.slot()), true)
70577123
.unwrap()
70587124
.unwrap();
70597125
finalized_blocks.insert(state.finalized_checkpoint().root);

beacon_node/beacon_chain/src/block_verification.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,9 @@ pub enum BlockError {
282282
/// problems to worry about than losing peers, and we're doing the network a favour by
283283
/// disconnecting.
284284
ParentExecutionPayloadInvalid { parent_root: Hash256 },
285+
/// This is a known invalid block that was listed in Lighthouses configuration.
286+
/// At the moment this error is only relevant as part of the Holesky network recovery efforts.
287+
KnownInvalidExecutionPayload(Hash256),
285288
/// The block is a slashable equivocation from the proposer.
286289
///
287290
/// ## Peer scoring
@@ -862,6 +865,9 @@ impl<T: BeaconChainTypes> GossipVerifiedBlock<T> {
862865
return Err(BlockError::DuplicateFullyImported(block_root));
863866
}
864867

868+
// Do not process a block that is known to be invalid.
869+
chain.check_invalid_block_roots(block_root)?;
870+
865871
// Do not process a block that doesn't descend from the finalized root.
866872
//
867873
// We check this *before* we load the parent so that we can return a more detailed error.
@@ -1081,6 +1087,9 @@ impl<T: BeaconChainTypes> SignatureVerifiedBlock<T> {
10811087
.fork_name(&chain.spec)
10821088
.map_err(BlockError::InconsistentFork)?;
10831089

1090+
// Check whether the block is a banned block prior to loading the parent.
1091+
chain.check_invalid_block_roots(block_root)?;
1092+
10841093
let (mut parent, block) = load_parent(block, chain)?;
10851094

10861095
let state = cheap_state_advance_to_obtain_committees::<_, BlockError>(
@@ -1758,7 +1767,22 @@ pub fn check_block_is_finalized_checkpoint_or_descendant<
17581767
fork_choice: &BeaconForkChoice<T>,
17591768
block: B,
17601769
) -> Result<B, BlockError> {
1761-
if fork_choice.is_finalized_checkpoint_or_descendant(block.parent_root()) {
1770+
// If we have a split block newer than finalization then we also ban blocks which are not
1771+
// descended from that split block. It's important not to try checking `is_descendant` if
1772+
// finality is ahead of the split and the split block has been pruned, as `is_descendant` will
1773+
// return `false` in this case.
1774+
let finalized_slot = fork_choice
1775+
.finalized_checkpoint()
1776+
.epoch
1777+
.start_slot(T::EthSpec::slots_per_epoch());
1778+
let split = chain.store.get_split_info();
1779+
let is_descendant_from_split_block = split.slot == 0
1780+
|| split.slot <= finalized_slot
1781+
|| fork_choice.is_descendant(split.block_root, block.parent_root());
1782+
1783+
if fork_choice.is_finalized_checkpoint_or_descendant(block.parent_root())
1784+
&& is_descendant_from_split_block
1785+
{
17621786
Ok(block)
17631787
} else {
17641788
// If fork choice does *not* consider the parent to be a descendant of the finalized block,

beacon_node/beacon_chain/src/builder.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,8 +298,13 @@ where
298298
.get_blinded_block(&chain.genesis_block_root)
299299
.map_err(|e| descriptive_db_error("genesis block", &e))?
300300
.ok_or("Genesis block not found in store")?;
301+
// We're resuming from some state in the db so it makes sense to cache it.
301302
let genesis_state = store
302-
.get_state(&genesis_block.state_root(), Some(genesis_block.slot()))
303+
.get_state(
304+
&genesis_block.state_root(),
305+
Some(genesis_block.slot()),
306+
true,
307+
)
303308
.map_err(|e| descriptive_db_error("genesis state", &e))?
304309
.ok_or("Genesis state not found in store")?;
305310

beacon_node/beacon_chain/src/canonical_head.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -784,6 +784,12 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
784784
.execution_status
785785
.is_optimistic_or_invalid();
786786

787+
// Update the state cache so it doesn't mistakenly prune the new head.
788+
self.store
789+
.state_cache
790+
.lock()
791+
.update_head_block_root(new_cached_head.head_block_root());
792+
787793
// Detect and potentially report any re-orgs.
788794
let reorg_distance = detect_reorg(
789795
&old_snapshot.beacon_state,

beacon_node/beacon_chain/src/chain_config.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
pub use proto_array::{DisallowedReOrgOffsets, ReOrgThreshold};
22
use serde::{Deserialize, Serialize};
3-
use std::time::Duration;
4-
use types::{Checkpoint, Epoch};
3+
use std::str::FromStr;
4+
use std::{collections::HashSet, sync::LazyLock, time::Duration};
5+
use types::{Checkpoint, Epoch, Hash256};
56

67
pub const DEFAULT_RE_ORG_HEAD_THRESHOLD: ReOrgThreshold = ReOrgThreshold(20);
78
pub const DEFAULT_RE_ORG_PARENT_THRESHOLD: ReOrgThreshold = ReOrgThreshold(160);
@@ -19,6 +20,12 @@ pub const FORK_CHOICE_LOOKAHEAD_FACTOR: u32 = 24;
1920
/// Default sync tolerance epochs.
2021
pub const DEFAULT_SYNC_TOLERANCE_EPOCHS: u64 = 2;
2122

23+
/// Invalid block root to be banned from processing and importing on Holesky network by default.
24+
pub static INVALID_HOLESKY_BLOCK_ROOT: LazyLock<Hash256> = LazyLock::new(|| {
25+
Hash256::from_str("2db899881ed8546476d0b92c6aa9110bea9a4cd0dbeb5519eb0ea69575f1f359")
26+
.expect("valid block root")
27+
});
28+
2229
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
2330
pub struct ChainConfig {
2431
/// Maximum number of slots to skip when importing an attestation.
@@ -100,6 +107,11 @@ pub struct ChainConfig {
100107
/// The max distance between the head block and the current slot at which Lighthouse will
101108
/// consider itself synced and still serve validator-related requests.
102109
pub sync_tolerance_epochs: u64,
110+
/// Block roots of "banned" blocks which Lighthouse will refuse to import.
111+
///
112+
/// On Holesky there is a block which is added to this set by default but which can be removed
113+
/// by using `--invalid-block-roots ""`.
114+
pub invalid_block_roots: HashSet<Hash256>,
103115
}
104116

105117
impl Default for ChainConfig {
@@ -136,6 +148,7 @@ impl Default for ChainConfig {
136148
blob_publication_batches: 4,
137149
blob_publication_batch_interval: Duration::from_millis(300),
138150
sync_tolerance_epochs: DEFAULT_SYNC_TOLERANCE_EPOCHS,
151+
invalid_block_roots: HashSet::new(),
139152
}
140153
}
141154
}

beacon_node/beacon_chain/src/errors.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ pub enum BeaconChainError {
6161
ForkChoiceStoreError(ForkChoiceStoreError),
6262
MissingBeaconBlock(Hash256),
6363
MissingBeaconState(Hash256),
64+
MissingHotStateSummary(Hash256),
6465
SlotProcessingError(SlotProcessingError),
6566
EpochProcessingError(EpochProcessingError),
6667
StateAdvanceError(StateAdvanceError),
@@ -181,9 +182,9 @@ pub enum BeaconChainError {
181182
execution_block_hash: Option<ExecutionBlockHash>,
182183
},
183184
ForkchoiceUpdate(execution_layer::Error),
184-
FinalizedCheckpointMismatch {
185-
head_state: Checkpoint,
186-
fork_choice: Hash256,
185+
InvalidCheckpoint {
186+
state_root: Hash256,
187+
checkpoint: Checkpoint,
187188
},
188189
InvalidSlot(Slot),
189190
HeadBlockNotFullyVerified {

beacon_node/beacon_chain/src/fork_revert.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,9 @@ pub fn reset_fork_choice_to_finalization<E: EthSpec, Hot: ItemStore<E>, Cold: It
116116

117117
// Advance finalized state to finalized epoch (to handle skipped slots).
118118
let finalized_state_root = finalized_block.state_root();
119+
// The enshrined finalized state should be in the state cache.
119120
let mut finalized_state = store
120-
.get_state(&finalized_state_root, Some(finalized_block.slot()))
121+
.get_state(&finalized_state_root, Some(finalized_block.slot()), true)
121122
.map_err(|e| format!("Error loading finalized state: {:?}", e))?
122123
.ok_or_else(|| {
123124
format!(

beacon_node/beacon_chain/src/light_client_server_cache.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,8 +319,11 @@ impl<T: BeaconChainTypes> LightClientServerCache<T> {
319319
metrics::inc_counter(&metrics::LIGHT_CLIENT_SERVER_CACHE_PREV_BLOCK_CACHE_MISS);
320320

321321
// Compute the value, handling potential errors.
322+
// This state should already be cached. By electing not to cache it here
323+
// we remove any chance of the light client server from affecting the state cache.
324+
// We'd like the light client server to be as minimally invasive as possible.
322325
let mut state = store
323-
.get_state(block_state_root, Some(block_slot))?
326+
.get_state(block_state_root, Some(block_slot), false)?
324327
.ok_or_else(|| {
325328
BeaconChainError::DBInconsistent(format!("Missing state {:?}", block_state_root))
326329
})?;

0 commit comments

Comments
 (0)