diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index a59508a37a..801ef7696f 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -1147,7 +1147,6 @@ impl BeaconChain { } } - /// Returns the full block at the given root, if it's available in the database. /// /// Should always return a full block for pre-merge and post-gloas blocks. diff --git a/beacon_node/beacon_chain/src/block_verification.rs b/beacon_node/beacon_chain/src/block_verification.rs index 9d1b8e0d3d..c11f26aa00 100644 --- a/beacon_node/beacon_chain/src/block_verification.rs +++ b/beacon_node/beacon_chain/src/block_verification.rs @@ -718,7 +718,8 @@ pub struct SignatureVerifiedBlock { } /// Used to await the result of executing payload with an EE. -pub type PayloadVerificationHandle = JoinHandle>>; +pub type PayloadVerificationHandle = + JoinHandle>>; /// A wrapper around a `SignedBeaconBlock` that indicates that this block is fully verified and /// ready to import into the `BeaconChain`. The validation includes: diff --git a/beacon_node/beacon_chain/src/execution_payload.rs b/beacon_node/beacon_chain/src/execution_payload.rs index 370f044c7a..b8fbbc3c32 100644 --- a/beacon_node/beacon_chain/src/execution_payload.rs +++ b/beacon_node/beacon_chain/src/execution_payload.rs @@ -113,7 +113,7 @@ impl PayloadNotifier { if let Some(precomputed_status) = self.payload_verification_status { Ok(precomputed_status) } else { - notify_new_payload(&self.chain, self.block.message()).await + notify_new_payload(&self.chain, self.block.message().tree_hash_root(), self.block.message().try_into()?).await } } } @@ -127,17 +127,18 @@ impl PayloadNotifier { /// contains a few extra checks by running `partially_verify_execution_payload` first: /// /// https://github.com/ethereum/consensus-specs/blob/v1.1.9/specs/bellatrix/beacon-chain.md#notify_new_payload -async fn notify_new_payload( +pub async fn notify_new_payload( chain: &Arc>, - block: BeaconBlockRef<'_, T::EthSpec>, + beacon_block_root: Hash256, + new_payload_request: NewPayloadRequest<'_, T::EthSpec>, ) -> Result { let execution_layer = chain .execution_layer .as_ref() .ok_or(ExecutionPayloadError::NoExecutionConnection)?; - let execution_block_hash = block.execution_payload()?.block_hash(); - let new_payload_response = execution_layer.notify_new_payload(block.try_into()?).await; + let execution_block_hash = new_payload_request.execution_payload_ref().block_hash(); + let new_payload_response = execution_layer.notify_new_payload(new_payload_request.clone()).await; match new_payload_response { Ok(status) => match status { @@ -153,10 +154,10 @@ async fn notify_new_payload( ?validation_error, ?latest_valid_hash, ?execution_block_hash, - root = ?block.tree_hash_root(), - graffiti = block.body().graffiti().as_utf8_lossy(), - proposer_index = block.proposer_index(), - slot = %block.slot(), + // root = ?block.tree_hash_root(), + // graffiti = block.body().graffiti().as_utf8_lossy(), + // proposer_index = block.proposer_index(), + // slot = %block.slot(), method = "new_payload", "Invalid execution payload" ); @@ -179,11 +180,11 @@ async fn notify_new_payload( { // This block has not yet been applied to fork choice, so the latest block that was // imported to fork choice was the parent. - let latest_root = block.parent_root(); + let latest_root = new_payload_request.parent_beacon_block_root()?; chain .process_invalid_execution_payload(&InvalidationOperation::InvalidateMany { - head_block_root: latest_root, + head_block_root: *latest_root, always_invalidate_head: false, latest_valid_ancestor: latest_valid_hash, }) @@ -198,10 +199,10 @@ async fn notify_new_payload( warn!( ?validation_error, ?execution_block_hash, - root = ?block.tree_hash_root(), - graffiti = block.body().graffiti().as_utf8_lossy(), - proposer_index = block.proposer_index(), - slot = %block.slot(), + // root = ?block.tree_hash_root(), + // graffiti = block.body().graffiti().as_utf8_lossy(), + // proposer_index = block.proposer_index(), + // slot = %block.slot(), method = "new_payload", "Invalid execution payload block hash" ); diff --git a/beacon_node/beacon_chain/src/lib.rs b/beacon_node/beacon_chain/src/lib.rs index 7aa64deb1c..4120ed86fc 100644 --- a/beacon_node/beacon_chain/src/lib.rs +++ b/beacon_node/beacon_chain/src/lib.rs @@ -43,7 +43,6 @@ pub mod observed_data_sidecars; pub mod observed_operations; mod observed_slashable; pub mod payload_envelope_verification; -pub mod payload_envelope_verification_types; pub mod persisted_beacon_chain; pub mod persisted_custody; mod persisted_fork_choice; diff --git a/beacon_node/beacon_chain/src/payload_envelope_verification/execution_pending_envelope.rs b/beacon_node/beacon_chain/src/payload_envelope_verification/execution_pending_envelope.rs new file mode 100644 index 0000000000..956e6ffb8f --- /dev/null +++ b/beacon_node/beacon_chain/src/payload_envelope_verification/execution_pending_envelope.rs @@ -0,0 +1,26 @@ +use task_executor::JoinHandle; +use types::{EthSpec, FullPayload}; + +use crate::{BeaconChainTypes, PayloadVerificationOutcome, payload_envelope_verification::PayloadEnvelopeImportData}; + + +/// Used to await the result of executing payload with an EE. +pub type PayloadVerificationHandle = + JoinHandle>>>; + +/// A wrapper around a `SignedBeaconBlock` that indicates that this block is fully verified and +/// ready to import into the `BeaconChain`. The validation includes: +/// +/// - Parent is known +/// - Signatures +/// - State root check +/// - Block processing +/// +/// Note: a `ExecutionPendingEnvelope` is not _forever_ valid to be imported, it may later become invalid +/// due to finality or some other event. A `ExecutionPendingEnvelope` should be imported into the +/// `BeaconChain` immediately after it is instantiated. +pub struct ExecutionPendingEnvelope { + pub block: MaybeAvailableBlock, + pub import_data: PayloadEnvelopeImportData, + pub payload_verification_handle: PayloadVerificationHandle, +} \ No newline at end of file diff --git a/beacon_node/beacon_chain/src/payload_envelope_verification.rs b/beacon_node/beacon_chain/src/payload_envelope_verification/gossip_verified_envelope.rs similarity index 55% rename from beacon_node/beacon_chain/src/payload_envelope_verification.rs rename to beacon_node/beacon_chain/src/payload_envelope_verification/gossip_verified_envelope.rs index b22acd69fb..2bd5a6c3d7 100644 --- a/beacon_node/beacon_chain/src/payload_envelope_verification.rs +++ b/beacon_node/beacon_chain/src/payload_envelope_verification/gossip_verified_envelope.rs @@ -1,175 +1,14 @@ -//! 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 `SignedBeaconBlock` and finishing with a `Fully VerifiedBlock` (see -//! diagram below). -//! -//! ```ignore -//! START -//! | -//! ▼ -//! SignedExecutionPayloadEnvelope -//! | -//! |--------------- -//! | | -//! | ▼ -//! | GossipVerifiedEnvelope -//! | | -//! |--------------- -//! | -//! ▼ -//! ExecutionPendingEnvelope -//! | -//! await -//! | -//! ▼ -//! END -//! -//! ``` - -use crate::NotifyExecutionLayer; -use crate::block_verification::{PayloadVerificationHandle, PayloadVerificationOutcome}; -use crate::payload_envelope_verification_types::{EnvelopeImportData, MaybeAvailableEnvelope}; -use crate::execution_payload::PayloadNotifier; -use crate::{BeaconChain, BeaconChainError, BeaconChainTypes}; -use educe::Educe; -use slot_clock::SlotClock; -use state_processing::envelope_processing::{EnvelopeProcessingError, process_execution_payload_envelope}; -use state_processing::{BlockProcessingError, VerifySignatures}; use std::sync::Arc; -use tracing::{debug, instrument}; -use types::{ - BeaconState, BeaconStateError, EthSpec, ExecutionBlockHash, Hash256, SignedBeaconBlock, - SignedExecutionPayloadEnvelope, Slot, + +use educe::Educe; +use state_processing::{VerifySignatures, envelope_processing::process_execution_payload_envelope}; +use tracing::debug; +use types::{EthSpec, SignedBeaconBlock, SignedExecutionPayloadEnvelope}; + +use crate::{ + BeaconChain, BeaconChainError, BeaconChainTypes, NotifyExecutionLayer, PayloadVerificationOutcome, payload_envelope_verification::{EnvelopeError, EnvelopeImportData, EnvelopeProcessingSnapshot, ExecutionPendingEnvelope, IntoExecutionPendingEnvelope, MaybeAvailableEnvelope, load_snapshot, payload_notifier::PayloadNotifier} }; -#[derive(Debug, Clone)] -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, - }, - // Some Beacon Chain Error - BeaconChainError(Arc), - // Some Beacon State error - BeaconStateError(BeaconStateError), - // Some BlockProcessingError (for electra operations) - BlockProcessingError(BlockProcessingError), - // Some EnvelopeProcessingError - EnvelopeProcessingError(EnvelopeProcessingError), -} - -impl From for EnvelopeError { - fn from(e: BeaconChainError) -> Self { - EnvelopeError::BeaconChainError(Arc::new(e)) - } -} - -impl From for EnvelopeError { - fn from(e: BeaconStateError) -> Self { - EnvelopeError::BeaconStateError(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), - } - } -} - -/// 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, -} - -#[allow(clippy::type_complexity)] -#[instrument(skip_all, level = "debug", fields(beacon_block_root = %envelope.beacon_block_root()))] -fn load_snapshot( - envelope: &SignedExecutionPayloadEnvelope, - chain: &BeaconChain, -) -> Result, EnvelopeError> { - // Reject any block 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 block because we should only import a child - // envelope after its parent has been fully imported. - // - Pre-finalized: if the 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 = chain.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); - - // TODO(EIP-7732): add metrics here - - let block_state_root = proto_beacon_block.state_root; - // 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 = chain - .store - .get_hot_state(&block_state_root, cache_state) - .map_err(|e| EnvelopeError::BeaconChainError(Arc::new(e.into())))? - .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, - }) -} - /// A wrapper around a `SignedExecutionPayloadEnvelope` that indicates it has been approved for re-gossiping on /// the p2p network. #[derive(Educe)] @@ -313,20 +152,6 @@ impl GossipVerifiedEnvelope { } } -pub trait IntoExecutionPendingEnvelope: Sized { - fn into_execution_pending_envelope( - self, - chain: &Arc>, - notify_execution_layer: NotifyExecutionLayer, - ) -> Result, EnvelopeError>; -} - -pub struct ExecutionPendingEnvelope { - pub signed_envelope: MaybeAvailableEnvelope, - pub import_data: EnvelopeImportData, - pub payload_verification_handle: PayloadVerificationHandle, -} - impl IntoExecutionPendingEnvelope for GossipVerifiedEnvelope { fn into_execution_pending_envelope( self, @@ -336,10 +161,13 @@ impl IntoExecutionPendingEnvelope for GossipVerifiedEnve let signed_envelope = self.signed_envelope; let envelope = &signed_envelope.message; let payload = &envelope.payload; + + // TODO(gloas) unwrap + let bid = chain.get_full_block(&envelope.beacon_block_root).unwrap().unwrap().message().body().signed_execution_payload_bid().unwrap().message; // Verify the execution payload is valid let payload_notifier = - PayloadNotifier::from_envelope(chain.clone(), envelope, notify_execution_layer)?; + PayloadNotifier::new(chain.clone(), envelope, notify_execution_layer)?; let block_root = envelope.beacon_block_root; let slot = self.block.slot(); @@ -403,17 +231,3 @@ impl IntoExecutionPendingEnvelope for GossipVerifiedEnve }) } } - -impl IntoExecutionPendingEnvelope - for Arc> -{ - fn into_execution_pending_envelope( - self, - chain: &Arc>, - notify_execution_layer: NotifyExecutionLayer, - ) -> Result, EnvelopeError> { - // TODO(EIP-7732): figure out how this should be refactored.. - GossipVerifiedEnvelope::new(self, chain)? - .into_execution_pending_envelope(chain, notify_execution_layer) - } -} \ No newline at end of file diff --git a/beacon_node/beacon_chain/src/payload_envelope_verification/mod.rs b/beacon_node/beacon_chain/src/payload_envelope_verification/mod.rs new file mode 100644 index 0000000000..4a739e74c5 --- /dev/null +++ b/beacon_node/beacon_chain/src/payload_envelope_verification/mod.rs @@ -0,0 +1,229 @@ +//! 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 `SignedBeaconBlock` and finishing with a `Fully VerifiedBlock` (see +//! diagram below). +//! +//! ```ignore +//! START +//! | +//! ▼ +//! SignedExecutionPayloadEnvelope +//! | +//! |--------------- +//! | | +//! | ▼ +//! | GossipVerifiedEnvelope +//! | | +//! |--------------- +//! | +//! ▼ +//! ExecutionPendingEnvelope +//! | +//! await +//! | +//! ▼ +//! END +//! +//! ``` + +use std::sync::Arc; + +use state_processing::{BlockProcessingError, ConsensusContext, envelope_processing::EnvelopeProcessingError}; +use tracing::instrument; +use types::{BeaconState, BeaconStateError, ChainSpec, DataColumnSidecarList, EthSpec, ExecutionBlockHash, Hash256, SignedBeaconBlock, SignedExecutionPayloadEnvelope, Slot}; + +use crate::{BeaconChain, BeaconChainError, BeaconChainTypes, NotifyExecutionLayer, block_verification::PayloadVerificationHandle, payload_envelope_verification::gossip_verified_envelope::GossipVerifiedEnvelope}; + +pub mod execution_pending_envelope; +pub mod gossip_verified_envelope; +mod payload_notifier; + +pub trait IntoExecutionPendingEnvelope: Sized { + fn into_execution_pending_envelope( + self, + chain: &Arc>, + notify_execution_layer: NotifyExecutionLayer, + ) -> Result, EnvelopeError>; +} + +pub struct ExecutionPendingEnvelope { + pub signed_envelope: MaybeAvailableEnvelope, + pub import_data: EnvelopeImportData, + pub payload_verification_handle: PayloadVerificationHandle, +} + +#[derive(PartialEq)] +pub struct EnvelopeImportData { + pub block_root: Hash256, + pub block: Arc>, + pub post_state: Box>, +} + +#[derive(Debug)] +#[allow(dead_code)] +pub struct AvailableEnvelope { + // TODO(EIP-7732): rename to execution_block_hash + 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, +} + +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, +} + +#[derive(Debug, Clone)] +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, + }, + // Some Beacon Chain Error + BeaconChainError(Arc), + // Some Beacon State error + BeaconStateError(BeaconStateError), + // Some BlockProcessingError (for electra operations) + BlockProcessingError(BlockProcessingError), + // Some EnvelopeProcessingError + EnvelopeProcessingError(EnvelopeProcessingError), +} + +impl From for EnvelopeError { + fn from(e: BeaconChainError) -> Self { + EnvelopeError::BeaconChainError(Arc::new(e)) + } +} + +impl From for EnvelopeError { + fn from(e: BeaconStateError) -> Self { + EnvelopeError::BeaconStateError(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 = %envelope.beacon_block_root()))] +pub(crate) fn load_snapshot( + envelope: &SignedExecutionPayloadEnvelope, + chain: &BeaconChain, +) -> Result, EnvelopeError> { + // Reject any block 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 block because we should only import a child + // envelope after its parent has been fully imported. + // - Pre-finalized: if the 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 = chain.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); + + // TODO(EIP-7732): add metrics here + + let block_state_root = proto_beacon_block.state_root; + // 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 = chain + .store + .get_hot_state(&block_state_root, cache_state) + .map_err(|e| EnvelopeError::BeaconChainError(Arc::new(e.into())))? + .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, + }) +} + +impl IntoExecutionPendingEnvelope + for Arc> +{ + fn into_execution_pending_envelope( + self, + chain: &Arc>, + notify_execution_layer: NotifyExecutionLayer, + ) -> Result, EnvelopeError> { + // TODO(EIP-7732): figure out how this should be refactored.. + GossipVerifiedEnvelope::new(self, chain)? + .into_execution_pending_envelope(chain, notify_execution_layer) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct PayloadEnvelopeImportData { + pub block_root: Hash256, + pub state: BeaconState, + pub consensus_context: ConsensusContext, +} diff --git a/beacon_node/beacon_chain/src/payload_envelope_verification/payload_notifier.rs b/beacon_node/beacon_chain/src/payload_envelope_verification/payload_notifier.rs new file mode 100644 index 0000000000..15fec7e21d --- /dev/null +++ b/beacon_node/beacon_chain/src/payload_envelope_verification/payload_notifier.rs @@ -0,0 +1,77 @@ +use std::sync::Arc; + +use execution_layer::NewPayloadRequest; +use fork_choice::PayloadVerificationStatus; +use state_processing::{envelope_processing::partially_verify_payload_envelope, per_block_processing::is_execution_enabled}; +use tracing::warn; +use types::{BeaconState, ExecutionPayloadBid, Hash256, SignedBeaconBlock, SignedExecutionPayloadEnvelope}; + +use crate::{BeaconChain, BeaconChainTypes, BlockError, ExecutionPayloadError, NotifyExecutionLayer, execution_payload::notify_new_payload}; + + +/// Used to await the result of executing payload with a remote EE. +pub struct PayloadNotifier { + pub chain: Arc>, + pub envelope: Arc>, + payload_verification_status: Option, +} + +impl PayloadNotifier { + pub fn new( + chain: Arc>, + bid: &ExecutionPayloadBid, + envelope: Arc>, + state: &BeaconState, + notify_execution_layer: NotifyExecutionLayer, + ) -> Result { + let payload_verification_status = { + // Perform the initial stages of payload verification. + // + // We will duplicate these checks again during `per_block_processing`, however these + // checks are cheap and doing them here ensures we have verified them before marking + // the block as optimistically imported. This is particularly relevant in the case + // where we do not send the block to the EL at all. + let payload_message = &envelope.message; + partially_verify_payload_envelope( + state, + &envelope, + &chain.spec, + ).unwrap(); // TODO(gloas) unwrap + + match notify_execution_layer { + NotifyExecutionLayer::No if chain.config.optimistic_finalized_sync => { + // Create a NewPayloadRequest (no clones required) and check optimistic sync verifications + let new_payload_request: NewPayloadRequest = + payload_message.try_into()?; + if let Err(e) = new_payload_request.perform_optimistic_sync_verifications() { + warn!( + block_number = ?payload_message.payload.block_number, + info = "you can silence this warning with --disable-optimistic-finalized-sync", + error = ?e, + "Falling back to slow block hash verification" + ); + None + } else { + Some(PayloadVerificationStatus::Optimistic) + } + } + _ => None, + } + }; + + Ok(Self { + chain, + envelope, + payload_verification_status, + }) + } + + pub async fn notify_new_payload(self) -> Result { + if let Some(precomputed_status) = self.payload_verification_status { + Ok(precomputed_status) + } else { + // tODO(gloas) fix zero + notify_new_payload(&self.chain, Hash256::ZERO, self.envelope.message.try_into()?).await + } + } +} \ No newline at end of file diff --git a/beacon_node/beacon_chain/src/payload_envelope_verification_types.rs b/beacon_node/beacon_chain/src/payload_envelope_verification_types.rs deleted file mode 100644 index bd2eb5a34f..0000000000 --- a/beacon_node/beacon_chain/src/payload_envelope_verification_types.rs +++ /dev/null @@ -1,31 +0,0 @@ -use std::sync::Arc; -use types::{ - BeaconState, ChainSpec, DataColumnSidecarList, EthSpec, ExecutionBlockHash, Hash256, - SignedBeaconBlock, SignedExecutionPayloadEnvelope, -}; - -#[derive(PartialEq)] -pub struct EnvelopeImportData { - pub block_root: Hash256, - pub block: Arc>, - pub post_state: Box>, -} - -#[derive(Debug)] -#[allow(dead_code)] -pub struct AvailableEnvelope { - // TODO(EIP-7732): rename to execution_block_hash - 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, -} -pub enum MaybeAvailableEnvelope { - Available(AvailableEnvelope), - AvailabilityPending { - block_hash: ExecutionBlockHash, - envelope: Arc>, - }, -} \ No newline at end of file diff --git a/beacon_node/execution_layer/src/engine_api/new_payload_request.rs b/beacon_node/execution_layer/src/engine_api/new_payload_request.rs index ba94296b85..b88dd7ca3a 100644 --- a/beacon_node/execution_layer/src/engine_api/new_payload_request.rs +++ b/beacon_node/execution_layer/src/engine_api/new_payload_request.rs @@ -4,8 +4,7 @@ use crate::versioned_hashes::verify_versioned_hashes; use state_processing::per_block_processing::deneb::kzg_commitment_to_versioned_hash; use superstruct::superstruct; use types::{ - BeaconBlockRef, BeaconStateError, EthSpec, ExecutionBlockHash, ExecutionPayload, - ExecutionPayloadRef, Hash256, VersionedHash, + BeaconBlockRef, BeaconStateError, EthSpec, ExecutionBlockHash, ExecutionPayload, ExecutionPayloadEnvelope, ExecutionPayloadRef, Hash256, VersionedHash }; use types::{ ExecutionPayloadBellatrix, ExecutionPayloadCapella, ExecutionPayloadDeneb, diff --git a/consensus/state_processing/src/envelope_processing.rs b/consensus/state_processing/src/envelope_processing.rs index d46728dbbc..2f511846a5 100644 --- a/consensus/state_processing/src/envelope_processing.rs +++ b/consensus/state_processing/src/envelope_processing.rs @@ -276,3 +276,104 @@ pub fn process_execution_payload_envelope( Ok(()) } + + +/// Performs *partial* verification of the `payload envelope`. +pub fn partially_verify_payload_envelope( + state: &BeaconState, + signed_envelope: &SignedExecutionPayloadEnvelope, + spec: &ChainSpec, +) -> Result<(), EnvelopeProcessingError> { + let envelope = &signed_envelope.message; + let payload = &signed_envelope.message.payload; + + // Verify consistency with the beacon block + let latest_block_header_root = state.latest_block_header().tree_hash_root(); + envelope_verify!( + envelope.beacon_block_root == latest_block_header_root, + EnvelopeProcessingError::LatestBlockHeaderMismatch { + envelope_root: envelope.beacon_block_root, + block_header_root: latest_block_header_root, + } + ); + envelope_verify!( + envelope.slot == state.slot(), + EnvelopeProcessingError::SlotMismatch { + envelope_slot: envelope.slot, + parent_state_slot: state.slot(), + } + ); + + // Verify consistency with the committed bid + let committed_bid = state.latest_execution_payload_bid()?; + envelope_verify!( + envelope.builder_index == committed_bid.builder_index, + EnvelopeProcessingError::BuilderIndexMismatch { + committed_bid: committed_bid.builder_index, + envelope: envelope.builder_index, + } + ); + envelope_verify!( + committed_bid.prev_randao == payload.prev_randao, + EnvelopeProcessingError::PrevRandaoMismatch { + committed_bid: committed_bid.prev_randao, + envelope: payload.prev_randao, + } + ); + + // Verify consistency with expected withdrawals + // NOTE: we don't bother hashing here except in case of error, because we can just compare for + // equality directly. This equality check could be more straight-forward if the types were + // changed to match (currently we are comparing VariableList to List). This could happen + // coincidentally when we adopt ProgressiveList. + envelope_verify!( + payload.withdrawals.len() == state.payload_expected_withdrawals()?.len() + && payload + .withdrawals + .iter() + .eq(state.payload_expected_withdrawals()?.iter()), + EnvelopeProcessingError::WithdrawalsRootMismatch { + state: state.payload_expected_withdrawals()?.tree_hash_root(), + payload: payload.withdrawals.tree_hash_root(), + } + ); + + // Verify the gas limit + envelope_verify!( + committed_bid.gas_limit == payload.gas_limit, + EnvelopeProcessingError::GasLimitMismatch { + committed_bid: committed_bid.gas_limit, + envelope: payload.gas_limit, + } + ); + + // Verify the block hash + envelope_verify!( + committed_bid.block_hash == payload.block_hash, + EnvelopeProcessingError::BlockHashMismatch { + committed_bid: committed_bid.block_hash, + envelope: payload.block_hash, + } + ); + + // Verify consistency of the parent hash with respect to the previous execution payload + envelope_verify!( + payload.parent_hash == *state.latest_block_hash()?, + EnvelopeProcessingError::ParentHashMismatch { + state: *state.latest_block_hash()?, + envelope: payload.parent_hash, + } + ); + + // Verify timestamp + let state_timestamp = compute_timestamp_at_slot(state, state.slot(), spec)?; + envelope_verify!( + payload.timestamp == state_timestamp, + EnvelopeProcessingError::TimestampMismatch { + state: state_timestamp, + envelope: payload.timestamp, + } + ); + + Ok(()) +}