mirror of
https://github.com/sigp/lighthouse.git
synced 2026-04-29 10:43:42 +00:00
Altair consensus changes and refactors (#2279)
## Proposed Changes Implement the consensus changes necessary for the upcoming Altair hard fork. ## Additional Info This is quite a heavy refactor, with pivotal types like the `BeaconState` and `BeaconBlock` changing from structs to enums. This ripples through the whole codebase with field accesses changing to methods, e.g. `state.slot` => `state.slot()`. Co-authored-by: realbigsean <seananderson33@gmail.com>
This commit is contained in:
@@ -71,8 +71,8 @@ use std::io::Write;
|
||||
use store::{Error as DBError, HotColdDB, HotStateSummary, KeyValueStore, StoreOp};
|
||||
use tree_hash::TreeHash;
|
||||
use types::{
|
||||
BeaconBlock, BeaconState, BeaconStateError, ChainSpec, CloneConfig, Epoch, EthSpec, Hash256,
|
||||
PublicKey, RelativeEpoch, SignedBeaconBlock, SignedBeaconBlockHeader, Slot,
|
||||
BeaconBlockRef, BeaconState, BeaconStateError, ChainSpec, CloneConfig, Epoch, EthSpec, Hash256,
|
||||
InconsistentFork, PublicKey, RelativeEpoch, SignedBeaconBlock, SignedBeaconBlockHeader, Slot,
|
||||
};
|
||||
|
||||
/// Maximum block slot number. Block with slots bigger than this constant will NOT be processed.
|
||||
@@ -219,6 +219,12 @@ pub enum BlockError<T: EthSpec> {
|
||||
///
|
||||
/// The block is invalid and the peer is faulty.
|
||||
WeakSubjectivityConflict,
|
||||
/// The block has the wrong structure for the fork at `block.slot`.
|
||||
///
|
||||
/// ## Peer scoring
|
||||
///
|
||||
/// The block is invalid and the peer is faulty.
|
||||
InconsistentFork(InconsistentFork),
|
||||
}
|
||||
|
||||
impl<T: EthSpec> std::fmt::Display for BlockError<T> {
|
||||
@@ -477,6 +483,11 @@ impl<T: BeaconChainTypes> GossipVerifiedBlock<T> {
|
||||
block: SignedBeaconBlock<T::EthSpec>,
|
||||
chain: &BeaconChain<T>,
|
||||
) -> Result<Self, BlockError<T::EthSpec>> {
|
||||
// Ensure the block is the correct structure for the fork at `block.slot()`.
|
||||
block
|
||||
.fork_name(&chain.spec)
|
||||
.map_err(BlockError::InconsistentFork)?;
|
||||
|
||||
// Do not gossip or process blocks from future slots.
|
||||
let present_slot_with_tolerance = chain
|
||||
.slot_clock
|
||||
@@ -492,7 +503,7 @@ impl<T: BeaconChainTypes> GossipVerifiedBlock<T> {
|
||||
let block_root = get_block_root(&block);
|
||||
|
||||
// Do not gossip a block from a finalized slot.
|
||||
check_block_against_finalized_slot(&block.message, chain)?;
|
||||
check_block_against_finalized_slot(block.message(), chain)?;
|
||||
|
||||
// Check if the block is already known. We know it is post-finalization, so it is
|
||||
// sufficient to check the fork choice.
|
||||
@@ -509,12 +520,12 @@ impl<T: BeaconChainTypes> GossipVerifiedBlock<T> {
|
||||
if chain
|
||||
.observed_block_producers
|
||||
.read()
|
||||
.proposer_has_been_observed(&block.message)
|
||||
.proposer_has_been_observed(block.message())
|
||||
.map_err(|e| BlockError::BeaconChainError(e.into()))?
|
||||
{
|
||||
return Err(BlockError::RepeatProposal {
|
||||
proposer: block.message.proposer_index,
|
||||
slot: block.message.slot,
|
||||
proposer: block.message().proposer_index(),
|
||||
slot: block.slot(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -563,7 +574,7 @@ impl<T: BeaconChainTypes> GossipVerifiedBlock<T> {
|
||||
};
|
||||
|
||||
// Reject any block that exceeds our limit on skipped slots.
|
||||
check_block_skip_slots(chain, parent_block.slot, &block.message)?;
|
||||
check_block_skip_slots(chain, parent_block.slot, block.message())?;
|
||||
|
||||
// We assign to a variable instead of using `if let Some` directly to ensure we drop the
|
||||
// write lock before trying to acquire it again in the `else` clause.
|
||||
@@ -607,17 +618,17 @@ impl<T: BeaconChainTypes> GossipVerifiedBlock<T> {
|
||||
block_epoch,
|
||||
proposer_shuffling_decision_block,
|
||||
proposers,
|
||||
state.fork,
|
||||
state.fork(),
|
||||
)?;
|
||||
|
||||
(proposer_index, state.fork, Some(parent), block)
|
||||
(proposer_index, state.fork(), Some(parent), block)
|
||||
};
|
||||
|
||||
let signature_is_valid = {
|
||||
let pubkey_cache = get_validator_pubkey_cache(chain)?;
|
||||
let pubkey = pubkey_cache
|
||||
.get(block.message.proposer_index as usize)
|
||||
.ok_or(BlockError::UnknownValidator(block.message.proposer_index))?;
|
||||
.get(block.message().proposer_index() as usize)
|
||||
.ok_or_else(|| BlockError::UnknownValidator(block.message().proposer_index()))?;
|
||||
block.verify_signature(
|
||||
Some(block_root),
|
||||
pubkey,
|
||||
@@ -639,18 +650,18 @@ impl<T: BeaconChainTypes> GossipVerifiedBlock<T> {
|
||||
if chain
|
||||
.observed_block_producers
|
||||
.write()
|
||||
.observe_proposer(&block.message)
|
||||
.observe_proposer(block.message())
|
||||
.map_err(|e| BlockError::BeaconChainError(e.into()))?
|
||||
{
|
||||
return Err(BlockError::RepeatProposal {
|
||||
proposer: block.message.proposer_index,
|
||||
slot: block.message.slot,
|
||||
proposer: block.message().proposer_index(),
|
||||
slot: block.slot(),
|
||||
});
|
||||
}
|
||||
|
||||
if block.message.proposer_index != expected_proposer as u64 {
|
||||
if block.message().proposer_index() != expected_proposer as u64 {
|
||||
return Err(BlockError::IncorrectBlockProposer {
|
||||
block: block.message.proposer_index,
|
||||
block: block.message().proposer_index(),
|
||||
local_shuffling: expected_proposer as u64,
|
||||
});
|
||||
}
|
||||
@@ -693,10 +704,15 @@ impl<T: BeaconChainTypes> SignatureVerifiedBlock<T> {
|
||||
block_root: Hash256,
|
||||
chain: &BeaconChain<T>,
|
||||
) -> Result<Self, BlockError<T::EthSpec>> {
|
||||
// Ensure the block is the correct structure for the fork at `block.slot()`.
|
||||
block
|
||||
.fork_name(&chain.spec)
|
||||
.map_err(BlockError::InconsistentFork)?;
|
||||
|
||||
let (mut parent, block) = load_parent(block, chain)?;
|
||||
|
||||
// Reject any block that exceeds our limit on skipped slots.
|
||||
check_block_skip_slots(chain, parent.beacon_block.slot(), &block.message)?;
|
||||
check_block_skip_slots(chain, parent.beacon_block.slot(), block.message())?;
|
||||
|
||||
let state = cheap_state_advance_to_obtain_committees(
|
||||
&mut parent.pre_state,
|
||||
@@ -860,7 +876,7 @@ impl<'a, T: BeaconChainTypes> FullyVerifiedBlock<'a, T> {
|
||||
}
|
||||
|
||||
// Reject any block that exceeds our limit on skipped slots.
|
||||
check_block_skip_slots(chain, parent.beacon_block.slot(), &block.message)?;
|
||||
check_block_skip_slots(chain, parent.beacon_block.slot(), block.message())?;
|
||||
|
||||
/*
|
||||
* Perform cursory checks to see if the block is even worth processing.
|
||||
@@ -896,20 +912,20 @@ impl<'a, T: BeaconChainTypes> FullyVerifiedBlock<'a, 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() > parent_slot + 1 {
|
||||
return Err(BeaconChainError::BadPreState {
|
||||
parent_root: parent.beacon_block_root,
|
||||
parent_slot,
|
||||
block_root,
|
||||
block_slot: block.slot(),
|
||||
state_slot: state.slot,
|
||||
state_slot: state.slot(),
|
||||
}
|
||||
.into());
|
||||
}
|
||||
|
||||
let distance = block.slot().as_u64().saturating_sub(state.slot.as_u64());
|
||||
let distance = block.slot().as_u64().saturating_sub(state.slot().as_u64());
|
||||
for _ in 0..distance {
|
||||
let state_root = if parent.beacon_block.slot() == state.slot {
|
||||
let state_root = if parent.beacon_block.slot() == state.slot() {
|
||||
// If it happens that `pre_state` has *not* already been advanced forward a single
|
||||
// slot, then there is no need to compute the state root for this
|
||||
// `per_slot_processing` call since that state root is already stored in the parent
|
||||
@@ -935,7 +951,7 @@ impl<'a, T: BeaconChainTypes> FullyVerifiedBlock<'a, T> {
|
||||
vec![]
|
||||
} else {
|
||||
vec![
|
||||
if state.slot % T::EthSpec::slots_per_epoch() == 0 {
|
||||
if state.slot() % T::EthSpec::slots_per_epoch() == 0 {
|
||||
StoreOp::PutState(state_root, &state)
|
||||
} else {
|
||||
StoreOp::PutStateSummary(
|
||||
@@ -1070,14 +1086,14 @@ impl<'a, T: BeaconChainTypes> FullyVerifiedBlock<'a, T> {
|
||||
fn check_block_skip_slots<T: BeaconChainTypes>(
|
||||
chain: &BeaconChain<T>,
|
||||
parent_slot: Slot,
|
||||
block: &BeaconBlock<T::EthSpec>,
|
||||
block: BeaconBlockRef<'_, T::EthSpec>,
|
||||
) -> Result<(), BlockError<T::EthSpec>> {
|
||||
// Reject any block that exceeds our limit on skipped slots.
|
||||
if let Some(max_skip_slots) = chain.config.import_max_skip_slots {
|
||||
if block.slot > parent_slot + max_skip_slots {
|
||||
if block.slot() > parent_slot + max_skip_slots {
|
||||
return Err(BlockError::TooManySkippedSlots {
|
||||
parent_slot,
|
||||
block_slot: block.slot,
|
||||
block_slot: block.slot(),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1090,7 +1106,7 @@ fn check_block_skip_slots<T: BeaconChainTypes>(
|
||||
/// Returns an error if the block is earlier or equal to the finalized slot, or there was an error
|
||||
/// verifying that condition.
|
||||
fn check_block_against_finalized_slot<T: BeaconChainTypes>(
|
||||
block: &BeaconBlock<T::EthSpec>,
|
||||
block: BeaconBlockRef<'_, T::EthSpec>,
|
||||
chain: &BeaconChain<T>,
|
||||
) -> Result<(), BlockError<T::EthSpec>> {
|
||||
let finalized_slot = chain
|
||||
@@ -1099,9 +1115,9 @@ fn check_block_against_finalized_slot<T: BeaconChainTypes>(
|
||||
.epoch
|
||||
.start_slot(T::EthSpec::slots_per_epoch());
|
||||
|
||||
if block.slot <= finalized_slot {
|
||||
if block.slot() <= finalized_slot {
|
||||
Err(BlockError::WouldRevertFinalizedSlot {
|
||||
block_slot: block.slot,
|
||||
block_slot: block.slot(),
|
||||
finalized_slot,
|
||||
})
|
||||
} else {
|
||||
@@ -1127,7 +1143,7 @@ pub fn check_block_is_finalized_descendant<T: BeaconChainTypes, F: ForkChoiceSto
|
||||
// 2. The parent is unknown to us, we probably want to download it since it might actually
|
||||
// descend from the finalized root.
|
||||
if store
|
||||
.item_exists::<SignedBeaconBlock<T::EthSpec>>(&block.parent_root())
|
||||
.block_exists(&block.parent_root())
|
||||
.map_err(|e| BlockError::BeaconChainError(e.into()))?
|
||||
{
|
||||
Err(BlockError::NotFinalizedDescendant {
|
||||
@@ -1151,24 +1167,24 @@ pub fn check_block_relevancy<T: BeaconChainTypes>(
|
||||
block_root: Option<Hash256>,
|
||||
chain: &BeaconChain<T>,
|
||||
) -> Result<Hash256, BlockError<T::EthSpec>> {
|
||||
let block = &signed_block.message;
|
||||
let block = signed_block.message();
|
||||
|
||||
// Do not process blocks from the future.
|
||||
if block.slot > chain.slot()? {
|
||||
if block.slot() > chain.slot()? {
|
||||
return Err(BlockError::FutureSlot {
|
||||
present_slot: chain.slot()?,
|
||||
block_slot: block.slot,
|
||||
block_slot: block.slot(),
|
||||
});
|
||||
}
|
||||
|
||||
// Do not re-process the genesis block.
|
||||
if block.slot == 0 {
|
||||
if block.slot() == 0 {
|
||||
return Err(BlockError::GenesisBlock);
|
||||
}
|
||||
|
||||
// This is an artificial (non-spec) restriction that provides some protection from overflow
|
||||
// abuses.
|
||||
if block.slot >= MAXIMUM_BLOCK_SLOT_NUMBER {
|
||||
if block.slot() >= MAXIMUM_BLOCK_SLOT_NUMBER {
|
||||
return Err(BlockError::BlockSlotLimitReached);
|
||||
}
|
||||
|
||||
@@ -1209,7 +1225,7 @@ fn verify_parent_block_is_known<T: BeaconChainTypes>(
|
||||
if let Some(proto_block) = chain
|
||||
.fork_choice
|
||||
.read()
|
||||
.get_block(&block.message.parent_root)
|
||||
.get_block(&block.message().parent_root())
|
||||
{
|
||||
Ok((proto_block, block))
|
||||
} else {
|
||||
@@ -1327,10 +1343,10 @@ fn cheap_state_advance_to_obtain_committees<'a, E: EthSpec>(
|
||||
state.build_committee_cache(RelativeEpoch::Current, spec)?;
|
||||
|
||||
Ok(Cow::Borrowed(state))
|
||||
} else if state.slot > block_slot {
|
||||
} else if state.slot() > block_slot {
|
||||
Err(BlockError::BlockIsNotLaterThanParent {
|
||||
block_slot,
|
||||
parent_slot: state.slot,
|
||||
parent_slot: state.slot(),
|
||||
})
|
||||
} else {
|
||||
let mut state = state.clone_with(CloneConfig::committee_caches_only());
|
||||
@@ -1372,7 +1388,7 @@ fn get_signature_verifier<'a, T: BeaconChainTypes>(
|
||||
move |validator_index| {
|
||||
// Disallow access to any validator pubkeys that are not in the current beacon
|
||||
// state.
|
||||
if validator_index < state.validators.len() {
|
||||
if validator_index < state.validators().len() {
|
||||
validator_pubkey_cache
|
||||
.get(validator_index)
|
||||
.map(|pk| Cow::Borrowed(pk))
|
||||
@@ -1398,8 +1414,8 @@ fn verify_header_signature<T: BeaconChainTypes>(
|
||||
let (fork, genesis_validators_root) = chain
|
||||
.with_head(|head| {
|
||||
Ok((
|
||||
head.beacon_state.fork,
|
||||
head.beacon_state.genesis_validators_root,
|
||||
head.beacon_state.fork(),
|
||||
head.beacon_state.genesis_validators_root(),
|
||||
))
|
||||
})
|
||||
.map_err(|e: BlockError<T::EthSpec>| e)?;
|
||||
@@ -1458,7 +1474,7 @@ fn participation_ratio(section: u64, total: u64) -> Option<f64> {
|
||||
fn write_state<T: EthSpec>(prefix: &str, state: &BeaconState<T>, log: &Logger) {
|
||||
if WRITE_BLOCK_PROCESSING_SSZ {
|
||||
let root = state.tree_hash_root();
|
||||
let filename = format!("{}_slot_{}_root_{}.ssz", prefix, state.slot, root);
|
||||
let filename = format!("{}_slot_{}_root_{}.ssz", prefix, state.slot(), root);
|
||||
let mut path = std::env::temp_dir().join("lighthouse");
|
||||
let _ = fs::create_dir_all(path.clone());
|
||||
path = path.join(filename);
|
||||
@@ -1479,7 +1495,7 @@ fn write_state<T: EthSpec>(prefix: &str, state: &BeaconState<T>, log: &Logger) {
|
||||
|
||||
fn write_block<T: EthSpec>(block: &SignedBeaconBlock<T>, root: Hash256, log: &Logger) {
|
||||
if WRITE_BLOCK_PROCESSING_SSZ {
|
||||
let filename = format!("block_slot_{}_root{}.ssz", block.message.slot, root);
|
||||
let filename = format!("block_slot_{}_root{}.ssz", block.slot(), root);
|
||||
let mut path = std::env::temp_dir().join("lighthouse");
|
||||
let _ = fs::create_dir_all(path.clone());
|
||||
path = path.join(filename);
|
||||
|
||||
Reference in New Issue
Block a user