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:
Michael Sproul
2021-07-09 06:15:32 +00:00
parent 89361573d4
commit b4689e20c6
271 changed files with 9652 additions and 8444 deletions

View File

@@ -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);