Merge branch 'unstable' into merge-unstable-to-deneb-20230822

# Conflicts:
#	beacon_node/beacon_chain/src/builder.rs
#	beacon_node/beacon_chain/tests/store_tests.rs
#	beacon_node/client/src/builder.rs
#	beacon_node/src/config.rs
#	beacon_node/store/src/hot_cold_store.rs
#	lighthouse/tests/beacon_node.rs
This commit is contained in:
Jimmy Chen
2023-08-22 21:20:47 +10:00
41 changed files with 729 additions and 375 deletions
+8 -5
View File
@@ -4943,6 +4943,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
self.log,
"Produced block on state";
"block_size" => block_size,
"slot" => block.slot(),
);
metrics::observe(&metrics::BLOCK_SIZE, block_size as f64);
@@ -5921,14 +5922,16 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
let (mut state, state_root) = if let Some((state, state_root)) = head_state_opt {
(state, state_root)
} else {
let state_root = head_block.state_root;
let state = self
let block_state_root = head_block.state_root;
let max_slot = shuffling_epoch.start_slot(T::EthSpec::slots_per_epoch());
let (state_root, state) = self
.store
.get_inconsistent_state_for_attestation_verification_only(
&state_root,
Some(head_block.slot),
&head_block_root,
max_slot,
block_state_root,
)?
.ok_or(Error::MissingBeaconState(head_block.state_root))?;
.ok_or(Error::MissingBeaconState(block_state_root))?;
(state, state_root)
};
@@ -321,9 +321,17 @@ where
.deconstruct()
.0;
let state = self
let max_slot = self
.justified_checkpoint
.epoch
.start_slot(E::slots_per_epoch());
let (_, state) = self
.store
.get_state(&justified_block.state_root(), Some(justified_block.slot()))
.get_advanced_hot_state(
self.justified_checkpoint.root,
max_slot,
justified_block.state_root(),
)
.map_err(Error::FailedToReadState)?
.ok_or_else(|| Error::MissingState(justified_block.state_root()))?;
@@ -1358,7 +1358,7 @@ impl<T: BeaconChainTypes> ExecutionPendingBlock<T> {
// Perform a sanity check on the pre-state.
let parent_slot = parent.beacon_block.slot();
if state.slot() < parent_slot || state.slot() > parent_slot + 1 {
if state.slot() < parent_slot || state.slot() > block.slot() {
return Err(BeaconChainError::BadPreState {
parent_root: parent.beacon_block_root,
parent_slot,
@@ -1860,13 +1860,18 @@ fn load_parent<T: BeaconChainTypes, B: AsBlock<T::EthSpec>>(
BlockError::from(BeaconChainError::MissingBeaconBlock(block.parent_root()))
})?;
// Load the parent blocks state from the database, returning an error if it is not found.
// Load the parent block's state from the database, returning an error if it is not found.
// It is an error because if we know the parent block we should also know the parent state.
let parent_state_root = parent_block.state_root();
let parent_state = chain
.get_state(&parent_state_root, Some(parent_block.slot()))?
// Retrieve any state that is advanced through to at most `block.slot()`: this is
// particularly important if `block` descends from the finalized/split block, but at a slot
// prior to the finalized slot (which is invalid and inaccessible in our DB schema).
let (parent_state_root, parent_state) = chain
.store
.get_advanced_hot_state(root, block.slot(), parent_block.state_root())?
.ok_or_else(|| {
BeaconChainError::DBInconsistent(format!("Missing state {:?}", parent_state_root))
BeaconChainError::DBInconsistent(
format!("Missing state for parent block {root:?}",),
)
})?;
metrics::inc_counter(&metrics::BLOCK_PROCESSING_SNAPSHOT_CACHE_MISSES);
+49 -32
View File
@@ -26,8 +26,9 @@ use operation_pool::{OperationPool, PersistedOperationPool};
use parking_lot::RwLock;
use proto_array::{DisallowedReOrgOffsets, ReOrgThreshold};
use slasher::Slasher;
use slog::{crit, error, info, Logger};
use slog::{crit, debug, error, info, Logger};
use slot_clock::{SlotClock, TestingSlotClock};
use state_processing::per_slot_processing;
use std::marker::PhantomData;
use std::sync::Arc;
use std::time::Duration;
@@ -291,7 +292,7 @@ where
let genesis_state = store
.get_state(&genesis_block.state_root(), Some(genesis_block.slot()))
.map_err(|e| descriptive_db_error("genesis state", &e))?
.ok_or("Genesis block not found in store")?;
.ok_or("Genesis state not found in store")?;
self.genesis_time = Some(genesis_state.genesis_time());
@@ -386,6 +387,16 @@ where
let (genesis, updated_builder) = self.set_genesis_state(beacon_state)?;
self = updated_builder;
// Stage the database's metadata fields for atomic storage when `build` is called.
// Since v4.4.0 we will set the anchor with a dummy state upper limit in order to prevent
// historic states from being retained (unless `--reconstruct-historic-states` is set).
let retain_historic_states = self.chain_config.reconstruct_historic_states;
self.pending_io_batch.push(
store
.init_anchor_info(genesis.beacon_block.message(), retain_historic_states)
.map_err(|e| format!("Failed to initialize genesis anchor: {:?}", e))?,
);
let fc_store = BeaconForkChoiceStore::get_forkchoice_store(store, &genesis)
.map_err(|e| format!("Unable to initialize fork choice store: {e:?}"))?;
let current_slot = None;
@@ -412,21 +423,28 @@ where
weak_subj_block: SignedBeaconBlock<TEthSpec>,
genesis_state: BeaconState<TEthSpec>,
) -> Result<Self, String> {
let store = self.store.clone().ok_or("genesis_state requires a store")?;
let store = self
.store
.clone()
.ok_or("weak_subjectivity_state requires a store")?;
let log = self
.log
.as_ref()
.ok_or("weak_subjectivity_state requires a log")?;
let weak_subj_slot = weak_subj_state.slot();
let weak_subj_block_root = weak_subj_block.canonical_root();
let weak_subj_state_root = weak_subj_block.state_root();
// Check that the given state lies on an epoch boundary. Due to the database only storing
// full states on epoch boundaries and at restore points it would be difficult to support
// starting from a mid-epoch state.
if weak_subj_slot % TEthSpec::slots_per_epoch() != 0 {
return Err(format!(
"Checkpoint state at slot {} is not aligned to epoch start. \
Please supply an aligned checkpoint with state.slot % 32 == 0",
weak_subj_slot,
));
// Ensure the state is advanced to an epoch boundary.
let slots_per_epoch = TEthSpec::slots_per_epoch();
if weak_subj_state.slot() % slots_per_epoch != 0 {
debug!(
log,
"Advancing checkpoint state to boundary";
"state_slot" => weak_subj_state.slot(),
"block_slot" => weak_subj_block.slot(),
);
while weak_subj_state.slot() % slots_per_epoch != 0 {
per_slot_processing(&mut weak_subj_state, None, &self.spec)
.map_err(|e| format!("Error advancing state: {e:?}"))?;
}
}
// Prime all caches before storing the state in the database and computing the tree hash
@@ -434,21 +452,20 @@ where
weak_subj_state
.build_caches(&self.spec)
.map_err(|e| format!("Error building caches on checkpoint state: {e:?}"))?;
weak_subj_state
let weak_subj_state_root = weak_subj_state
.update_tree_hash_cache()
.map_err(|e| format!("Error computing checkpoint state root: {:?}", e))?;
let latest_block_slot = weak_subj_state.latest_block_header().slot;
let weak_subj_slot = weak_subj_state.slot();
let weak_subj_block_root = weak_subj_block.canonical_root();
// We can only validate the block root if it exists in the state. We can't calculated it
// from the `latest_block_header` because the state root might be set to the zero hash.
if let Ok(state_slot_block_root) = weak_subj_state.get_block_root(latest_block_slot) {
if weak_subj_block_root != *state_slot_block_root {
return Err(format!(
"Snapshot state's most recent block root does not match block, expected: {:?}, got: {:?}",
weak_subj_block_root, state_slot_block_root
));
}
// Validate the state's `latest_block_header` against the checkpoint block.
let state_latest_block_root = weak_subj_state.get_latest_block_root(weak_subj_state_root);
if weak_subj_block_root != state_latest_block_root {
return Err(format!(
"Snapshot state's most recent block root does not match block, expected: {:?}, got: {:?}",
weak_subj_block_root, state_latest_block_root
));
}
// Check that the checkpoint state is for the same network as the genesis state.
@@ -464,7 +481,7 @@ where
// Set the store's split point *before* storing genesis so that genesis is stored
// immediately in the freezer DB.
store.set_split(weak_subj_slot, weak_subj_state_root);
store.set_split(weak_subj_slot, weak_subj_state_root, weak_subj_block_root);
let (_, updated_builder) = self.set_genesis_state(genesis_state)?;
self = updated_builder;
@@ -480,10 +497,11 @@ where
// Stage the database's metadata fields for atomic storage when `build` is called.
// This prevents the database from restarting in an inconsistent state if the anchor
// info or split point is written before the `PersistedBeaconChain`.
let retain_historic_states = self.chain_config.reconstruct_historic_states;
self.pending_io_batch.push(store.store_split_in_batch());
self.pending_io_batch.push(
store
.init_anchor_info(weak_subj_block.message())
.init_anchor_info(weak_subj_block.message(), retain_historic_states)
.map_err(|e| format!("Failed to initialize anchor info: {:?}", e))?,
);
@@ -685,9 +703,8 @@ where
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()))
let (_head_state_root, head_state) = store
.get_advanced_hot_state(head_block_root, current_slot, head_block.state_root())
.map_err(|e| descriptive_db_error("head state", &e))?
.ok_or("Head state not found in store")?;
+14 -9
View File
@@ -47,7 +47,8 @@ use crate::{
};
use eth2::types::{EventKind, SseChainReorg, SseFinalizedCheckpoint, SseHead, SseLateHead};
use fork_choice::{
ExecutionStatus, ForkChoiceView, ForkchoiceUpdateParameters, ProtoBlock, ResetPayloadStatuses,
ExecutionStatus, ForkChoiceStore, ForkChoiceView, ForkchoiceUpdateParameters, ProtoBlock,
ResetPayloadStatuses,
};
use itertools::process_results;
use parking_lot::{Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard};
@@ -298,10 +299,10 @@ impl<T: BeaconChainTypes> CanonicalHead<T> {
let beacon_block = store
.get_full_block(&beacon_block_root)?
.ok_or(Error::MissingBeaconBlock(beacon_block_root))?;
let beacon_state_root = beacon_block.state_root();
let beacon_state = store
.get_state(&beacon_state_root, Some(beacon_block.slot()))?
.ok_or(Error::MissingBeaconState(beacon_state_root))?;
let current_slot = fork_choice.fc_store().get_current_slot();
let (_, beacon_state) = store
.get_advanced_hot_state(beacon_block_root, current_slot, beacon_block.state_root())?
.ok_or(Error::MissingBeaconState(beacon_block.state_root()))?;
let snapshot = BeaconSnapshot {
beacon_block_root,
@@ -669,10 +670,14 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
.get_full_block(&new_view.head_block_root)?
.ok_or(Error::MissingBeaconBlock(new_view.head_block_root))?;
let beacon_state_root = beacon_block.state_root();
let beacon_state: BeaconState<T::EthSpec> = self
.get_state(&beacon_state_root, Some(beacon_block.slot()))?
.ok_or(Error::MissingBeaconState(beacon_state_root))?;
let (_, beacon_state) = self
.store
.get_advanced_hot_state(
new_view.head_block_root,
current_slot,
beacon_block.state_root(),
)?
.ok_or(Error::MissingBeaconState(beacon_block.state_root()))?;
Ok(BeaconSnapshot {
beacon_block: Arc::new(beacon_block),
+5 -2
View File
@@ -21,8 +21,11 @@ pub struct ServerSentEventHandler<T: EthSpec> {
}
impl<T: EthSpec> ServerSentEventHandler<T> {
pub fn new(log: Logger) -> Self {
Self::new_with_capacity(log, DEFAULT_CHANNEL_CAPACITY)
pub fn new(log: Logger, capacity_multiplier: usize) -> Self {
Self::new_with_capacity(
log,
capacity_multiplier.saturating_mul(DEFAULT_CHANNEL_CAPACITY),
)
}
pub fn new_with_capacity(log: Logger, capacity: usize) -> Self {
+7 -1
View File
@@ -289,6 +289,7 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> BackgroundMigrator<E, Ho
debug!(log, "Database consolidation started");
let finalized_state_root = notif.finalized_state_root;
let finalized_block_root = notif.finalized_checkpoint.root;
let finalized_state = match db.get_state(&finalized_state_root.into(), None) {
Ok(Some(state)) => state,
@@ -342,7 +343,12 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> BackgroundMigrator<E, Ho
}
};
match migrate_database(db.clone(), finalized_state_root.into(), &finalized_state) {
match migrate_database(
db.clone(),
finalized_state_root.into(),
finalized_block_root,
&finalized_state,
) {
Ok(()) => {}
Err(Error::HotColdDBError(HotColdDBError::FreezeSlotUnaligned(slot))) => {
debug!(
@@ -9,7 +9,7 @@ use beacon_chain::{
test_utils::{
test_spec, AttestationStrategy, BeaconChainHarness, BlockStrategy, EphemeralHarnessType,
},
BeaconChain, BeaconChainError, BeaconChainTypes, WhenSlotSkipped,
BeaconChain, BeaconChainError, BeaconChainTypes, ChainConfig, WhenSlotSkipped,
};
use genesis::{interop_genesis_state, DEFAULT_ETH1_BLOCK_HASH};
use int_to_bytes::int_to_bytes32;
@@ -47,6 +47,10 @@ fn get_harness(validator_count: usize) -> BeaconChainHarness<EphemeralHarnessTyp
let harness = BeaconChainHarness::builder(MainnetEthSpec)
.spec(spec)
.chain_config(ChainConfig {
reconstruct_historic_states: true,
..ChainConfig::default()
})
.keypairs(KEYPAIRS[0..validator_count].to_vec())
.fresh_ephemeral_store()
.mock_execution_layer()
@@ -79,6 +83,10 @@ fn get_harness_capella_spec(
let harness = BeaconChainHarness::builder(MainnetEthSpec)
.spec(spec.clone())
.chain_config(ChainConfig {
reconstruct_historic_states: true,
..ChainConfig::default()
})
.keypairs(validator_keypairs)
.withdrawal_keypairs(
KEYPAIRS[0..validator_count]
@@ -7,7 +7,8 @@ use beacon_chain::{
AvailabilityProcessingStatus, BeaconChain, BeaconChainTypes, ExecutionPendingBlock,
};
use beacon_chain::{
BeaconSnapshot, BlockError, ChainSegmentResult, IntoExecutionPendingBlock, NotifyExecutionLayer,
BeaconSnapshot, BlockError, ChainConfig, ChainSegmentResult, IntoExecutionPendingBlock,
NotifyExecutionLayer,
};
use lazy_static::lazy_static;
use logging::test_logger;
@@ -138,6 +139,10 @@ async fn get_chain_segment_with_signed_blobs() -> (
fn get_harness(validator_count: usize) -> BeaconChainHarness<EphemeralHarnessType<E>> {
let harness = BeaconChainHarness::builder(MainnetEthSpec)
.default_spec()
.chain_config(ChainConfig {
reconstruct_historic_states: true,
..ChainConfig::default()
})
.keypairs(KEYPAIRS[0..validator_count].to_vec())
.fresh_ephemeral_store()
.mock_execution_layer()
@@ -7,7 +7,7 @@ use beacon_chain::otb_verification_service::{
use beacon_chain::{
canonical_head::{CachedHead, CanonicalHead},
test_utils::{BeaconChainHarness, EphemeralHarnessType},
BeaconChainError, BlockError, ExecutionPayloadError, NotifyExecutionLayer,
BeaconChainError, BlockError, ChainConfig, ExecutionPayloadError, NotifyExecutionLayer,
OverrideForkchoiceUpdate, StateSkipConfig, WhenSlotSkipped,
INVALID_FINALIZED_MERGE_TRANSITION_BLOCK_SHUTDOWN_REASON,
INVALID_JUSTIFIED_PAYLOAD_SHUTDOWN_REASON,
@@ -59,6 +59,10 @@ impl InvalidPayloadRig {
let harness = BeaconChainHarness::builder(MainnetEthSpec)
.spec(spec)
.chain_config(ChainConfig {
reconstruct_historic_states: true,
..ChainConfig::default()
})
.logger(test_logger())
.deterministic_keypairs(VALIDATOR_COUNT)
.mock_execution_layer()
+269 -89
View File
@@ -12,7 +12,7 @@ use beacon_chain::validator_monitor::DEFAULT_INDIVIDUAL_TRACKING_THRESHOLD;
use beacon_chain::{
data_availability_checker::MaybeAvailableBlock, historical_blocks::HistoricalBlockError,
migrate::MigratorConfig, BeaconChain, BeaconChainError, BeaconChainTypes, BeaconSnapshot,
ChainConfig, NotifyExecutionLayer, ServerSentEventHandler, WhenSlotSkipped,
BlockError, ChainConfig, NotifyExecutionLayer, ServerSentEventHandler, WhenSlotSkipped,
};
use eth2_network_config::get_trusted_setup;
use kzg::TrustedSetup;
@@ -20,7 +20,8 @@ use lazy_static::lazy_static;
use logging::test_logger;
use maplit::hashset;
use rand::Rng;
use state_processing::BlockReplayer;
use slot_clock::{SlotClock, TestingSlotClock};
use state_processing::{state_advance::complete_state_advance, BlockReplayer};
use std::collections::HashMap;
use std::collections::HashSet;
use std::convert::TryInto;
@@ -77,6 +78,19 @@ fn get_store_with_spec(
fn get_harness(
store: Arc<HotColdDB<E, LevelDB<E>, LevelDB<E>>>,
validator_count: usize,
) -> TestHarness {
// Most tests expect to retain historic states, so we use this as the default.
let chain_config = ChainConfig {
reconstruct_historic_states: true,
..ChainConfig::default()
};
get_harness_generic(store, validator_count, chain_config)
}
fn get_harness_generic(
store: Arc<HotColdDB<E, LevelDB<E>, LevelDB<E>>>,
validator_count: usize,
chain_config: ChainConfig,
) -> TestHarness {
let harness = TestHarness::builder(MinimalEthSpec)
.default_spec()
@@ -84,6 +98,7 @@ fn get_harness(
.logger(store.logger().clone())
.fresh_disk_store(store)
.mock_execution_layer()
.chain_config(chain_config)
.build();
harness.advance_slot();
harness
@@ -472,13 +487,15 @@ async fn block_replay_with_inaccurate_state_roots() {
.await;
// Slot must not be 0 mod 32 or else no blocks will be replayed.
let (mut head_state, head_root) = harness.get_current_state_and_root();
let (mut head_state, head_state_root) = harness.get_current_state_and_root();
let head_block_root = harness.head_block_root();
assert_ne!(head_state.slot() % 32, 0);
let mut fast_head_state = store
let (_, mut fast_head_state) = store
.get_inconsistent_state_for_attestation_verification_only(
&head_root,
Some(head_state.slot()),
&head_block_root,
head_state.slot(),
head_state_root,
)
.unwrap()
.unwrap();
@@ -577,14 +594,7 @@ async fn block_replayer_hooks() {
async fn delete_blocks_and_states() {
let db_path = tempdir().unwrap();
let store = get_store(&db_path);
let validators_keypairs =
types::test_utils::generate_deterministic_keypairs(LOW_VALIDATOR_COUNT);
let harness = TestHarness::builder(MinimalEthSpec)
.default_spec()
.keypairs(validators_keypairs)
.fresh_disk_store(store.clone())
.mock_execution_layer()
.build();
let harness = get_harness(store.clone(), LOW_VALIDATOR_COUNT);
let unforked_blocks: u64 = 4 * E::slots_per_epoch();
@@ -1027,18 +1037,14 @@ fn check_shuffling_compatible(
// Ensure blocks from abandoned forks are pruned from the Hot DB
#[tokio::test]
async fn prunes_abandoned_fork_between_two_finalized_checkpoints() {
const HONEST_VALIDATOR_COUNT: usize = 32 + 0;
const ADVERSARIAL_VALIDATOR_COUNT: usize = 16 - 0;
const HONEST_VALIDATOR_COUNT: usize = 32;
const ADVERSARIAL_VALIDATOR_COUNT: usize = 16;
const VALIDATOR_COUNT: usize = HONEST_VALIDATOR_COUNT + ADVERSARIAL_VALIDATOR_COUNT;
let validators_keypairs = types::test_utils::generate_deterministic_keypairs(VALIDATOR_COUNT);
let honest_validators: Vec<usize> = (0..HONEST_VALIDATOR_COUNT).collect();
let adversarial_validators: Vec<usize> = (HONEST_VALIDATOR_COUNT..VALIDATOR_COUNT).collect();
let rig = BeaconChainHarness::builder(MinimalEthSpec)
.default_spec()
.keypairs(validators_keypairs)
.fresh_ephemeral_store()
.mock_execution_layer()
.build();
let db_path = tempdir().unwrap();
let store = get_store(&db_path);
let rig = get_harness(store.clone(), VALIDATOR_COUNT);
let slots_per_epoch = rig.slots_per_epoch();
let (mut state, state_root) = rig.get_current_state_and_root();
@@ -1137,18 +1143,14 @@ async fn prunes_abandoned_fork_between_two_finalized_checkpoints() {
#[tokio::test]
async fn pruning_does_not_touch_abandoned_block_shared_with_canonical_chain() {
const HONEST_VALIDATOR_COUNT: usize = 32 + 0;
const ADVERSARIAL_VALIDATOR_COUNT: usize = 16 - 0;
const HONEST_VALIDATOR_COUNT: usize = 32;
const ADVERSARIAL_VALIDATOR_COUNT: usize = 16;
const VALIDATOR_COUNT: usize = HONEST_VALIDATOR_COUNT + ADVERSARIAL_VALIDATOR_COUNT;
let validators_keypairs = types::test_utils::generate_deterministic_keypairs(VALIDATOR_COUNT);
let honest_validators: Vec<usize> = (0..HONEST_VALIDATOR_COUNT).collect();
let adversarial_validators: Vec<usize> = (HONEST_VALIDATOR_COUNT..VALIDATOR_COUNT).collect();
let rig = BeaconChainHarness::builder(MinimalEthSpec)
.default_spec()
.keypairs(validators_keypairs)
.fresh_ephemeral_store()
.mock_execution_layer()
.build();
let db_path = tempdir().unwrap();
let store = get_store(&db_path);
let rig = get_harness(store.clone(), VALIDATOR_COUNT);
let slots_per_epoch = rig.slots_per_epoch();
let (state, state_root) = rig.get_current_state_and_root();
@@ -1272,15 +1274,11 @@ async fn pruning_does_not_touch_blocks_prior_to_finalization() {
const HONEST_VALIDATOR_COUNT: usize = 32;
const ADVERSARIAL_VALIDATOR_COUNT: usize = 16;
const VALIDATOR_COUNT: usize = HONEST_VALIDATOR_COUNT + ADVERSARIAL_VALIDATOR_COUNT;
let validators_keypairs = types::test_utils::generate_deterministic_keypairs(VALIDATOR_COUNT);
let honest_validators: Vec<usize> = (0..HONEST_VALIDATOR_COUNT).collect();
let adversarial_validators: Vec<usize> = (HONEST_VALIDATOR_COUNT..VALIDATOR_COUNT).collect();
let rig = BeaconChainHarness::builder(MinimalEthSpec)
.default_spec()
.keypairs(validators_keypairs)
.fresh_ephemeral_store()
.mock_execution_layer()
.build();
let db_path = tempdir().unwrap();
let store = get_store(&db_path);
let rig = get_harness(store.clone(), VALIDATOR_COUNT);
let slots_per_epoch = rig.slots_per_epoch();
let (mut state, state_root) = rig.get_current_state_and_root();
@@ -1364,18 +1362,14 @@ async fn pruning_does_not_touch_blocks_prior_to_finalization() {
#[tokio::test]
async fn prunes_fork_growing_past_youngest_finalized_checkpoint() {
const HONEST_VALIDATOR_COUNT: usize = 32 + 0;
const ADVERSARIAL_VALIDATOR_COUNT: usize = 16 - 0;
const HONEST_VALIDATOR_COUNT: usize = 32;
const ADVERSARIAL_VALIDATOR_COUNT: usize = 16;
const VALIDATOR_COUNT: usize = HONEST_VALIDATOR_COUNT + ADVERSARIAL_VALIDATOR_COUNT;
let validators_keypairs = types::test_utils::generate_deterministic_keypairs(VALIDATOR_COUNT);
let honest_validators: Vec<usize> = (0..HONEST_VALIDATOR_COUNT).collect();
let adversarial_validators: Vec<usize> = (HONEST_VALIDATOR_COUNT..VALIDATOR_COUNT).collect();
let rig = BeaconChainHarness::builder(MinimalEthSpec)
.default_spec()
.keypairs(validators_keypairs)
.fresh_ephemeral_store()
.mock_execution_layer()
.build();
let db_path = tempdir().unwrap();
let store = get_store(&db_path);
let rig = get_harness(store.clone(), VALIDATOR_COUNT);
let (state, state_root) = rig.get_current_state_and_root();
// Fill up 0th epoch with canonical chain blocks
@@ -1509,18 +1503,14 @@ async fn prunes_fork_growing_past_youngest_finalized_checkpoint() {
// This is to check if state outside of normal block processing are pruned correctly.
#[tokio::test]
async fn prunes_skipped_slots_states() {
const HONEST_VALIDATOR_COUNT: usize = 32 + 0;
const ADVERSARIAL_VALIDATOR_COUNT: usize = 16 - 0;
const HONEST_VALIDATOR_COUNT: usize = 32;
const ADVERSARIAL_VALIDATOR_COUNT: usize = 16;
const VALIDATOR_COUNT: usize = HONEST_VALIDATOR_COUNT + ADVERSARIAL_VALIDATOR_COUNT;
let validators_keypairs = types::test_utils::generate_deterministic_keypairs(VALIDATOR_COUNT);
let honest_validators: Vec<usize> = (0..HONEST_VALIDATOR_COUNT).collect();
let adversarial_validators: Vec<usize> = (HONEST_VALIDATOR_COUNT..VALIDATOR_COUNT).collect();
let rig = BeaconChainHarness::builder(MinimalEthSpec)
.default_spec()
.keypairs(validators_keypairs)
.fresh_ephemeral_store()
.mock_execution_layer()
.build();
let db_path = tempdir().unwrap();
let store = get_store(&db_path);
let rig = get_harness(store.clone(), VALIDATOR_COUNT);
let (state, state_root) = rig.get_current_state_and_root();
let canonical_slots_zeroth_epoch: Vec<Slot> =
@@ -1638,18 +1628,14 @@ async fn prunes_skipped_slots_states() {
// This is to check if state outside of normal block processing are pruned correctly.
#[tokio::test]
async fn finalizes_non_epoch_start_slot() {
const HONEST_VALIDATOR_COUNT: usize = 32 + 0;
const ADVERSARIAL_VALIDATOR_COUNT: usize = 16 - 0;
const HONEST_VALIDATOR_COUNT: usize = 32;
const ADVERSARIAL_VALIDATOR_COUNT: usize = 16;
const VALIDATOR_COUNT: usize = HONEST_VALIDATOR_COUNT + ADVERSARIAL_VALIDATOR_COUNT;
let validators_keypairs = types::test_utils::generate_deterministic_keypairs(VALIDATOR_COUNT);
let honest_validators: Vec<usize> = (0..HONEST_VALIDATOR_COUNT).collect();
let adversarial_validators: Vec<usize> = (HONEST_VALIDATOR_COUNT..VALIDATOR_COUNT).collect();
let rig = BeaconChainHarness::builder(MinimalEthSpec)
.default_spec()
.keypairs(validators_keypairs)
.fresh_ephemeral_store()
.mock_execution_layer()
.build();
let db_path = tempdir().unwrap();
let store = get_store(&db_path);
let rig = get_harness(store.clone(), VALIDATOR_COUNT);
let (state, state_root) = rig.get_current_state_and_root();
let canonical_slots_zeroth_epoch: Vec<Slot> =
@@ -2068,39 +2054,82 @@ async fn garbage_collect_temp_states_from_failed_block() {
}
#[tokio::test]
async fn weak_subjectivity_sync() {
async fn weak_subjectivity_sync_easy() {
let num_initial_slots = E::slots_per_epoch() * 11;
let checkpoint_slot = Slot::new(E::slots_per_epoch() * 9);
let slots = (1..num_initial_slots).map(Slot::new).collect();
weak_subjectivity_sync_test(slots, checkpoint_slot).await
}
#[tokio::test]
async fn weak_subjectivity_sync_unaligned_advanced_checkpoint() {
let num_initial_slots = E::slots_per_epoch() * 11;
let checkpoint_slot = Slot::new(E::slots_per_epoch() * 9);
let slots = (1..num_initial_slots)
.map(Slot::new)
.filter(|&slot| {
// Skip 3 slots leading up to the checkpoint slot.
slot <= checkpoint_slot - 3 || slot > checkpoint_slot
})
.collect();
weak_subjectivity_sync_test(slots, checkpoint_slot).await
}
#[tokio::test]
async fn weak_subjectivity_sync_unaligned_unadvanced_checkpoint() {
let num_initial_slots = E::slots_per_epoch() * 11;
let checkpoint_slot = Slot::new(E::slots_per_epoch() * 9 - 3);
let slots = (1..num_initial_slots)
.map(Slot::new)
.filter(|&slot| {
// Skip 3 slots after the checkpoint slot.
slot <= checkpoint_slot || slot > checkpoint_slot + 3
})
.collect();
weak_subjectivity_sync_test(slots, checkpoint_slot).await
}
async fn weak_subjectivity_sync_test(slots: Vec<Slot>, checkpoint_slot: Slot) {
// Build an initial chain on one harness, representing a synced node with full history.
let num_initial_blocks = E::slots_per_epoch() * 11;
let num_final_blocks = E::slots_per_epoch() * 2;
let temp1 = tempdir().unwrap();
let full_store = get_store(&temp1);
let harness = get_harness(full_store.clone(), LOW_VALIDATOR_COUNT);
let all_validators = (0..LOW_VALIDATOR_COUNT).collect::<Vec<_>>();
let (genesis_state, genesis_state_root) = harness.get_current_state_and_root();
harness
.extend_chain(
num_initial_blocks as usize,
BlockStrategy::OnCanonicalHead,
AttestationStrategy::AllValidators,
.add_attested_blocks_at_slots(
genesis_state.clone(),
genesis_state_root,
&slots,
&all_validators,
)
.await;
let genesis_state = full_store
.get_state(&harness.chain.genesis_state_root, Some(Slot::new(0)))
let wss_block_root = harness
.chain
.block_root_at_slot(checkpoint_slot, WhenSlotSkipped::Prev)
.unwrap()
.unwrap();
let wss_checkpoint = harness.finalized_checkpoint();
let wss_state_root = harness
.chain
.state_root_at_slot(checkpoint_slot)
.unwrap()
.unwrap();
let wss_block = harness
.chain
.store
.get_full_block(&wss_checkpoint.root)
.get_full_block(&wss_block_root)
.unwrap()
.unwrap();
let wss_state = full_store
.get_state(&wss_block.state_root(), None)
.get_state(&wss_state_root, Some(checkpoint_slot))
.unwrap()
.unwrap();
let wss_slot = wss_block.slot();
// Add more blocks that advance finalization further.
harness.advance_slot();
@@ -2130,19 +2159,25 @@ async fn weak_subjectivity_sync() {
None,
);
// Initialise a new beacon chain from the finalized checkpoint
// Initialise a new beacon chain from the finalized checkpoint.
// The slot clock must be set to a time ahead of the checkpoint state.
let slot_clock = TestingSlotClock::new(
Slot::new(0),
Duration::from_secs(harness.chain.genesis_time),
Duration::from_secs(seconds_per_slot),
);
slot_clock.set_slot(harness.get_current_slot().as_u64());
let beacon_chain = BeaconChainBuilder::<DiskHarnessType<E>>::new(MinimalEthSpec)
.store(store.clone())
.custom_spec(test_spec::<E>())
.task_executor(harness.chain.task_executor.clone())
.logger(log.clone())
.weak_subjectivity_state(wss_state, wss_block.clone(), genesis_state)
.unwrap()
.logger(log.clone())
.store_migrator_config(MigratorConfig::default().blocking())
.dummy_eth1_backend()
.expect("should build dummy backend")
.testing_slot_clock(Duration::from_secs(seconds_per_slot))
.expect("should configure testing slot clock")
.slot_clock(slot_clock)
.shutdown_sender(shutdown_tx)
.chain_config(ChainConfig::default())
.event_handler(Some(ServerSentEventHandler::new_with_capacity(
@@ -2159,9 +2194,9 @@ async fn weak_subjectivity_sync() {
// Apply blocks forward to reach head.
let chain_dump = harness.chain.chain_dump().unwrap();
let new_blocks = &chain_dump[wss_slot.as_usize() + 1..];
assert_eq!(new_blocks[0].beacon_block.slot(), wss_slot + 1);
let new_blocks = chain_dump
.iter()
.filter(|snapshot| snapshot.beacon_block.slot() > checkpoint_slot);
for snapshot in new_blocks {
let block_root = snapshot.beacon_block_root;
@@ -2271,13 +2306,17 @@ async fn weak_subjectivity_sync() {
assert_eq!(forwards, expected);
// All blocks can be loaded.
let mut prev_block_root = Hash256::zero();
for (block_root, slot) in beacon_chain
.forwards_iter_block_roots(Slot::new(0))
.unwrap()
.map(Result::unwrap)
{
let block = store.get_blinded_block(&block_root).unwrap().unwrap();
assert_eq!(block.slot(), slot);
if block_root != prev_block_root {
assert_eq!(block.slot(), slot);
}
prev_block_root = block_root;
}
// All states from the oldest state slot can be loaded.
@@ -2292,14 +2331,141 @@ async fn weak_subjectivity_sync() {
assert_eq!(state.canonical_root(), state_root);
}
// Anchor slot is still set to the starting slot.
assert_eq!(store.get_anchor_slot(), Some(wss_slot));
// Anchor slot is still set to the slot of the checkpoint block.
assert_eq!(store.get_anchor_slot(), Some(wss_block.slot()));
// Reconstruct states.
store.clone().reconstruct_historic_states().unwrap();
assert_eq!(store.get_anchor_slot(), None);
}
/// Test that blocks and attestations that refer to states around an unaligned split state are
/// processed correctly.
#[tokio::test]
async fn process_blocks_and_attestations_for_unaligned_checkpoint() {
let temp = tempdir().unwrap();
let store = get_store(&temp);
let chain_config = ChainConfig {
reconstruct_historic_states: false,
..ChainConfig::default()
};
let harness = get_harness_generic(store.clone(), LOW_VALIDATOR_COUNT, chain_config);
let all_validators = (0..LOW_VALIDATOR_COUNT).collect::<Vec<_>>();
let split_slot = Slot::new(E::slots_per_epoch() * 4);
let pre_skips = 1;
let post_skips = 1;
// Build the chain up to the intended split slot, with 3 skips before the split.
let slots = (1..=split_slot.as_u64() - pre_skips)
.map(Slot::new)
.collect::<Vec<_>>();
let (genesis_state, genesis_state_root) = harness.get_current_state_and_root();
harness
.add_attested_blocks_at_slots(
genesis_state.clone(),
genesis_state_root,
&slots,
&all_validators,
)
.await;
// Before the split slot becomes finalized, create two forking blocks that build on the split
// block:
//
// - one that is invalid because it conflicts with finalization (slot <= finalized_slot)
// - one that is valid because its slot is not finalized (slot > finalized_slot)
let (unadvanced_split_state, unadvanced_split_state_root) =
harness.get_current_state_and_root();
let (invalid_fork_block, _) = harness
.make_block(unadvanced_split_state.clone(), split_slot)
.await;
let (valid_fork_block, _) = harness
.make_block(unadvanced_split_state.clone(), split_slot + 1)
.await;
// Advance the chain so that the intended split slot is finalized.
// Do not attest in the epoch boundary slot, to make attestation production later easier (no
// equivocations).
let finalizing_slot = split_slot + 2 * E::slots_per_epoch();
for _ in 0..pre_skips + post_skips {
harness.advance_slot();
}
harness.extend_to_slot(finalizing_slot - 1).await;
harness
.add_block_at_slot(finalizing_slot, harness.get_current_state())
.await
.unwrap();
// Check that the split slot is as intended.
let split = store.get_split_info();
assert_eq!(split.slot, split_slot);
assert_eq!(split.block_root, valid_fork_block.parent_root());
assert_ne!(split.state_root, unadvanced_split_state_root);
// Applying the invalid block should fail.
let err = harness
.chain
.process_block(
invalid_fork_block.canonical_root(),
Arc::new(invalid_fork_block.clone()),
NotifyExecutionLayer::Yes,
|| Ok(()),
)
.await
.unwrap_err();
assert!(matches!(err, BlockError::WouldRevertFinalizedSlot { .. }));
// Applying the valid block should succeed, but it should not become head.
harness
.chain
.process_block(
valid_fork_block.canonical_root(),
Arc::new(valid_fork_block.clone()),
NotifyExecutionLayer::Yes,
|| Ok(()),
)
.await
.unwrap();
harness.chain.recompute_head_at_current_slot().await;
assert_ne!(harness.head_block_root(), valid_fork_block.canonical_root());
// Attestations to the split block in the next 2 epochs should be processed successfully.
let attestation_start_slot = harness.get_current_slot();
let attestation_end_slot = attestation_start_slot + 2 * E::slots_per_epoch();
let (split_state_root, mut advanced_split_state) = harness
.chain
.store
.get_advanced_hot_state(split.block_root, split.slot, split.state_root)
.unwrap()
.unwrap();
complete_state_advance(
&mut advanced_split_state,
Some(split_state_root),
attestation_start_slot,
&harness.chain.spec,
)
.unwrap();
advanced_split_state
.build_caches(&harness.chain.spec)
.unwrap();
let advanced_split_state_root = advanced_split_state.update_tree_hash_cache().unwrap();
for slot in (attestation_start_slot.as_u64()..attestation_end_slot.as_u64()).map(Slot::new) {
let attestations = harness.make_attestations(
&all_validators,
&advanced_split_state,
advanced_split_state_root,
split.block_root.into(),
slot,
);
harness.advance_slot();
harness.process_attestations(attestations);
}
}
#[tokio::test]
async fn finalizes_after_resuming_from_db() {
let validator_count = 16;
@@ -2358,6 +2524,7 @@ async fn finalizes_after_resuming_from_db() {
.default_spec()
.keypairs(KEYPAIRS[0..validator_count].to_vec())
.resumed_disk_store(store)
.testing_slot_clock(original_chain.slot_clock.clone())
.execution_layer(original_chain.execution_layer.clone())
.build();
@@ -2611,6 +2778,9 @@ async fn schema_downgrade_to_min_version() {
SchemaVersion(11)
};
// Save the slot clock so that the new harness doesn't revert in time.
let slot_clock = harness.chain.slot_clock.clone();
// Close the database to ensure everything is written to disk.
drop(store);
drop(harness);
@@ -2641,11 +2811,21 @@ async fn schema_downgrade_to_min_version() {
)
.expect("schema upgrade from minimum version should work");
// Rescreate the harness.
// Recreate the harness.
/*
let slot_clock = TestingSlotClock::new(
Slot::new(0),
Duration::from_secs(harness.chain.genesis_time),
Duration::from_secs(spec.seconds_per_slot),
);
slot_clock.set_slot(harness.get_current_slot().as_u64());
*/
let harness = BeaconChainHarness::builder(MinimalEthSpec)
.default_spec()
.keypairs(KEYPAIRS[0..LOW_VALIDATOR_COUNT].to_vec())
.logger(store.logger().clone())
.testing_slot_clock(slot_clock)
.resumed_disk_store(store.clone())
.mock_execution_layer()
.build();
+5 -1
View File
@@ -6,7 +6,7 @@ use beacon_chain::{
AttestationStrategy, BeaconChainHarness, BlockStrategy, EphemeralHarnessType,
OP_POOL_DB_KEY,
},
BeaconChain, NotifyExecutionLayer, StateSkipConfig, WhenSlotSkipped,
BeaconChain, ChainConfig, NotifyExecutionLayer, StateSkipConfig, WhenSlotSkipped,
};
use lazy_static::lazy_static;
use operation_pool::PersistedOperationPool;
@@ -28,6 +28,10 @@ lazy_static! {
fn get_harness(validator_count: usize) -> BeaconChainHarness<EphemeralHarnessType<MinimalEthSpec>> {
let harness = BeaconChainHarness::builder(MinimalEthSpec)
.default_spec()
.chain_config(ChainConfig {
reconstruct_historic_states: true,
..ChainConfig::default()
})
.keypairs(KEYPAIRS[0..validator_count].to_vec())
.fresh_ephemeral_store()
.mock_execution_layer()