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:
Michael Sproul
2021-08-30 06:41:31 +00:00
parent 6b65b6f3bd
commit 10945e0619
16 changed files with 683 additions and 213 deletions

View File

@@ -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,

View File

@@ -0,0 +1,174 @@
use crate::{BeaconForkChoiceStore, BeaconSnapshot};
use fork_choice::ForkChoice;
use itertools::process_results;
use slog::{info, warn, Logger};
use state_processing::state_advance::complete_state_advance;
use state_processing::{per_block_processing, per_block_processing::BlockSignatureStrategy};
use std::sync::Arc;
use store::{iter::ParentRootBlockIterator, HotColdDB, ItemStore};
use types::{BeaconState, ChainSpec, EthSpec, ForkName, Hash256, SignedBeaconBlock, Slot};
const CORRUPT_DB_MESSAGE: &str = "The database could be corrupt. Check its file permissions or \
consider deleting it by running with the --purge-db flag.";
/// Revert the head to the last block before the most recent hard fork.
///
/// This function is destructive and should only be used if there is no viable alternative. It will
/// cause the reverted blocks and states to be completely forgotten, lying dormant in the database
/// forever.
///
/// Return the `(head_block_root, head_block)` that should be used post-reversion.
pub fn revert_to_fork_boundary<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>>(
current_slot: Slot,
head_block_root: Hash256,
store: Arc<HotColdDB<E, Hot, Cold>>,
spec: &ChainSpec,
log: &Logger,
) -> Result<(Hash256, SignedBeaconBlock<E>), String> {
let current_fork = spec.fork_name_at_slot::<E>(current_slot);
let fork_epoch = spec
.fork_epoch(current_fork)
.ok_or_else(|| format!("Current fork '{}' never activates", current_fork))?;
if current_fork == ForkName::Base {
return Err(format!(
"Cannot revert to before phase0 hard fork. {}",
CORRUPT_DB_MESSAGE
));
}
warn!(
log,
"Reverting invalid head block";
"target_fork" => %current_fork,
"fork_epoch" => fork_epoch,
);
let block_iter = ParentRootBlockIterator::fork_tolerant(&store, head_block_root);
process_results(block_iter, |mut iter| {
iter.find_map(|(block_root, block)| {
if block.slot() < fork_epoch.start_slot(E::slots_per_epoch()) {
Some((block_root, block))
} else {
info!(
log,
"Reverting block";
"block_root" => ?block_root,
"slot" => block.slot(),
);
None
}
})
})
.map_err(|e| {
format!(
"Error fetching blocks to revert: {:?}. {}",
e, CORRUPT_DB_MESSAGE
)
})?
.ok_or_else(|| format!("No pre-fork blocks found. {}", CORRUPT_DB_MESSAGE))
}
/// Reset fork choice to the finalized checkpoint of the supplied head state.
///
/// The supplied `head_block_root` should correspond to the most recently applied block on
/// `head_state`.
///
/// This function avoids quirks of fork choice initialization by replaying all of the blocks from
/// the checkpoint to the head.
///
/// See this issue for details: https://github.com/ethereum/consensus-specs/issues/2566
///
/// It will fail if the finalized state or any of the blocks to replay are unavailable.
///
/// WARNING: this function is destructive and causes fork choice to permanently forget all
/// chains other than the chain leading to `head_block_root`. It should only be used in extreme
/// circumstances when there is no better alternative.
pub fn reset_fork_choice_to_finalization<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>>(
head_block_root: Hash256,
head_state: &BeaconState<E>,
store: Arc<HotColdDB<E, Hot, Cold>>,
spec: &ChainSpec,
) -> Result<ForkChoice<BeaconForkChoiceStore<E, Hot, Cold>, E>, String> {
// Fetch finalized block.
let finalized_checkpoint = head_state.finalized_checkpoint();
let finalized_block_root = finalized_checkpoint.root;
let finalized_block = store
.get_block(&finalized_block_root)
.map_err(|e| format!("Error loading finalized block: {:?}", e))?
.ok_or_else(|| {
format!(
"Finalized block missing for revert: {:?}",
finalized_block_root
)
})?;
// Advance finalized state to finalized epoch (to handle skipped slots).
let finalized_state_root = finalized_block.state_root();
let mut finalized_state = store
.get_state(&finalized_state_root, Some(finalized_block.slot()))
.map_err(|e| format!("Error loading finalized state: {:?}", e))?
.ok_or_else(|| {
format!(
"Finalized block state missing from database: {:?}",
finalized_state_root
)
})?;
let finalized_slot = finalized_checkpoint.epoch.start_slot(E::slots_per_epoch());
complete_state_advance(
&mut finalized_state,
Some(finalized_state_root),
finalized_slot,
spec,
)
.map_err(|e| {
format!(
"Error advancing finalized state to finalized epoch: {:?}",
e
)
})?;
let finalized_snapshot = BeaconSnapshot {
beacon_block_root: finalized_block_root,
beacon_block: finalized_block,
beacon_state: finalized_state,
};
let fc_store = BeaconForkChoiceStore::get_forkchoice_store(store.clone(), &finalized_snapshot);
let mut fork_choice = ForkChoice::from_anchor(
fc_store,
finalized_block_root,
&finalized_snapshot.beacon_block,
&finalized_snapshot.beacon_state,
)
.map_err(|e| format!("Unable to reset fork choice for revert: {:?}", e))?;
// Replay blocks from finalized checkpoint back to head.
// We do not replay attestations presently, relying on the absence of other blocks
// to guarantee `head_block_root` as the head.
let blocks = store
.load_blocks_to_replay(finalized_slot + 1, head_state.slot(), head_block_root)
.map_err(|e| format!("Error loading blocks to replay for fork choice: {:?}", e))?;
let mut state = finalized_snapshot.beacon_state;
for block in blocks {
complete_state_advance(&mut state, None, block.slot(), spec)
.map_err(|e| format!("State advance failed: {:?}", e))?;
per_block_processing(
&mut state,
&block,
None,
BlockSignatureStrategy::NoVerification,
spec,
)
.map_err(|e| format!("Error replaying block: {:?}", e))?;
let (block, _) = block.deconstruct();
fork_choice
.on_block(block.slot(), &block, block.canonical_root(), &state)
.map_err(|e| format!("Error applying replayed block to fork choice: {:?}", e))?;
}
Ok(fork_choice)
}

View File

@@ -11,6 +11,7 @@ pub mod chain_config;
mod errors;
pub mod eth1_chain;
pub mod events;
pub mod fork_revert;
mod head_tracker;
mod metrics;
pub mod migrate;

View File

@@ -334,16 +334,33 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> BackgroundMigrator<E, Ho
);
for (head_hash, head_slot) in heads {
// Load head block. If it fails with a decode error, it's likely a reverted block,
// so delete it from the head tracker but leave it and its states in the database
// This is suboptimal as it wastes disk space, but it's difficult to fix. A re-sync
// can be used to reclaim the space.
let head_state_root = match store.get_block(&head_hash) {
Ok(Some(block)) => block.state_root(),
Ok(None) => {
return Err(BeaconStateError::MissingBeaconBlock(head_hash.into()).into())
}
Err(Error::SszDecodeError(e)) => {
warn!(
log,
"Forgetting invalid head block";
"block_root" => ?head_hash,
"error" => ?e,
);
abandoned_heads.insert(head_hash);
continue;
}
Err(e) => return Err(e.into()),
};
let mut potentially_abandoned_head = Some(head_hash);
let mut potentially_abandoned_blocks = vec![];
let head_state_hash = store
.get_block(&head_hash)?
.ok_or_else(|| BeaconStateError::MissingBeaconBlock(head_hash.into()))?
.state_root();
// Iterate backwards from this head, staging blocks and states for deletion.
let iter = std::iter::once(Ok((head_hash, head_state_hash, head_slot)))
let iter = std::iter::once(Ok((head_hash, head_state_root, head_slot)))
.chain(RootsIterator::from_block(store.clone(), head_hash)?);
for maybe_tuple in iter {

View File

@@ -29,7 +29,6 @@ use std::sync::Arc;
use std::time::Duration;
use store::{config::StoreConfig, BlockReplay, HotColdDB, ItemStore, LevelDB, MemoryStore};
use task_executor::ShutdownReason;
use tempfile::{tempdir, TempDir};
use tree_hash::TreeHash;
use types::sync_selection_proof::SyncSelectionProof;
pub use types::test_utils::generate_deterministic_keypairs;
@@ -50,6 +49,12 @@ pub const HARNESS_SLOT_TIME: Duration = Duration::from_secs(1);
// Environment variable to read if `fork_from_env` feature is enabled.
const FORK_NAME_ENV_VAR: &str = "FORK_NAME";
// Default target aggregators to set during testing, this ensures an aggregator at each slot.
//
// You should mutate the `ChainSpec` prior to initialising the harness if you would like to use
// a different value.
pub const DEFAULT_TARGET_AGGREGATORS: u64 = u64::max_value();
pub type BaseHarnessType<TEthSpec, THotStore, TColdStore> =
Witness<TestingSlotClock, CachingEth1Backend<TEthSpec>, TEthSpec, THotStore, TColdStore>;
@@ -126,7 +131,7 @@ pub fn test_logger() -> Logger {
/// If the `fork_from_env` feature is enabled, read the fork to use from the FORK_NAME environment
/// variable. Otherwise use the default spec.
pub fn test_spec<E: EthSpec>() -> ChainSpec {
if cfg!(feature = "fork_from_env") {
let mut spec = if cfg!(feature = "fork_from_env") {
let fork_name = std::env::var(FORK_NAME_ENV_VAR).unwrap_or_else(|e| {
panic!(
"{} env var must be defined when using fork_from_env: {:?}",
@@ -141,7 +146,11 @@ pub fn test_spec<E: EthSpec>() -> ChainSpec {
fork.make_genesis_spec(E::default_spec())
} else {
E::default_spec()
}
};
// Set target aggregators to a high value by default.
spec.target_aggregators_per_committee = DEFAULT_TARGET_AGGREGATORS;
spec
}
/// A testing harness which can instantiate a `BeaconChain` and populate it with blocks and
@@ -153,7 +162,6 @@ pub struct BeaconChainHarness<T: BeaconChainTypes> {
pub chain: Arc<BeaconChain<T>>,
pub spec: ChainSpec,
pub data_dir: TempDir,
pub shutdown_receiver: Receiver<ShutdownReason>,
pub rng: Mutex<StdRng>,
@@ -188,200 +196,123 @@ impl<E: EthSpec> BeaconChainHarness<EphemeralHarnessType<E>> {
spec: Option<ChainSpec>,
validator_keypairs: Vec<Keypair>,
config: StoreConfig,
) -> Self {
// Setting the target aggregators to really high means that _all_ validators in the
// committee are required to produce an aggregate. This is overkill, however with small
// validator counts it's the only way to be certain there is _at least one_ aggregator per
// committee.
Self::new_with_target_aggregators(
eth_spec_instance,
spec,
validator_keypairs,
1 << 32,
config,
)
}
/// Instantiate a new harness with a custom `target_aggregators_per_committee` spec value
pub fn new_with_target_aggregators(
eth_spec_instance: E,
spec: Option<ChainSpec>,
validator_keypairs: Vec<Keypair>,
target_aggregators_per_committee: u64,
store_config: StoreConfig,
) -> Self {
Self::new_with_chain_config(
eth_spec_instance,
spec,
validator_keypairs,
target_aggregators_per_committee,
store_config,
config,
ChainConfig::default(),
)
}
/// Instantiate a new harness with `validator_count` initial validators, a custom
/// `target_aggregators_per_committee` spec value, and a `ChainConfig`
pub fn new_with_chain_config(
eth_spec_instance: E,
spec: Option<ChainSpec>,
validator_keypairs: Vec<Keypair>,
target_aggregators_per_committee: u64,
store_config: StoreConfig,
chain_config: ChainConfig,
) -> Self {
Self::new_with_mutator(
Self::ephemeral_with_mutator(
eth_spec_instance,
spec,
validator_keypairs,
target_aggregators_per_committee,
store_config,
chain_config,
|x| x,
)
}
/// Apply a function to beacon chain builder before building.
pub fn new_with_mutator(
pub fn ephemeral_with_mutator(
eth_spec_instance: E,
spec: Option<ChainSpec>,
validator_keypairs: Vec<Keypair>,
target_aggregators_per_committee: u64,
store_config: StoreConfig,
chain_config: ChainConfig,
mutator: impl FnOnce(
BeaconChainBuilder<EphemeralHarnessType<E>>,
) -> BeaconChainBuilder<EphemeralHarnessType<E>>,
) -> Self {
let data_dir = tempdir().expect("should create temporary data_dir");
let mut spec = spec.unwrap_or_else(test_spec::<E>);
spec.target_aggregators_per_committee = target_aggregators_per_committee;
let (shutdown_tx, shutdown_receiver) = futures::channel::mpsc::channel(1);
let spec = spec.unwrap_or_else(test_spec::<E>);
let log = test_logger();
let store = HotColdDB::open_ephemeral(store_config, spec.clone(), log.clone()).unwrap();
let builder = BeaconChainBuilder::new(eth_spec_instance)
.logger(log.clone())
.custom_spec(spec.clone())
.store(Arc::new(store))
.store_migrator_config(MigratorConfig::default().blocking())
.genesis_state(
interop_genesis_state::<E>(&validator_keypairs, HARNESS_GENESIS_TIME, &spec)
.expect("should generate interop state"),
)
.expect("should build state using recent genesis")
.dummy_eth1_backend()
.expect("should build dummy backend")
.testing_slot_clock(HARNESS_SLOT_TIME)
.expect("should configure testing slot clock")
.shutdown_sender(shutdown_tx)
.chain_config(chain_config)
.event_handler(Some(ServerSentEventHandler::new_with_capacity(
log.clone(),
1,
)))
.monitor_validators(true, vec![], log);
let chain = mutator(builder).build().expect("should build");
Self {
spec: chain.spec.clone(),
chain: Arc::new(chain),
validator_keypairs,
data_dir,
shutdown_receiver,
rng: make_rng(),
}
let store = Arc::new(HotColdDB::open_ephemeral(store_config, spec.clone(), log).unwrap());
Self::new_with_mutator(
eth_spec_instance,
spec,
store,
validator_keypairs.clone(),
chain_config,
|mut builder| {
builder = mutator(builder);
let genesis_state = interop_genesis_state::<E>(
&validator_keypairs,
HARNESS_GENESIS_TIME,
builder.get_spec(),
)
.expect("should generate interop state");
builder
.genesis_state(genesis_state)
.expect("should build state using recent genesis")
},
)
}
}
impl<E: EthSpec> BeaconChainHarness<DiskHarnessType<E>> {
/// Instantiate a new harness with `validator_count` initial validators.
/// Disk store, start from genesis.
pub fn new_with_disk_store(
eth_spec_instance: E,
spec: Option<ChainSpec>,
store: Arc<HotColdDB<E, LevelDB<E>, LevelDB<E>>>,
validator_keypairs: Vec<Keypair>,
) -> Self {
let data_dir = tempdir().expect("should create temporary data_dir");
let spec = spec.unwrap_or_else(test_spec::<E>);
let log = test_logger();
let (shutdown_tx, shutdown_receiver) = futures::channel::mpsc::channel(1);
let chain_config = ChainConfig::default();
let chain = BeaconChainBuilder::new(eth_spec_instance)
.logger(log.clone())
.custom_spec(spec.clone())
.import_max_skip_slots(None)
.store(store)
.store_migrator_config(MigratorConfig::default().blocking())
.genesis_state(
interop_genesis_state::<E>(&validator_keypairs, HARNESS_GENESIS_TIME, &spec)
.expect("should generate interop state"),
)
.expect("should build state using recent genesis")
.dummy_eth1_backend()
.expect("should build dummy backend")
.testing_slot_clock(HARNESS_SLOT_TIME)
.expect("should configure testing slot clock")
.shutdown_sender(shutdown_tx)
.monitor_validators(true, vec![], log)
.build()
.expect("should build");
Self {
spec: chain.spec.clone(),
chain: Arc::new(chain),
validator_keypairs,
data_dir,
shutdown_receiver,
rng: make_rng(),
}
Self::new_with_mutator(
eth_spec_instance,
spec,
store,
validator_keypairs.clone(),
chain_config,
|builder| {
let genesis_state = interop_genesis_state::<E>(
&validator_keypairs,
HARNESS_GENESIS_TIME,
builder.get_spec(),
)
.expect("should generate interop state");
builder
.genesis_state(genesis_state)
.expect("should build state using recent genesis")
},
)
}
}
impl<E: EthSpec> BeaconChainHarness<DiskHarnessType<E>> {
/// Instantiate a new harness with `validator_count` initial validators.
/// Disk store, resume.
pub fn resume_from_disk_store(
eth_spec_instance: E,
spec: Option<ChainSpec>,
store: Arc<HotColdDB<E, LevelDB<E>, LevelDB<E>>>,
validator_keypairs: Vec<Keypair>,
data_dir: TempDir,
) -> Self {
let spec = spec.unwrap_or_else(test_spec::<E>);
let log = test_logger();
let (shutdown_tx, shutdown_receiver) = futures::channel::mpsc::channel(1);
let chain_config = ChainConfig::default();
let chain = BeaconChainBuilder::new(eth_spec_instance)
.logger(log.clone())
.custom_spec(spec)
.import_max_skip_slots(None)
.store(store)
.store_migrator_config(MigratorConfig::default().blocking())
.resume_from_db()
.expect("should resume beacon chain from db")
.dummy_eth1_backend()
.expect("should build dummy backend")
.testing_slot_clock(Duration::from_secs(1))
.expect("should configure testing slot clock")
.shutdown_sender(shutdown_tx)
.monitor_validators(true, vec![], log)
.build()
.expect("should build");
Self {
spec: chain.spec.clone(),
chain: Arc::new(chain),
Self::new_with_mutator(
eth_spec_instance,
spec,
store,
validator_keypairs,
data_dir,
shutdown_receiver,
rng: make_rng(),
}
chain_config,
|builder| {
builder
.resume_from_db()
.expect("should resume from database")
},
)
}
}
@@ -391,6 +322,62 @@ where
Hot: ItemStore<E>,
Cold: ItemStore<E>,
{
/// Generic initializer.
///
/// This initializer should be able to handle almost any configuration via arguments and the
/// provided `mutator` function. Please do not copy and paste this function.
pub fn new_with_mutator(
eth_spec_instance: E,
spec: ChainSpec,
store: Arc<HotColdDB<E, Hot, Cold>>,
validator_keypairs: Vec<Keypair>,
chain_config: ChainConfig,
mutator: impl FnOnce(
BeaconChainBuilder<BaseHarnessType<E, Hot, Cold>>,
) -> BeaconChainBuilder<BaseHarnessType<E, Hot, Cold>>,
) -> Self {
let (shutdown_tx, shutdown_receiver) = futures::channel::mpsc::channel(1);
let log = test_logger();
let mut builder = BeaconChainBuilder::new(eth_spec_instance)
.logger(log.clone())
.custom_spec(spec)
.store(store)
.store_migrator_config(MigratorConfig::default().blocking())
.dummy_eth1_backend()
.expect("should build dummy backend")
.shutdown_sender(shutdown_tx)
.chain_config(chain_config)
.event_handler(Some(ServerSentEventHandler::new_with_capacity(
log.clone(),
1,
)))
.monitor_validators(true, vec![], log);
// Caller must initialize genesis state.
builder = mutator(builder);
// Initialize the slot clock only if it hasn't already been initialized.
builder = if builder.get_slot_clock().is_none() {
builder
.testing_slot_clock(HARNESS_SLOT_TIME)
.expect("should configure testing slot clock")
} else {
builder
};
let chain = builder.build().expect("should build");
Self {
spec: chain.spec.clone(),
chain: Arc::new(chain),
validator_keypairs,
shutdown_receiver,
rng: make_rng(),
}
}
pub fn logger(&self) -> &slog::Logger {
&self.chain.log
}
@@ -574,6 +561,9 @@ where
attestation_slot: Slot,
) -> Vec<Vec<(Attestation<E>, SubnetId)>> {
let committee_count = state.get_committee_count_at_slot(state.slot()).unwrap();
let fork = self
.spec
.fork_at_epoch(attestation_slot.epoch(E::slots_per_epoch()));
state
.get_beacon_committees_at_slot(attestation_slot)
@@ -604,7 +594,7 @@ where
let domain = self.spec.get_domain(
attestation.data.target.epoch,
Domain::BeaconAttester,
&state.fork(),
&fork,
state.genesis_validators_root(),
);
@@ -651,6 +641,9 @@ where
.expect("should be called on altair beacon state")
.clone(),
};
let fork = self
.spec
.fork_at_epoch(message_slot.epoch(E::slots_per_epoch()));
sync_committee
.pubkeys
@@ -672,7 +665,7 @@ where
head_block_root,
validator_index as u64,
&self.validator_keypairs[validator_index].sk,
&state.fork(),
&fork,
state.genesis_validators_root(),
&self.spec,
);
@@ -727,6 +720,7 @@ where
block_hash,
slot,
);
let fork = self.spec.fork_at_epoch(slot.epoch(E::slots_per_epoch()));
let aggregated_attestations: Vec<Option<SignedAggregateAndProof<E>>> =
unaggregated_attestations
@@ -749,9 +743,9 @@ where
}
let selection_proof = SelectionProof::new::<E>(
state.slot(),
slot,
&self.validator_keypairs[*validator_index].sk,
&state.fork(),
&fork,
state.genesis_validators_root(),
&self.spec,
);
@@ -782,7 +776,7 @@ where
aggregate,
None,
&self.validator_keypairs[aggregator_index].sk,
&state.fork(),
&fork,
state.genesis_validators_root(),
&self.spec,
);