Finished Gossip Block Validation Conditions (#2640)

* Gossip Block Validation is Much More Efficient

Co-authored-by: realbigsean <seananderson33@gmail.com>
This commit is contained in:
ethDreamer 2021-09-28 18:36:03 -05:00 committed by Paul Hauner
parent fe945bc84a
commit 1563bce905
No known key found for this signature in database
GPG Key ID: 5E2CFF9B75FA63DF
5 changed files with 80 additions and 42 deletions

View File

@ -53,10 +53,11 @@ use crate::{
use fork_choice::{ForkChoice, ForkChoiceStore};
use parking_lot::RwLockReadGuard;
use proto_array::Block as ProtoBlock;
use safe_arith::ArithError;
use slog::{debug, error, Logger};
use slot_clock::SlotClock;
use ssz::Encode;
use state_processing::per_block_processing::{is_execution_enabled, is_merge_complete};
use state_processing::per_block_processing::is_execution_enabled;
use state_processing::{
block_signature_verifier::{BlockSignatureVerifier, Error as BlockSignatureVerifierError},
per_block_processing, per_slot_processing,
@ -254,18 +255,12 @@ pub enum ExecutionPayloadError {
///
/// The block is invalid and the peer is faulty
RejectedByExecutionEngine,
/// The execution payload is empty when is shouldn't be
///
/// ## Peer scoring
///
/// The block is invalid and the peer is faulty
PayloadEmpty,
/// The execution payload timestamp does not match the slot
///
/// ## Peer scoring
///
/// The block is invalid and the peer is faulty
InvalidPayloadTimestamp,
InvalidPayloadTimestamp { expected: u64, found: u64 },
/// The gas used in the block exceeds the gas limit
///
/// ## Peer scoring
@ -338,6 +333,12 @@ impl<T: EthSpec> From<DBError> for BlockError<T> {
}
}
impl<T: EthSpec> From<ArithError> for BlockError<T> {
fn from(e: ArithError) -> Self {
BlockError::BeaconChainError(BeaconChainError::ArithError(e))
}
}
/// Information about invalid blocks which might still be slashable despite being invalid.
#[allow(clippy::enum_variant_names)]
pub enum BlockSlashInfo<TErr> {
@ -729,17 +730,8 @@ impl<T: BeaconChainTypes> GossipVerifiedBlock<T> {
});
}
// TODO: avoid this by adding field to fork-choice to determine if merge-block has been imported
let (parent, block) = if let Some(snapshot) = parent {
(Some(snapshot), block)
} else {
let (snapshot, block) = load_parent(block, chain)?;
(Some(snapshot), block)
};
let state = &parent.as_ref().unwrap().pre_state;
// validate the block's execution_payload
validate_execution_payload(block.message(), state)?;
validate_execution_payload(&parent_block, block.message(), chain)?;
Ok(Self {
block,
@ -1201,32 +1193,56 @@ impl<'a, T: BeaconChainTypes> FullyVerifiedBlock<'a, T> {
/// Validate the gossip block's execution_payload according to the checks described here:
/// https://github.com/ethereum/consensus-specs/blob/dev/specs/merge/p2p-interface.md#beacon_block
fn validate_execution_payload<E: EthSpec>(
block: BeaconBlockRef<'_, E>,
state: &BeaconState<E>,
) -> Result<(), BlockError<E>> {
if !is_execution_enabled(state, block.body()) {
fn validate_execution_payload<T: BeaconChainTypes>(
parent_block: &ProtoBlock,
block: BeaconBlockRef<'_, T::EthSpec>,
chain: &BeaconChain<T>,
) -> Result<(), BlockError<T::EthSpec>> {
// Only apply this validation if this is a merge beacon block.
if let Some(execution_payload) = block.body().execution_payload() {
// This logic should match `is_execution_enabled`. We use only the execution block hash of
// the parent here in order to avoid loading the parent state during gossip verification.
let is_merge_complete = parent_block.execution_block_hash != Hash256::zero();
let is_merge_block =
!is_merge_complete && *execution_payload != <ExecutionPayload<T::EthSpec>>::default();
if !is_merge_block && !is_merge_complete {
return Ok(());
}
let execution_payload = block
.body()
.execution_payload()
// TODO: this really should never error so maybe
// we should make this simpler..
.ok_or_else(|| {
BlockError::InconsistentFork(InconsistentFork {
fork_at_slot: eth2::types::ForkName::Merge,
object_fork: block.body().fork_name(),
})
})?;
if is_merge_complete(state) && *execution_payload == <ExecutionPayload<E>>::default() {
let expected_timestamp = chain
.slot_clock
.compute_timestamp_at_slot(block.slot())
.ok_or(BlockError::BeaconChainError(
BeaconChainError::UnableToComputeTimeAtSlot,
))?;
// The block's execution payload timestamp is correct with respect to the slot
if execution_payload.timestamp != expected_timestamp {
return Err(BlockError::ExecutionPayloadError(
ExecutionPayloadError::PayloadEmpty,
ExecutionPayloadError::InvalidPayloadTimestamp {
expected: expected_timestamp,
found: execution_payload.timestamp,
},
));
}
// TODO: finish these
// Gas used is less than the gas limit
if execution_payload.gas_used > execution_payload.gas_limit {
return Err(BlockError::ExecutionPayloadError(
ExecutionPayloadError::GasUsedExceedsLimit,
));
}
// The execution payload block hash is not equal to the parent hash
if execution_payload.block_hash == execution_payload.parent_hash {
return Err(BlockError::ExecutionPayloadError(
ExecutionPayloadError::BlockHashEqualsParentHash,
));
}
// The execution payload transaction list data is within expected size limits
if execution_payload.transactions.len() > T::EthSpec::max_transactions_per_payload() {
return Err(BlockError::ExecutionPayloadError(
ExecutionPayloadError::TransactionDataExceedsSizeLimit,
));
}
}
Ok(())
}

View File

@ -40,6 +40,7 @@ macro_rules! easy_from_to {
pub enum BeaconChainError {
InsufficientValidators,
UnableToReadSlot,
UnableToComputeTimeAtSlot,
RevertedFinalizedEpoch {
previous_epoch: Epoch,
new_epoch: Epoch,

View File

@ -65,6 +65,9 @@ pub trait SlotClock: Send + Sync + Sized + Clone {
/// Returns the first slot to be returned at the genesis time.
fn genesis_slot(&self) -> Slot;
/// Returns the `Duration` from `UNIX_EPOCH` to the genesis time.
fn genesis_duration(&self) -> Duration;
/// Returns the slot if the internal clock were advanced by `duration`.
fn now_with_future_tolerance(&self, tolerance: Duration) -> Option<Slot> {
self.slot_of(self.now_duration()?.checked_add(tolerance)?)
@ -99,4 +102,14 @@ pub trait SlotClock: Send + Sync + Sized + Clone {
fn sync_committee_contribution_production_delay(&self) -> Duration {
self.slot_duration() * 2 / 3
}
/// An implementation of the method described in the consensus spec here:
///
/// https://github.com/ethereum/consensus-specs/blob/dev/specs/merge/beacon-chain.md#compute_timestamp_at_slot
fn compute_timestamp_at_slot(&self, slot: Slot) -> Option<u64> {
let slots_since_genesis = slot.as_u64().checked_sub(self.genesis_slot().as_u64())?;
slots_since_genesis
.checked_mul(self.slot_duration().as_secs())
.and_then(|since_genesis| self.genesis_duration().as_secs().checked_add(since_genesis))
}
}

View File

@ -154,6 +154,10 @@ impl SlotClock for ManualSlotClock {
fn genesis_slot(&self) -> Slot {
self.genesis_slot
}
fn genesis_duration(&self) -> Duration {
self.genesis_duration
}
}
#[cfg(test)]

View File

@ -61,6 +61,10 @@ impl SlotClock for SystemTimeSlotClock {
fn genesis_slot(&self) -> Slot {
self.clock.genesis_slot()
}
fn genesis_duration(&self) -> Duration {
*self.clock.genesis_duration()
}
}
#[cfg(test)]