Skip to content

Commit

Permalink
Add test and polish impl
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelsproul committed Jun 29, 2023
1 parent 048d481 commit e96c874
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 15 deletions.
40 changes: 35 additions & 5 deletions beacon_node/beacon_chain/src/beacon_chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,17 @@ pub struct PrePayloadAttributes {
pub parent_block_number: u64,
}

/// Information about a state/block at a specific slot.
#[derive(Debug, Clone, Copy)]
pub struct FinalizationAndCanonicity {
/// True if the slot of the state or block is finalized.
///
/// This alone DOES NOT imply that the state/block is finalized, use `self.is_finalized()`.
pub slot_is_finalized: bool,
/// True if the state or block is canonical at its slot.
pub canonical: bool,
}

/// Define whether a forkchoiceUpdate needs to be checked for an override (`Yes`) or has already
/// been checked (`AlreadyApplied`). It is safe to specify `Yes` even if re-orgs are disabled.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
Expand Down Expand Up @@ -426,6 +437,12 @@ pub struct BeaconChain<T: BeaconChainTypes> {

type BeaconBlockAndState<T, Payload> = (BeaconBlock<T, Payload>, BeaconState<T>);

impl FinalizationAndCanonicity {
pub fn is_finalized(self) -> bool {
self.slot_is_finalized && self.canonical
}
}

impl<T: BeaconChainTypes> BeaconChain<T> {
/// Checks if a block is finalized.
/// The finalization check is done with the block slot. The block root is used to verify that
Expand All @@ -450,22 +467,35 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
/// Checks if a state is finalized.
/// The finalization check is done with the slot. The state root is used to verify that
/// the finalized state is in the canonical chain.
pub fn is_canonical_finalized_state(
pub fn is_finalized_state(
&self,
state_root: &Hash256,
state_slot: Slot,
) -> Result<(bool, bool), Error> {
) -> Result<bool, Error> {
self.state_finalization_and_canonicity(state_root, state_slot)
.map(FinalizationAndCanonicity::is_finalized)
}

/// Fetch the finalization and canonicity status of the state with `state_root`.
pub fn state_finalization_and_canonicity(
&self,
state_root: &Hash256,
state_slot: Slot,
) -> Result<FinalizationAndCanonicity, Error> {
let finalized_slot = self
.canonical_head
.cached_head()
.finalized_checkpoint()
.epoch
.start_slot(T::EthSpec::slots_per_epoch());
let is_canonical = self
let slot_is_finalized = state_slot <= finalized_slot;
let canonical = self
.state_root_at_slot(state_slot)?
.map_or(false, |canonical_root| state_root == &canonical_root);
let is_finalized_slot = state_slot <= finalized_slot;
Ok((is_canonical, is_finalized_slot))
Ok(FinalizationAndCanonicity {
slot_is_finalized,
canonical,
})
}

/// Persists the head tracker and fork choice.
Expand Down
10 changes: 8 additions & 2 deletions beacon_node/beacon_chain/src/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -516,18 +516,24 @@ where
let validator_keypairs = self
.validator_keypairs
.expect("cannot build without validator keypairs");
let chain_config = self.chain_config.unwrap_or_default();
println!("HERE! {}", chain_config.epochs_per_migration);

let mut builder = BeaconChainBuilder::new(self.eth_spec_instance)
.logger(log.clone())
.custom_spec(spec)
.store(self.store.expect("cannot build without store"))
.store_migrator_config(MigratorConfig::default().blocking())
.store_migrator_config(
MigratorConfig::default()
.blocking()
.epochs_per_migration(chain_config.epochs_per_migration),
)
.task_executor(self.runtime.task_executor.clone())
.execution_layer(self.execution_layer)
.dummy_eth1_backend()
.expect("should build dummy backend")
.shutdown_sender(shutdown_tx)
.chain_config(self.chain_config.unwrap_or_default())
.chain_config(chain_config)
.event_handler(Some(ServerSentEventHandler::new_with_capacity(
log.clone(),
5,
Expand Down
14 changes: 9 additions & 5 deletions beacon_node/http_api/src/state_id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,17 +70,21 @@ impl StateId {
.map_err(BeaconChainError::DBError)
.map_err(warp_utils::reject::beacon_chain_error)?
{
let (canonical, finalized_slot) = chain
.is_canonical_finalized_state(root, hot_summary.slot)
let finalization_status = chain
.state_finalization_and_canonicity(root, hot_summary.slot)
.map_err(warp_utils::reject::beacon_chain_error)?;
let finalized = canonical && finalized_slot;
let finalized = finalization_status.is_finalized();
let fork_choice = chain.canonical_head.fork_choice_read_lock();
let execution_optimistic = if finalized_slot && !canonical {
let execution_optimistic = if finalization_status.slot_is_finalized
&& !finalization_status.canonical
{
// This block is permanently orphaned and has likely been pruned from fork
// choice. If it isn't found in fork choice, mark it optimistic to be on the
// safe side.
fork_choice
.is_optimistic_or_invalid_block(&hot_summary.latest_block_root)
.is_optimistic_or_invalid_block_no_fallback(
&hot_summary.latest_block_root,
)
.unwrap_or(true)
} else {
// This block is either old and finalized, or recent and unfinalized, so
Expand Down
75 changes: 73 additions & 2 deletions beacon_node/http_api/tests/interactive_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
use beacon_chain::{
chain_config::{DisallowedReOrgOffsets, ReOrgThreshold},
test_utils::{AttestationStrategy, BlockStrategy, SyncCommitteeStrategy},
ChainConfig,
};
use eth2::types::DepositContractData;
use eth2::types::{DepositContractData, StateId};
use execution_layer::{ForkchoiceState, PayloadAttributes};
use http_api::test_utils::InteractiveTester;
use parking_lot::Mutex;
Expand All @@ -17,7 +18,7 @@ use std::time::Duration;
use tree_hash::TreeHash;
use types::{
Address, Epoch, EthSpec, ExecPayload, ExecutionBlockHash, ForkName, FullPayload,
MainnetEthSpec, ProposerPreparationData, Slot,
MainnetEthSpec, MinimalEthSpec, ProposerPreparationData, Slot,
};

type E = MainnetEthSpec;
Expand Down Expand Up @@ -48,6 +49,76 @@ async fn deposit_contract_custom_network() {
assert_eq!(result, expected);
}

// Test that state lookups by root function correctly for states that are finalized but still
// present in the hot database, and have had their block pruned from fork choice.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn state_by_root_pruned_from_fork_choice() {
type E = MinimalEthSpec;

let validator_count = 24;
let spec = ForkName::latest().make_genesis_spec(E::default_spec());

let tester = InteractiveTester::<E>::new_with_initializer_and_mutator(
Some(spec.clone()),
validator_count,
Some(Box::new(move |builder| {
builder
.deterministic_keypairs(validator_count)
.fresh_ephemeral_store()
.chain_config(ChainConfig {
epochs_per_migration: 1024,
..ChainConfig::default()
})
})),
None,
)
.await;

let client = &tester.client;
let harness = &tester.harness;

// Create some chain depth and finalize beyond fork choice's pruning depth.
let num_epochs = 8_u64;
let num_initial = num_epochs * E::slots_per_epoch();
harness.advance_slot();
harness
.extend_chain_with_sync(
num_initial as usize,
BlockStrategy::OnCanonicalHead,
AttestationStrategy::AllValidators,
SyncCommitteeStrategy::NoValidators,
)
.await;

// Should now be finalized.
let finalized_epoch = harness.finalized_checkpoint().epoch;
assert_eq!(finalized_epoch, num_epochs - 2);

// The split slot should still be at 0.
assert_eq!(harness.chain.store.get_split_slot(), 0);

// States that are between the split and the finalized slot should be able to be looked up by
// state root.
for slot in 0..finalized_epoch.start_slot(E::slots_per_epoch()).as_u64() {
let state_root = harness
.chain
.state_root_at_slot(Slot::new(slot))
.unwrap()
.unwrap();
let response = client
.get_debug_beacon_states::<E>(StateId::Root(state_root))
.await
.unwrap()
.unwrap();

assert!(response.finalized.unwrap());
assert!(!response.execution_optimistic.unwrap());

let mut state = response.data;
assert_eq!(state.update_tree_hash_cache().unwrap(), state_root);
}
}

/// Data structure for tracking fork choice updates received by the mock execution layer.
#[derive(Debug, Default)]
struct ForkChoiceUpdates {
Expand Down
2 changes: 1 addition & 1 deletion lighthouse/tests/beacon_node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1772,7 +1772,7 @@ fn epochs_per_migration_default() {
.with_config(|config| {
assert_eq!(
config.chain.epochs_per_migration,
beacon_node::beacon_chain::migrate::DEFAULT_EPOCHS_PER_RUN
beacon_node::beacon_chain::migrate::DEFAULT_EPOCHS_PER_MIGRATION
)
});
}
Expand Down

0 comments on commit e96c874

Please sign in to comment.