//! The incremental processing steps (e.g., signatures verified but not the state transition) is //! represented as a sequence of wrapper-types around the block. There is a linear progression of //! types, starting at a `SignedExecutionPayloadEnvelope` and finishing with an `AvailableExecutedEnvelope` (see //! diagram below). //! //! // TODO(gloas) we might want to update this diagram to include `AvailabelExecutedEnvelope` //! ```ignore //! START //! | //! ▼ //! SignedExecutionPayloadEnvelope //! | //! |--------------- //! | | //! | ▼ //! | GossipVerifiedEnvelope //! | | //! |--------------- //! | //! ▼ //! ExecutionPendingEnvelope //! | //! await //! | //! ▼ //! END //! //! ``` use std::sync::Arc; use store::Error as DBError; use state_processing::{BlockProcessingError, envelope_processing::EnvelopeProcessingError}; use tracing::instrument; use types::{ BeaconState, BeaconStateError, ChainSpec, DataColumnSidecarList, EthSpec, ExecutionBlockHash, ExecutionPayloadEnvelope, Hash256, SignedBeaconBlock, SignedExecutionPayloadEnvelope, Slot, }; use crate::{ BeaconChainError, BeaconChainTypes, BeaconStore, BlockError, ExecutionPayloadError, PayloadVerificationOutcome, canonical_head::CanonicalHead, }; pub mod execution_pending_envelope; pub mod gossip_verified_envelope; pub mod import; mod payload_notifier; pub use execution_pending_envelope::{ExecutionPendingEnvelope, IntoExecutionPendingEnvelope}; #[derive(PartialEq)] pub struct EnvelopeImportData { pub block_root: Hash256, pub block: Arc>, pub post_state: Box>, } #[derive(Debug)] #[allow(dead_code)] pub struct AvailableEnvelope { execution_block_hash: ExecutionBlockHash, envelope: Arc>, columns: DataColumnSidecarList, /// Timestamp at which this block first became available (UNIX timestamp, time since 1970). columns_available_timestamp: Option, pub spec: Arc, } impl AvailableEnvelope { pub fn message(&self) -> &ExecutionPayloadEnvelope { &self.envelope.message } #[allow(clippy::type_complexity)] pub fn deconstruct( self, ) -> ( Arc>, DataColumnSidecarList, ) { let AvailableEnvelope { envelope, columns, .. } = self; (envelope, columns) } } pub enum MaybeAvailableEnvelope { Available(AvailableEnvelope), AvailabilityPending { block_hash: ExecutionBlockHash, envelope: Arc>, }, } /// This snapshot is to be used for verifying a envelope of the block. #[derive(Debug, Clone)] pub struct EnvelopeProcessingSnapshot { /// This state is equivalent to the `self.beacon_block.state_root()` before applying the envelope. pub pre_state: BeaconState, pub state_root: Hash256, pub beacon_block_root: Hash256, } /// A payload envelope that has gone through processing checks and execution by an EL client. /// This envelope hasn't necessarily completed data availability checks. /// /// /// It contains 2 variants: /// 1. `Available`: This enelope has been executed and also contains all data to consider it /// fully available. /// 2. `AvailabilityPending`: This envelope hasn't received all required blobs to consider it /// fully available. pub enum ExecutedEnvelope { Available(AvailableExecutedEnvelope), // TODO(gloas) implement availability pending AvailabilityPending(), } impl ExecutedEnvelope { pub fn new( envelope: MaybeAvailableEnvelope, import_data: EnvelopeImportData, payload_verification_outcome: PayloadVerificationOutcome, ) -> Self { match envelope { MaybeAvailableEnvelope::Available(available_envelope) => { Self::Available(AvailableExecutedEnvelope::new( available_envelope, import_data, payload_verification_outcome, )) } // TODO(gloas) implement availability pending MaybeAvailableEnvelope::AvailabilityPending { block_hash: _, envelope: _, } => Self::AvailabilityPending(), } } } /// A payload envelope that has completed all payload processing checks including verification /// by an EL client **and** has all requisite blob data to be imported into fork choice. pub struct AvailableExecutedEnvelope { pub envelope: AvailableEnvelope, pub import_data: EnvelopeImportData, pub payload_verification_outcome: PayloadVerificationOutcome, } impl AvailableExecutedEnvelope { pub fn new( envelope: AvailableEnvelope, import_data: EnvelopeImportData, payload_verification_outcome: PayloadVerificationOutcome, ) -> Self { Self { envelope, import_data, payload_verification_outcome, } } } #[derive(Debug)] pub enum EnvelopeError { /// The envelope's block root is unknown. BlockRootUnknown { block_root: Hash256, }, /// The signature is invalid. BadSignature, /// The builder index doesn't match the committed bid BuilderIndexMismatch { committed_bid: u64, envelope: u64, }, // The envelope slot doesn't match the block SlotMismatch { block: Slot, envelope: Slot, }, // The validator index is unknown UnknownValidator { builder_index: u64, }, // The block hash doesn't match the committed bid BlockHashMismatch { committed_bid: ExecutionBlockHash, envelope: ExecutionBlockHash, }, // The block's proposer_index does not match the locally computed proposer IncorrectBlockProposer { block: u64, local_shuffling: u64, }, // The slot belongs to a block that is from a slot prior than // the most recently finalized slot PriorToFinalization { payload_slot: Slot, latest_finalized_slot: Slot, }, // Some Beacon Chain Error BeaconChainError(Arc), // Some Beacon State error BeaconStateError(BeaconStateError), // Some BlockProcessingError (for electra operations) BlockProcessingError(BlockProcessingError), // Some EnvelopeProcessingError EnvelopeProcessingError(EnvelopeProcessingError), // Error verifying the execution payload ExecutionPayloadError(ExecutionPayloadError), // An error from block-level checks reused during envelope import BlockError(BlockError), // Internal error InternalError(String), } impl std::fmt::Display for EnvelopeError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{:?}", self) } } impl From for EnvelopeError { fn from(e: BeaconChainError) -> Self { EnvelopeError::BeaconChainError(Arc::new(e)) } } impl From for EnvelopeError { fn from(e: ExecutionPayloadError) -> Self { EnvelopeError::ExecutionPayloadError(e) } } impl From for EnvelopeError { fn from(e: BeaconStateError) -> Self { EnvelopeError::BeaconStateError(e) } } impl From for EnvelopeError { fn from(e: DBError) -> Self { EnvelopeError::BeaconChainError(Arc::new(BeaconChainError::DBError(e))) } } impl From for EnvelopeError { fn from(e: BlockError) -> Self { EnvelopeError::BlockError(e) } } /// Pull errors up from EnvelopeProcessingError to EnvelopeError impl From for EnvelopeError { fn from(e: EnvelopeProcessingError) -> Self { match e { EnvelopeProcessingError::BadSignature => EnvelopeError::BadSignature, EnvelopeProcessingError::BeaconStateError(e) => EnvelopeError::BeaconStateError(e), EnvelopeProcessingError::BlockHashMismatch { committed_bid, envelope, } => EnvelopeError::BlockHashMismatch { committed_bid, envelope, }, EnvelopeProcessingError::BlockProcessingError(e) => { EnvelopeError::BlockProcessingError(e) } e => EnvelopeError::EnvelopeProcessingError(e), } } } #[allow(clippy::type_complexity)] #[instrument(skip_all, level = "debug", fields(beacon_block_root = %beacon_block_root))] /// Load state from store given a known state root and block root. /// Use this when the proto block has already been looked up from fork choice. pub(crate) fn load_snapshot_from_state_root( beacon_block_root: Hash256, block_state_root: Hash256, store: &BeaconStore, ) -> Result, EnvelopeError> { // TODO(EIP-7732): add metrics here // We can use `get_hot_state` here rather than `get_advanced_hot_state` because the envelope // must be from the same slot as its block (so no advance is required). let cache_state = true; let state = store .get_hot_state(&block_state_root, cache_state) .map_err(EnvelopeError::from)? .ok_or_else(|| { BeaconChainError::DBInconsistent(format!( "Missing state for envelope block {block_state_root:?}", )) })?; Ok(EnvelopeProcessingSnapshot { pre_state: state, state_root: block_state_root, beacon_block_root, }) } #[instrument(skip_all, level = "debug", fields(beacon_block_root = %envelope.beacon_block_root()))] pub(crate) fn load_snapshot( envelope: &SignedExecutionPayloadEnvelope, canonical_head: &CanonicalHead, store: &BeaconStore, ) -> Result, EnvelopeError> { // Reject any envelope if its block is not known to fork choice. // // A block that is not in fork choice is either: // // - Not yet imported: we should reject this envelope because we should only import it after // its parent block has been fully imported. // - Pre-finalized: if the parent block is _prior_ to finalization, we should ignore the // envelope because it will revert finalization. Note that the finalized block is stored in // fork choice, so we will not reject any child of the finalized block (this is relevant // during genesis). let fork_choice_read_lock = canonical_head.fork_choice_read_lock(); let beacon_block_root = envelope.beacon_block_root(); let Some(proto_beacon_block) = fork_choice_read_lock.get_block(&beacon_block_root) else { return Err(EnvelopeError::BlockRootUnknown { block_root: beacon_block_root, }); }; drop(fork_choice_read_lock); load_snapshot_from_state_root::(beacon_block_root, proto_beacon_block.state_root, store) }