From 29e5a1f599844007b9a7eed869a14ba140ddb1e5 Mon Sep 17 00:00:00 2001 From: Mark Mackey Date: Wed, 15 Oct 2025 10:15:44 -0500 Subject: [PATCH] hold for now --- .../beacon_chain/src/envelope_verification.rs | 449 ++++++++++++++++++ .../src/envelope_verification_types.rs | 136 ++++++ beacon_node/beacon_chain/src/lib.rs | 2 + .../src/envelope_processing.rs | 65 +++ consensus/state_processing/src/lib.rs | 1 + .../per_block_processing/signature_sets.rs | 30 +- .../src/signed_execution_payload_envelope.rs | 30 ++ 7 files changed, 712 insertions(+), 1 deletion(-) create mode 100644 beacon_node/beacon_chain/src/envelope_verification.rs create mode 100644 beacon_node/beacon_chain/src/envelope_verification_types.rs create mode 100644 consensus/state_processing/src/envelope_processing.rs diff --git a/beacon_node/beacon_chain/src/envelope_verification.rs b/beacon_node/beacon_chain/src/envelope_verification.rs new file mode 100644 index 0000000000..6f927c6387 --- /dev/null +++ b/beacon_node/beacon_chain/src/envelope_verification.rs @@ -0,0 +1,449 @@ +//! 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::block_verification::{PayloadVerificationHandle, PayloadVerificationOutcome}; +use crate::data_availability_checker::MaybeAvailableEnvelope; +use crate::envelope_verification_types::EnvelopeImportData; +use crate::execution_payload::PayloadNotifier; +use crate::NotifyExecutionLayer; +use crate::{BeaconChain, BeaconChainError, BeaconChainTypes}; +use derivative::Derivative; +use safe_arith::ArithError; +use slot_clock::SlotClock; +use state_processing::envelope_processing::{envelope_processing, EnvelopeProcessingError}; +use state_processing::per_block_processing::compute_timestamp_at_slot; +use state_processing::{BlockProcessingError, VerifySignatures}; +use std::sync::Arc; +use tree_hash::TreeHash; +use types::{ + BeaconState, BeaconStateError, EthSpec, ExecutionBlockHash, Hash256, SignedBlindedBeaconBlock, + SignedExecutionPayloadEnvelope, +}; + +// TODO(EIP7732): don't use this redefinition.. +macro_rules! envelope_verify { + ($condition: expr, $result: expr) => { + if !$condition { + return Err($result); + } + }; +} + +#[derive(Debug)] +pub enum EnvelopeError { + /// The envelope's block root is unknown. + BlockRootUnknown { + block_root: Hash256, + }, + /// The signature is invalid. + BadSignature, + /// Envelope doesn't match latest beacon block header + LatestBlockHeaderMismatch { + envelope_root: Hash256, + block_header_root: Hash256, + }, + /// The builder index doesn't match the committed bid + BuilderIndexMismatch { + committed_bid: u64, + envelope: u64, + }, + /// The blob KZG commitments root doesn't match the committed bid + BlobKzgCommitmentsRootMismatch { + committed_bid: Hash256, + envelope: Hash256, + }, + /// The withdrawals root doesn't match the state's latest withdrawals root + WithdrawalsRootMismatch { + state: Hash256, + envelope: Hash256, + }, + // The gas limit doesn't match the committed bid + GasLimitMismatch { + committed_bid: u64, + envelope: u64, + }, + // The block hash doesn't match the committed bid + BlockHashMismatch { + committed_bid: ExecutionBlockHash, + envelope: ExecutionBlockHash, + }, + // The parent hash doesn't match the previous execution payload + ParentHashMismatch { + state: ExecutionBlockHash, + envelope: ExecutionBlockHash, + }, + // The previous randao didn't match the payload + PrevRandaoMismatch { + state: Hash256, + envelope: Hash256, + }, + // The timestamp didn't match the payload + TimestampMismatch { + state: u64, + envelope: u64, + }, + // Blob committments exceeded the maximum + BlobLimitExceeded { + max: usize, + envelope: usize, + }, + // Invalid state root + InvalidStateRoot { + state: Hash256, + envelope: Hash256, + }, + // The payload was withheld but the block hash + // matched the committed bid + PayloadWithheldBlockHashMismatch, + // Some Beacon Chain Error + BeaconChainError(BeaconChainError), + // Some Beacon State error + BeaconStateError(BeaconStateError), + // Some ArithError + ArithError(ArithError), + // Some BlockProcessingError (for electra operations) + BlockProcessingError(BlockProcessingError), +} + +impl From for EnvelopeError { + fn from(e: BeaconChainError) -> Self { + EnvelopeError::BeaconChainError(e) + } +} + +impl From for EnvelopeError { + fn from(e: BeaconStateError) -> Self { + EnvelopeError::BeaconStateError(e) + } +} + +impl From for EnvelopeError { + fn from(e: ArithError) -> Self { + EnvelopeError::ArithError(e) + } +} + +impl From for EnvelopeError { + fn from(e: EnvelopeProcessingError) -> Self { + match e { + EnvelopeProcessingError::BadSignature => EnvelopeError::BadSignature, + EnvelopeProcessingError::BeaconStateError(e) => EnvelopeError::BeaconStateError(e), + EnvelopeProcessingError::BlockProcessingError(e) => { + EnvelopeError::BlockProcessingError(e) + } + } + } +} + +/// A wrapper around a `SignedExecutionPayloadEnvelope` that indicates it has been approved for re-gossiping on +/// the p2p network. +#[derive(Derivative)] +#[derivative(Debug(bound = "T: BeaconChainTypes"))] +pub struct GossipVerifiedEnvelope { + pub signed_envelope: Arc>, + pub parent_block: Arc>, + pub pre_state: Box>, +} + +impl GossipVerifiedEnvelope { + pub fn new( + signed_envelope: Arc>, + chain: &BeaconChain, + ) -> Result { + let envelope = signed_envelope.message(); + let payload = envelope.payload(); + let block_root = envelope.beacon_block_root(); + + // TODO(EIP7732): this check would fail if the block didn't pass validation right? + + // check that we've seen the parent block of this envelope + let fork_choice_read_lock = chain.canonical_head.fork_choice_read_lock(); + if !fork_choice_read_lock.contains_block(&block_root) { + return Err(EnvelopeError::BlockRootUnknown { block_root }); + } + drop(fork_choice_read_lock); + + let parent_block = chain + .get_blinded_block(&block_root)? + .ok_or_else(|| EnvelopeError::from(BeaconChainError::MissingBeaconBlock(block_root))) + .map(Arc::new)?; + let execution_bid = &parent_block + .message() + .body() + .signed_execution_bid()? + .message; + + // TODO(EIP7732): check we're within the bounds of the slot (probably) + + // TODO(EIP7732): check that we haven't seen another valid `SignedExecutionPayloadEnvelope` + // for this block root from this builder + + // builder index matches committed bid + if envelope.builder_index() != execution_bid.builder_index { + return Err(EnvelopeError::BuilderIndexMismatch { + committed_bid: execution_bid.builder_index, + envelope: envelope.builder_index(), + }); + } + + // if payload is withheld, the block hash should not match the committed bid + if !envelope.payload_withheld() && payload.block_hash() == execution_bid.block_hash { + return Err(EnvelopeError::PayloadWithheldBlockHashMismatch); + } + + let parent_state = chain + .get_state( + &parent_block.message().state_root(), + Some(parent_block.slot()), + )? + .ok_or_else(|| { + EnvelopeError::from(BeaconChainError::MissingBeaconState( + parent_block.message().state_root(), + )) + })?; + + // verify the signature + if !signed_envelope.verify_signature(&parent_state, &chain.spec)? { + return Err(EnvelopeError::BadSignature); + } + + Ok(Self { + signed_envelope, + parent_block, + pre_state: Box::new(parent_state), + }) + } + + pub fn envelope_cloned(&self) -> Arc> { + self.signed_envelope.clone() + } +} + +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, + chain: &Arc>, + notify_execution_layer: NotifyExecutionLayer, + ) -> Result, EnvelopeError> { + let signed_envelope = self.signed_envelope; + let envelope = signed_envelope.message(); + let payload = &envelope.payload(); + + // verify signature already done + let mut state = *self.pre_state; + + // setting state.latest_block_header happens in envelope_processing + + // Verify consistency with the beacon block + if !envelope.tree_hash_root() == state.latest_block_header().tree_hash_root() { + return Err(EnvelopeError::LatestBlockHeaderMismatch { + envelope_root: envelope.tree_hash_root(), + block_header_root: state.latest_block_header().tree_hash_root(), + }); + }; + + // Verify consistency with the committed bid + let committed_bid = state.latest_execution_bid()?; + // builder index match already verified + if committed_bid.blob_kzg_commitments_root + != envelope.blob_kzg_commitments().tree_hash_root() + { + return Err(EnvelopeError::BlobKzgCommitmentsRootMismatch { + committed_bid: committed_bid.blob_kzg_commitments_root, + envelope: envelope.blob_kzg_commitments().tree_hash_root(), + }); + }; + + if !envelope.payload_withheld() { + // Verify the withdrawals root + envelope_verify!( + payload.withdrawals()?.tree_hash_root() == state.latest_withdrawals_root()?, + EnvelopeError::WithdrawalsRootMismatch { + state: state.latest_withdrawals_root()?, + envelope: payload.withdrawals()?.tree_hash_root(), + } + .into() + ); + + // Verify the gas limit + envelope_verify!( + payload.gas_limit() == committed_bid.gas_limit, + EnvelopeError::GasLimitMismatch { + committed_bid: committed_bid.gas_limit, + envelope: payload.gas_limit(), + } + .into() + ); + // Verify the block hash + envelope_verify!( + committed_bid.block_hash == payload.block_hash(), + EnvelopeError::BlockHashMismatch { + committed_bid: committed_bid.block_hash, + envelope: payload.block_hash(), + } + .into() + ); + + // Verify consistency of the parent hash with respect to the previous execution payload + envelope_verify!( + payload.parent_hash() == state.latest_block_hash()?, + EnvelopeError::ParentHashMismatch { + state: state.latest_block_hash()?, + envelope: payload.parent_hash(), + } + .into() + ); + + // Verify prev_randao + envelope_verify!( + payload.prev_randao() == *state.get_randao_mix(state.current_epoch())?, + EnvelopeError::PrevRandaoMismatch { + state: *state.get_randao_mix(state.current_epoch())?, + envelope: payload.prev_randao(), + } + .into() + ); + + // Verify the timestamp + let state_timestamp = + compute_timestamp_at_slot(&state, state.slot(), chain.spec.as_ref())?; + envelope_verify!( + payload.timestamp() == state_timestamp, + EnvelopeError::TimestampMismatch { + state: state_timestamp, + envelope: payload.timestamp(), + } + .into() + ); + + // Verify the commitments are under limit + envelope_verify!( + envelope.blob_kzg_commitments().len() + <= T::EthSpec::max_blob_commitments_per_block(), + EnvelopeError::BlobLimitExceeded { + max: T::EthSpec::max_blob_commitments_per_block(), + envelope: envelope.blob_kzg_commitments().len(), + } + .into() + ); + } + + // Verify the execution payload is valid + let payload_notifier = + PayloadNotifier::from_envelope(chain.clone(), envelope, notify_execution_layer)?; + let block_root = envelope.beacon_block_root(); + let slot = self.parent_block.slot(); + + let payload_verification_future = async move { + let chain = payload_notifier.chain.clone(); + // TODO:(EIP7732): timing + if let Some(started_execution) = chain.slot_clock.now_duration() { + chain.block_times_cache.write().set_time_started_execution( + block_root, + slot, + started_execution, + ); + } + + let payload_verification_status = payload_notifier.notify_new_payload().await?; + Ok(PayloadVerificationOutcome { + payload_verification_status, + // This fork is after the merge so it'll never be the merge transition block + is_valid_merge_transition_block: false, + }) + }; + // Spawn the payload verification future as a new task, but don't wait for it to complete. + // The `payload_verification_future` will be awaited later to ensure verification completed + // successfully. + let payload_verification_handle = chain + .task_executor + .spawn_handle( + payload_verification_future, + "execution_payload_verification", + ) + .ok_or(BeaconChainError::RuntimeShutdown)?; + + // All the state modifications are done in envelope_processing + envelope_processing( + &mut state, + &signed_envelope, + VerifySignatures::False, + &chain.spec, + )?; + + // TODO(EIP7732): if verify + envelope_verify!( + state.canonical_root()? == envelope.state_root(), + EnvelopeError::InvalidStateRoot { + state: state.canonical_root()?, + envelope: envelope.state_root(), + } + ); + + Ok(ExecutionPendingEnvelope { + signed_envelope: MaybeAvailableEnvelope::AvailabilityPending { + block_root, + envelope: signed_envelope, + }, + import_data: EnvelopeImportData { + block_root, + parent_block: self.parent_block, + post_state: Box::new(state), + }, + payload_verification_handle, + }) + } +} + +impl IntoExecutionPendingEnvelope + for Arc> +{ + fn into_execution_pending_envelope( + self, + chain: &Arc>, + notify_execution_layer: NotifyExecutionLayer, + ) -> Result, EnvelopeError> { + // TODO(EIP7732): 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/envelope_verification_types.rs b/beacon_node/beacon_chain/src/envelope_verification_types.rs new file mode 100644 index 0000000000..ac58c98582 --- /dev/null +++ b/beacon_node/beacon_chain/src/envelope_verification_types.rs @@ -0,0 +1,136 @@ +use crate::data_availability_checker::{AvailableEnvelope, MaybeAvailableEnvelope}; +use crate::PayloadVerificationOutcome; +use std::sync::Arc; +use types::{ + BeaconState, BlobIdentifier, EthSpec, Hash256, SignedBlindedBeaconBlock, + SignedExecutionPayloadEnvelope, +}; + +/// A block that has completed all pre-deneb block processing checks including verification +/// by an EL client **and** has all requisite blob data to be imported into fork choice. +#[derive(PartialEq)] +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, + } + } + + pub fn get_all_blob_ids(&self) -> Vec { + let num_blobs_expected = self + .envelope + .envelope() + .message() + .blob_kzg_commitments() + .len(); + let mut blob_ids = Vec::with_capacity(num_blobs_expected); + for i in 0..num_blobs_expected { + blob_ids.push(BlobIdentifier { + block_root: self.import_data.block_root, + index: i as u64, + }); + } + blob_ids + } +} + +#[derive(PartialEq)] +pub struct EnvelopeImportData { + pub block_root: Hash256, + pub parent_block: Arc>, + pub post_state: Box>, +} + +pub struct AvailabilityPendingExecutedEnvelope { + pub envelope: Arc>, + pub import_data: EnvelopeImportData, + pub payload_verification_outcome: PayloadVerificationOutcome, +} + +impl AvailabilityPendingExecutedEnvelope { + pub fn new( + envelope: Arc>, + import_data: EnvelopeImportData, + payload_verification_outcome: PayloadVerificationOutcome, + ) -> Self { + Self { + envelope, + import_data, + payload_verification_outcome, + } + } + + pub fn as_envelope(&self) -> &SignedExecutionPayloadEnvelope { + self.envelope.as_ref() + } + + pub fn num_blobs_expected(&self) -> usize { + self.envelope.message().blob_kzg_commitments().len() + } +} + +/// An envelope that has gone through all envelope processing checks including envelope processing +/// and execution by an EL client. This block hasn't necessarily completed data availability checks. +/// +/// +/// It contains 2 variants: +/// 1. `Available`: This envelope has been executed and also contains all data to consider it a +/// fully available envelope. +/// 2. `AvailabilityPending`: This envelope hasn't received all required blobs to consider it a +/// fully available envelope. +pub enum ExecutedEnvelope { + Available(AvailableExecutedEnvelope), + AvailabilityPending(AvailabilityPendingExecutedEnvelope), +} + +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, + )) + } + MaybeAvailableEnvelope::AvailabilityPending { + block_root: _, + envelope, + } => Self::AvailabilityPending(AvailabilityPendingExecutedEnvelope::new( + envelope, + import_data, + payload_verification_outcome, + )), + } + } + + pub fn as_envelope(&self) -> &SignedExecutionPayloadEnvelope { + match self { + Self::Available(available) => available.envelope.envelope(), + Self::AvailabilityPending(pending) => pending.envelope.as_ref(), + } + } + + pub fn block_root(&self) -> Hash256 { + match self { + Self::Available(available) => available.import_data.block_root, + Self::AvailabilityPending(pending) => pending.import_data.block_root, + } + } +} \ No newline at end of file diff --git a/beacon_node/beacon_chain/src/lib.rs b/beacon_node/beacon_chain/src/lib.rs index 4ac3e54742..8df88532c2 100644 --- a/beacon_node/beacon_chain/src/lib.rs +++ b/beacon_node/beacon_chain/src/lib.rs @@ -21,6 +21,8 @@ pub mod custody_context; pub mod data_availability_checker; pub mod data_column_verification; mod early_attester_cache; +pub mod envelope_verification; +pub mod envelope_verification_types; mod errors; pub mod events; pub mod execution_payload; diff --git a/consensus/state_processing/src/envelope_processing.rs b/consensus/state_processing/src/envelope_processing.rs new file mode 100644 index 0000000000..aafc6bb3e8 --- /dev/null +++ b/consensus/state_processing/src/envelope_processing.rs @@ -0,0 +1,65 @@ +use crate::per_block_processing::process_operations::{ + process_consolidation_requests, process_deposit_requests, process_withdrawal_requests, +}; +use crate::BlockProcessingError; +use crate::VerifySignatures; +use types::{BeaconState, BeaconStateError, ChainSpec, EthSpec, Hash256, SignedExecutionPayloadEnvelope}; + +#[derive(Debug)] +pub enum EnvelopeProcessingError { + /// Bad Signature + BadSignature, + BeaconStateError(BeaconStateError), + BlockProcessingError(BlockProcessingError), +} + +impl From for EnvelopeProcessingError { + fn from(e: BeaconStateError) -> Self { + EnvelopeProcessingError::BeaconStateError(e) + } +} + +impl From for EnvelopeProcessingError { + fn from(e: BlockProcessingError) -> Self { + EnvelopeProcessingError::BlockProcessingError(e) + } +} + +/// Processes a `SignedExecutionPayloadEnvelope` +/// +/// This function does all the state modifications inside `process_execution_payload()` +pub fn envelope_processing( + state: &mut BeaconState, + signed_envelope: &SignedExecutionPayloadEnvelope, + verify_signatures: VerifySignatures, + spec: &ChainSpec, +) -> Result<(), EnvelopeProcessingError> { + if verify_signatures.is_true() { + // Verify Signed Envelope Signature + if !signed_envelope.verify_signature(&state, spec)? { + return Err(EnvelopeProcessingError::BadSignature); + } + } + + // Cache latest block header state root + let previous_state_root = state.canonical_root()?; + if state.latest_block_header().state_root == Hash256::default() { + state.latest_block_header_mut().state_root = previous_state_root; + } + + // Verify consistency with the beacon block + + // process electra operations + let envelope = signed_envelope.message(); + let payload = envelope.payload(); + let execution_requests = envelope.execution_requests(); + process_deposit_requests(state, &execution_requests.deposits, spec)?; + process_withdrawal_requests(state, &execution_requests.withdrawals, spec)?; + process_consolidation_requests(state, &execution_requests.consolidations, spec)?; + + // cache the latest block hash and full slot + *state.latest_block_hash_mut()? = payload.block_hash(); + + todo!("the rest of process_execution_payload()"); + //Ok(()) +} \ No newline at end of file diff --git a/consensus/state_processing/src/lib.rs b/consensus/state_processing/src/lib.rs index 9b2696c6d5..e37c526579 100644 --- a/consensus/state_processing/src/lib.rs +++ b/consensus/state_processing/src/lib.rs @@ -20,6 +20,7 @@ pub mod all_caches; pub mod block_replayer; pub mod common; pub mod consensus_context; +pub mod envelope_processing; pub mod epoch_cache; pub mod genesis; pub mod per_block_processing; diff --git a/consensus/state_processing/src/per_block_processing/signature_sets.rs b/consensus/state_processing/src/per_block_processing/signature_sets.rs index dafd0d79ea..af1b75a00f 100644 --- a/consensus/state_processing/src/per_block_processing/signature_sets.rs +++ b/consensus/state_processing/src/per_block_processing/signature_sets.rs @@ -12,7 +12,7 @@ use types::{ InconsistentFork, IndexedAttestation, IndexedAttestationRef, ProposerSlashing, PublicKey, PublicKeyBytes, Signature, SignedAggregateAndProof, SignedBeaconBlock, SignedBeaconBlockHeader, SignedBlsToExecutionChange, SignedContributionAndProof, SignedRoot, SignedVoluntaryExit, - SigningData, Slot, SyncAggregate, SyncAggregatorSelectionData, Unsigned, + SigningData, Slot, SyncAggregate, SyncAggregatorSelectionData, Unsigned, SignedExecutionPayloadEnvelope, }; pub type Result = std::result::Result; @@ -331,6 +331,34 @@ where Ok(SignatureSet::multiple_pubkeys(signature, pubkeys, message)) } +pub fn execution_envelope_signature_set<'a, E, F>( + state: &'a BeaconState, + get_pubkey: F, + signed_envelope: &'a SignedExecutionPayloadEnvelope, + spec: &'a ChainSpec, +) -> Result> +where + E: EthSpec, + F: Fn(usize) -> Option>, +{ + let domain = spec.get_domain( + state.current_epoch(), + Domain::BeaconBuilder, + &state.fork(), + state.genesis_validators_root(), + ); + let message = signed_envelope.message().signing_root(domain); + let pubkey = get_pubkey(signed_envelope.message().builder_index() as usize).ok_or( + Error::ValidatorUnknown(signed_envelope.message().builder_index()), + )?; + + Ok(SignatureSet::single_pubkey( + signed_envelope.signature(), + pubkey, + message, + )) +} + /// Returns the signature set for the given `attester_slashing` and corresponding `pubkeys`. pub fn attester_slashing_signature_sets<'a, E, F>( state: &'a BeaconState, diff --git a/consensus/types/src/signed_execution_payload_envelope.rs b/consensus/types/src/signed_execution_payload_envelope.rs index 96276a764b..f8b6e48bce 100644 --- a/consensus/types/src/signed_execution_payload_envelope.rs +++ b/consensus/types/src/signed_execution_payload_envelope.rs @@ -74,6 +74,36 @@ impl SignedExecutionPayloadEnvelope { Self::NextFork(signed) => ExecutionPayloadEnvelopeRef::NextFork(&signed.message), } } + + /// Verify `self.signature`. + /// + /// The `parent_state` is the post-state of the beacon block with + /// block_root = self.message.beacon_block_root + pub fn verify_signature( + &self, + parent_state: &BeaconState, + spec: &ChainSpec, + ) -> Result { + let domain = spec.get_domain( + parent_state.current_epoch(), + Domain::BeaconBuilder, + &parent_state.fork(), + parent_state.genesis_validators_root(), + ); + let pubkey = parent_state + .validators() + .get(self.message().builder_index() as usize) + .and_then(|v| { + let pk: Option = v.pubkey.decompress().ok(); + pk + }) + .ok_or_else(|| { + BeaconStateError::UnknownValidator(self.message().builder_index() as usize) + })?; + let message = self.message().signing_root(domain); + + Ok(self.signature().verify(&pubkey, message)) + } } impl<'de, E: EthSpec> ContextDeserialize<'de, ForkName> for SignedExecutionPayloadEnvelope {