diff --git a/beacon_node/beacon_chain/src/beacon_fork_choice_store.rs b/beacon_node/beacon_chain/src/beacon_fork_choice_store.rs index a6aedda19d..f52becaa8a 100644 --- a/beacon_node/beacon_chain/src/beacon_fork_choice_store.rs +++ b/beacon_node/beacon_chain/src/beacon_fork_choice_store.rs @@ -139,6 +139,9 @@ pub struct BeaconForkChoiceStore, Cold: ItemStore< unrealized_justified_checkpoint: Checkpoint, unrealized_finalized_checkpoint: Checkpoint, proposer_boost_root: Hash256, + payload_withhold_boost_root: Hash256, + payload_withhold_boost_full: bool, + payload_reveal_boost_root: Hash256, equivocating_indices: BTreeSet, _phantom: PhantomData, } @@ -188,6 +191,9 @@ where unrealized_justified_checkpoint: justified_checkpoint, unrealized_finalized_checkpoint: finalized_checkpoint, proposer_boost_root: Hash256::zero(), + payload_withhold_boost_root: Hash256::zero(), + payload_withhold_boost_full: false, + payload_reveal_boost_root: Hash256::zero(), equivocating_indices: BTreeSet::new(), _phantom: PhantomData, }) @@ -205,6 +211,9 @@ where unrealized_justified_checkpoint: self.unrealized_justified_checkpoint, unrealized_finalized_checkpoint: self.unrealized_finalized_checkpoint, proposer_boost_root: self.proposer_boost_root, + payload_withhold_boost_root: self.payload_withhold_boost_root, + payload_withhold_boost_full: self.payload_withhold_boost_full, + payload_reveal_boost_root: self.payload_reveal_boost_root, equivocating_indices: self.equivocating_indices.clone(), } } @@ -226,6 +235,9 @@ where unrealized_justified_checkpoint: persisted.unrealized_justified_checkpoint, unrealized_finalized_checkpoint: persisted.unrealized_finalized_checkpoint, proposer_boost_root: persisted.proposer_boost_root, + payload_withhold_boost_root: persisted.payload_withhold_boost_root, + payload_withhold_boost_full: persisted.payload_withhold_boost_full, + payload_reveal_boost_root: persisted.payload_reveal_boost_root, equivocating_indices: persisted.equivocating_indices, _phantom: PhantomData, }) @@ -281,6 +293,18 @@ where self.proposer_boost_root } + fn payload_withhold_boost_root(&self) -> Hash256 { + self.payload_withhold_boost_root + } + + fn payload_withhold_boost_full(&self) -> bool { + self.payload_withhold_boost_full + } + + fn payload_reveal_boost_root(&self) -> Hash256 { + self.payload_reveal_boost_root + } + fn set_finalized_checkpoint(&mut self, checkpoint: Checkpoint) { self.finalized_checkpoint = checkpoint } @@ -337,6 +361,18 @@ where self.proposer_boost_root = proposer_boost_root; } + fn set_payload_withhold_boost_root(&mut self, payload_withhold_boost_root: Hash256) { + self.payload_withhold_boost_root = payload_withhold_boost_root; + } + + fn set_payload_withhold_boost_full(&mut self, payload_withhold_boost_full: bool) { + self.payload_withhold_boost_full = payload_withhold_boost_full; + } + + fn set_payload_reveal_boost_root(&mut self, payload_reveal_boost_root: Hash256) { + self.payload_reveal_boost_root = payload_reveal_boost_root; + } + fn equivocating_indices(&self) -> &BTreeSet { &self.equivocating_indices } @@ -359,5 +395,9 @@ pub struct PersistedForkChoiceStore { pub unrealized_justified_checkpoint: Checkpoint, pub unrealized_finalized_checkpoint: Checkpoint, pub proposer_boost_root: Hash256, + // TODO(EIP7732): implement db migration + pub payload_withhold_boost_root: Hash256, + pub payload_withhold_boost_full: bool, + pub payload_reveal_boost_root: Hash256, pub equivocating_indices: BTreeSet, } diff --git a/beacon_node/beacon_chain/src/block_verification.rs b/beacon_node/beacon_chain/src/block_verification.rs index add61a9425..3aaaa8c5ec 100644 --- a/beacon_node/beacon_chain/src/block_verification.rs +++ b/beacon_node/beacon_chain/src/block_verification.rs @@ -684,7 +684,8 @@ pub struct SignatureVerifiedBlock { } /// Used to await the result of executing payload with an EE. -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: @@ -1344,9 +1345,10 @@ impl ExecutionPendingBlock { // // We do this as early as possible so that later parts of this function can run in parallel // with the payload verification. + let block_copy = block.block_cloned(); let payload_notifier = PayloadNotifier::new( chain.clone(), - block.block_cloned(), + &block_copy, &parent.pre_state, notify_execution_layer, )?; @@ -1354,7 +1356,7 @@ impl ExecutionPendingBlock { is_merge_transition_block(&parent.pre_state, block.message().body()); let payload_verification_future = async move { let chain = payload_notifier.chain.clone(); - let block = payload_notifier.block.clone(); + let block = block_copy; // If this block triggers the merge, check to ensure that it references valid execution // blocks. 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..84cf1041c0 --- /dev/null +++ b/beacon_node/beacon_chain/src/envelope_verification.rs @@ -0,0 +1,406 @@ +//! 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 +//! | +//! ▼ +//! SignedExecutionEnvelope +//! | +//! |--------------- +//! | | +//! | ▼ +//! | GossipVerifiedEnvelope +//! | | +//! |--------------- +//! | +//! ▼ +//! ExecutionPendingEnvelope +//! | +//! await +//! | +//! ▼ +//! END +//! +//! ``` + +use crate::block_verification::{PayloadVerificationHandle, PayloadVerificationOutcome}; +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::per_block_processing::compute_timestamp_at_slot; +use std::sync::Arc; +use tree_hash::TreeHash; +use types::{ + BeaconState, BeaconStateError, EthSpec, ExecutionBlockHash, Hash256, SignedBlindedBeaconBlock, + SignedExecutionEnvelope, +}; + +// TODO(EIP7732): don't use this redefinition.. +macro_rules! block_verify { + ($condition: expr, $result: expr) => { + if !$condition { + return Err($result); + } + }; +} + +// TODO: finish this properly +pub type MaybeAvailableEnvelope = Arc>; + +#[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, + }, + // 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), +} + +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) + } +} + +/// A wrapper around a `SignedBeaconBlock` 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 signed_block: Arc>, + pub pre_state: Box>, + /* + parent: Option>, + consensus_context: ConsensusContext, + */ +} + +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 signed_block = chain + .get_blinded_block(&block_root)? + .ok_or_else(|| EnvelopeError::from(BeaconChainError::MissingBeaconBlock(block_root))) + .map(Arc::new)?; + let execution_bid = &signed_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( + &signed_block.message().state_root(), + Some(signed_block.slot()), + )? + .ok_or_else(|| { + EnvelopeError::from(BeaconChainError::MissingBeaconState( + signed_block.message().state_root(), + )) + })?; + + // verify the signature + if signed_envelope.verify_signature( + &parent_state, + chain.genesis_validators_root, + &chain.spec, + )? { + return Err(EnvelopeError::BadSignature); + } + + Ok(Self { + signed_envelope, + signed_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 signed_block: Arc>, + pub pre_state: Box>, + 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 done + + let mut state = *self.pre_state; + 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 + 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 + block_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 + block_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 + block_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 + block_verify!( + payload.parent_hash() == state.latest_block_hash()?, + EnvelopeError::ParentHashMismatch { + state: state.latest_block_hash()?, + envelope: payload.parent_hash(), + } + .into() + ); + + // Verify prev_randao + block_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())?; + block_verify!( + payload.timestamp() == state_timestamp, + EnvelopeError::TimestampMismatch { + state: state_timestamp, + envelope: payload.timestamp(), + } + .into() + ); + + // Verify the commitments are under limit + block_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.signed_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)?; + + // TODO(EIP7732): process electra operations + + Ok(ExecutionPendingEnvelope { + signed_envelope, + pre_state: Box::new(state), + signed_block: self.signed_block, + payload_verification_handle, + }) + } +} diff --git a/beacon_node/beacon_chain/src/execution_payload.rs b/beacon_node/beacon_chain/src/execution_payload.rs index b4aa2ff781..f1881ebad8 100644 --- a/beacon_node/beacon_chain/src/execution_payload.rs +++ b/beacon_node/beacon_chain/src/execution_payload.rs @@ -10,7 +10,7 @@ use crate::otb_verification_service::OptimisticTransitionBlock; use crate::{ BeaconChain, BeaconChainError, BeaconChainTypes, BlockError, BlockProductionError, - ExecutionPayloadError, + EnvelopeError, ExecutionPayloadError, }; use execution_layer::{ BlockProposalContents, BlockProposalContentsType, BuilderParams, NewPayloadRequest, @@ -26,7 +26,6 @@ use state_processing::per_block_processing::{ }; use std::sync::Arc; use tokio::task::JoinHandle; -use tree_hash::TreeHash; use types::payload::BlockProductionVersion; use types::*; @@ -52,18 +51,24 @@ pub enum NotifyExecutionLayer { /// Used to await the result of executing payload with a remote EE. pub struct PayloadNotifier { pub chain: Arc>, - pub block: Arc>, - payload_verification_status: Option, + pub parent_root: Hash256, + pub payload_verification_state: PayloadVerificationState, } -impl PayloadNotifier { +pub enum PayloadVerificationState { + PreComputed(PayloadVerificationStatus), + Request(NewPayloadRequest), +} + +impl<'block, T: BeaconChainTypes> PayloadNotifier { pub fn new( chain: Arc>, - block: Arc>, + block: &SignedBeaconBlock, state: &BeaconState, notify_execution_layer: NotifyExecutionLayer, ) -> Result { - let payload_verification_status = if is_execution_enabled(state, block.message().body()) { + let parent_root = block.parent_root(); + let payload_verification_state = if is_execution_enabled(state, block.message().body()) { // Perform the initial stages of payload verification. // // We will duplicate these checks again during `per_block_processing`, however these @@ -78,12 +83,11 @@ impl PayloadNotifier { &chain.spec, ) .map_err(BlockError::PerBlockProcessingError)?; + let new_payload_request: NewPayloadRequest = block_message.try_into()?; 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 = - block_message.try_into()?; + // check optimistic sync verifications if let Err(e) = new_payload_request.perform_optimistic_sync_verifications() { warn!( chain.log, @@ -92,29 +96,68 @@ impl PayloadNotifier { "info" => "you can silence this warning with --disable-optimistic-finalized-sync", "error" => ?e, ); - None + PayloadVerificationState::Request(new_payload_request) } else { - Some(PayloadVerificationStatus::Optimistic) + PayloadVerificationState::PreComputed(PayloadVerificationStatus::Optimistic) } } - _ => None, + _ => PayloadVerificationState::Request(new_payload_request), } } else { - Some(PayloadVerificationStatus::Irrelevant) + PayloadVerificationState::PreComputed(PayloadVerificationStatus::Irrelevant) }; Ok(Self { chain, - block, - payload_verification_status, + parent_root, + payload_verification_state, + }) + } + + pub fn from_envelope( + chain: Arc>, + envelope: ExecutionEnvelopeRef, + notify_execution_layer: NotifyExecutionLayer, + ) -> Result { + let parent_root = envelope.beacon_block_root(); + let new_payload_request: NewPayloadRequest = envelope.try_into()?; + + let payload_verification_state = if !envelope.payload_withheld() { + match notify_execution_layer { + NotifyExecutionLayer::No if chain.config.optimistic_finalized_sync => { + // check optimistic sync verifications + if let Err(e) = new_payload_request.perform_optimistic_sync_verifications() { + warn!( + chain.log, + "Falling back to slow block hash verification"; + "block_number" => envelope.payload().block_number(), + "info" => "you can silence this warning with --disable-optimistic-finalized-sync", + "error" => ?e, + ); + PayloadVerificationState::Request(new_payload_request) + } else { + PayloadVerificationState::PreComputed(PayloadVerificationStatus::Optimistic) + } + } + _ => PayloadVerificationState::Request(new_payload_request), + } + } else { + PayloadVerificationState::PreComputed(PayloadVerificationStatus::Irrelevant) + }; + + Ok(Self { + chain, + parent_root, + payload_verification_state, }) } pub async fn notify_new_payload(self) -> Result { - if let Some(precomputed_status) = self.payload_verification_status { - Ok(precomputed_status) - } else { - notify_new_payload(&self.chain, self.block.message()).await + match self.payload_verification_state { + PayloadVerificationState::Request(request) => { + notify_new_payload(&self.chain, request, self.parent_root).await + } + PayloadVerificationState::PreComputed(status) => Ok(status), } } } @@ -128,17 +171,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<'a, T: BeaconChainTypes>( +async fn notify_new_payload( chain: &Arc>, - block: BeaconBlockRef<'a, T::EthSpec>, + request: NewPayloadRequest, + parent_root: Hash256, ) -> 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 = request.block_hash(); + let new_payload_response = execution_layer.notify_new_payload(request).await; match new_payload_response { Ok(status) => match status { @@ -156,10 +200,13 @@ async fn notify_new_payload<'a, T: BeaconChainTypes>( "validation_error" => ?validation_error, "latest_valid_hash" => ?latest_valid_hash, "execution_block_hash" => ?execution_block_hash, + /* + // EIP-7732 - none of this stuff is available in the envelope.. is it worth it? "root" => ?block.tree_hash_root(), "graffiti" => block.body().graffiti().as_utf8_lossy(), "proposer_index" => block.proposer_index(), "slot" => block.slot(), + */ "method" => "new_payload", ); @@ -181,8 +228,7 @@ async fn notify_new_payload<'a, T: BeaconChainTypes>( { // 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 = parent_root; chain .process_invalid_execution_payload(&InvalidationOperation::InvalidateMany { head_block_root: latest_root, @@ -202,10 +248,13 @@ async fn notify_new_payload<'a, T: BeaconChainTypes>( "Invalid execution payload block hash"; "validation_error" => ?validation_error, "execution_block_hash" => ?execution_block_hash, + /* + // Again this stuff isn't available in the envelope "root" => ?block.tree_hash_root(), "graffiti" => block.body().graffiti().as_utf8_lossy(), "proposer_index" => block.proposer_index(), "slot" => block.slot(), + */ "method" => "new_payload", ); diff --git a/beacon_node/beacon_chain/src/lib.rs b/beacon_node/beacon_chain/src/lib.rs index 2953516fb1..adf625973c 100644 --- a/beacon_node/beacon_chain/src/lib.rs +++ b/beacon_node/beacon_chain/src/lib.rs @@ -23,6 +23,7 @@ pub mod data_column_verification; pub mod deneb_readiness; mod early_attester_cache; pub mod electra_readiness; +pub mod envelope_verification; mod errors; pub mod eth1_chain; mod eth1_finalization_cache; @@ -83,6 +84,7 @@ pub use block_verification::{ pub use block_verification_types::AvailabilityPendingExecutedBlock; pub use block_verification_types::ExecutedBlock; pub use canonical_head::{CachedHead, CanonicalHead, CanonicalHeadRwLock}; +pub use envelope_verification::{EnvelopeError, ExecutionPendingEnvelope, GossipVerifiedEnvelope}; pub use eth1_chain::{Eth1Chain, Eth1ChainBackend}; pub use events::ServerSentEventHandler; pub use execution_layer::EngineState; diff --git a/beacon_node/execution_layer/src/engine_api/http.rs b/beacon_node/execution_layer/src/engine_api/http.rs index eaac313768..14656af187 100644 --- a/beacon_node/execution_layer/src/engine_api/http.rs +++ b/beacon_node/execution_layer/src/engine_api/http.rs @@ -784,7 +784,7 @@ impl HttpJsonRpc { pub async fn new_payload_v3_deneb( &self, - new_payload_request_deneb: NewPayloadRequestDeneb<'_, E>, + new_payload_request_deneb: NewPayloadRequestDeneb, ) -> Result { let params = json!([ JsonExecutionPayload::V3(new_payload_request_deneb.execution_payload.clone().into()), @@ -805,7 +805,7 @@ impl HttpJsonRpc { pub async fn new_payload_v4_electra( &self, - new_payload_request_electra: NewPayloadRequestElectra<'_, E>, + new_payload_request_electra: NewPayloadRequestElectra, ) -> Result { let params = json!([ JsonExecutionPayload::V4(new_payload_request_electra.execution_payload.clone().into()), @@ -1191,7 +1191,7 @@ impl HttpJsonRpc { // new_payload that the execution engine supports pub async fn new_payload( &self, - new_payload_request: NewPayloadRequest<'_, E>, + new_payload_request: NewPayloadRequest, ) -> Result { let engine_capabilities = self.get_engine_capabilities(None).await?; match new_payload_request { @@ -1221,6 +1221,7 @@ impl HttpJsonRpc { Err(Error::RequiredMethodUnsupported("engine_newPayloadV4")) } } + NewPayloadRequest::EIP7732(_) => todo!("EIP-7732 Engine API new_payload"), } } 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 d19f52b7ca..134d242bbe 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,8 @@ 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, ExecutionEnvelopeRef, + ExecutionPayload, ExecutionPayloadEIP7732, ExecutionPayloadRef, Hash256, VersionedHash, }; use types::{ ExecutionPayloadBellatrix, ExecutionPayloadCapella, ExecutionPayloadDeneb, @@ -13,7 +13,7 @@ use types::{ }; #[superstruct( - variants(Bellatrix, Capella, Deneb, Electra), + variants(Bellatrix, Capella, Deneb, Electra, EIP7732), variant_attributes(derive(Clone, Debug, PartialEq),), map_into(ExecutionPayload), map_ref_into(ExecutionPayloadRef), @@ -27,33 +27,36 @@ use types::{ ) )] #[derive(Clone, Debug, PartialEq)] -pub struct NewPayloadRequest<'block, E: EthSpec> { +pub struct NewPayloadRequest { #[superstruct( only(Bellatrix), partial_getter(rename = "execution_payload_bellatrix") )] - pub execution_payload: &'block ExecutionPayloadBellatrix, + pub execution_payload: ExecutionPayloadBellatrix, #[superstruct(only(Capella), partial_getter(rename = "execution_payload_capella"))] - pub execution_payload: &'block ExecutionPayloadCapella, + pub execution_payload: ExecutionPayloadCapella, #[superstruct(only(Deneb), partial_getter(rename = "execution_payload_deneb"))] - pub execution_payload: &'block ExecutionPayloadDeneb, + pub execution_payload: ExecutionPayloadDeneb, #[superstruct(only(Electra), partial_getter(rename = "execution_payload_electra"))] - pub execution_payload: &'block ExecutionPayloadElectra, - #[superstruct(only(Deneb, Electra))] + pub execution_payload: ExecutionPayloadElectra, + #[superstruct(only(EIP7732), partial_getter(rename = "execution_payload_eip7732"))] + pub execution_payload: ExecutionPayloadEIP7732, + #[superstruct(only(Deneb, Electra, EIP7732))] pub versioned_hashes: Vec, - #[superstruct(only(Deneb, Electra))] + #[superstruct(only(Deneb, Electra, EIP7732))] pub parent_beacon_block_root: Hash256, - #[superstruct(only(Electra))] - pub execution_requests_list: &'block ExecutionRequests, + #[superstruct(only(Electra, EIP7732))] + pub execution_requests_list: ExecutionRequests, } -impl<'block, E: EthSpec> NewPayloadRequest<'block, E> { +impl NewPayloadRequest { pub fn parent_hash(&self) -> ExecutionBlockHash { match self { Self::Bellatrix(payload) => payload.execution_payload.parent_hash, Self::Capella(payload) => payload.execution_payload.parent_hash, Self::Deneb(payload) => payload.execution_payload.parent_hash, Self::Electra(payload) => payload.execution_payload.parent_hash, + Self::EIP7732(payload) => payload.execution_payload.parent_hash, } } @@ -63,6 +66,7 @@ impl<'block, E: EthSpec> NewPayloadRequest<'block, E> { Self::Capella(payload) => payload.execution_payload.block_hash, Self::Deneb(payload) => payload.execution_payload.block_hash, Self::Electra(payload) => payload.execution_payload.block_hash, + Self::EIP7732(payload) => payload.execution_payload.block_hash, } } @@ -72,15 +76,17 @@ impl<'block, E: EthSpec> NewPayloadRequest<'block, E> { Self::Capella(payload) => payload.execution_payload.block_number, Self::Deneb(payload) => payload.execution_payload.block_number, Self::Electra(payload) => payload.execution_payload.block_number, + Self::EIP7732(payload) => payload.execution_payload.block_number, } } - pub fn execution_payload_ref(&self) -> ExecutionPayloadRef<'block, E> { + pub fn execution_payload_ref(&self) -> ExecutionPayloadRef { match self { - Self::Bellatrix(request) => ExecutionPayloadRef::Bellatrix(request.execution_payload), - Self::Capella(request) => ExecutionPayloadRef::Capella(request.execution_payload), - Self::Deneb(request) => ExecutionPayloadRef::Deneb(request.execution_payload), - Self::Electra(request) => ExecutionPayloadRef::Electra(request.execution_payload), + Self::Bellatrix(request) => ExecutionPayloadRef::Bellatrix(&request.execution_payload), + Self::Capella(request) => ExecutionPayloadRef::Capella(&request.execution_payload), + Self::Deneb(request) => ExecutionPayloadRef::Deneb(&request.execution_payload), + Self::Electra(request) => ExecutionPayloadRef::Electra(&request.execution_payload), + Self::EIP7732(request) => ExecutionPayloadRef::EIP7732(&request.execution_payload), } } @@ -89,9 +95,10 @@ impl<'block, E: EthSpec> NewPayloadRequest<'block, E> { Self::Bellatrix(request) => { ExecutionPayload::Bellatrix(request.execution_payload.clone()) } - Self::Capella(request) => ExecutionPayload::Capella(request.execution_payload.clone()), - Self::Deneb(request) => ExecutionPayload::Deneb(request.execution_payload.clone()), - Self::Electra(request) => ExecutionPayload::Electra(request.execution_payload.clone()), + Self::Capella(request) => ExecutionPayload::Capella(request.execution_payload), + Self::Deneb(request) => ExecutionPayload::Deneb(request.execution_payload), + Self::Electra(request) => ExecutionPayload::Electra(request.execution_payload), + Self::EIP7732(request) => ExecutionPayload::EIP7732(request.execution_payload), } } @@ -150,7 +157,8 @@ impl<'block, E: EthSpec> NewPayloadRequest<'block, E> { } } -impl<'a, E: EthSpec> TryFrom> for NewPayloadRequest<'a, E> { +//TODO(EIP7732): Consider implmenting these as methods on the NewPayloadRequest struct +impl<'a, E: EthSpec> TryFrom> for NewPayloadRequest { type Error = BeaconStateError; fn try_from(block: BeaconBlockRef<'a, E>) -> Result { @@ -160,14 +168,14 @@ impl<'a, E: EthSpec> TryFrom> for NewPayloadRequest<'a, E> } BeaconBlockRef::Bellatrix(block_ref) => { Ok(Self::Bellatrix(NewPayloadRequestBellatrix { - execution_payload: &block_ref.body.execution_payload.execution_payload, + execution_payload: block_ref.body.execution_payload.execution_payload.clone(), })) } BeaconBlockRef::Capella(block_ref) => Ok(Self::Capella(NewPayloadRequestCapella { - execution_payload: &block_ref.body.execution_payload.execution_payload, + execution_payload: block_ref.body.execution_payload.execution_payload.clone(), })), BeaconBlockRef::Deneb(block_ref) => Ok(Self::Deneb(NewPayloadRequestDeneb { - execution_payload: &block_ref.body.execution_payload.execution_payload, + execution_payload: block_ref.body.execution_payload.execution_payload.clone(), versioned_hashes: block_ref .body .blob_kzg_commitments @@ -177,7 +185,7 @@ impl<'a, E: EthSpec> TryFrom> for NewPayloadRequest<'a, E> parent_beacon_block_root: block_ref.parent_root, })), BeaconBlockRef::Electra(block_ref) => Ok(Self::Electra(NewPayloadRequestElectra { - execution_payload: &block_ref.body.execution_payload.execution_payload, + execution_payload: block_ref.body.execution_payload.execution_payload.clone(), versioned_hashes: block_ref .body .blob_kzg_commitments @@ -185,26 +193,47 @@ impl<'a, E: EthSpec> TryFrom> for NewPayloadRequest<'a, E> .map(kzg_commitment_to_versioned_hash) .collect(), parent_beacon_block_root: block_ref.parent_root, - execution_requests_list: &block_ref.body.execution_requests, + execution_requests_list: block_ref.body.execution_requests.clone(), })), - //TODO(EIP7732): Need new method of constructing NewPayloadRequest BeaconBlockRef::EIP7732(_) => Err(Self::Error::IncorrectStateVariant), } } } -impl<'a, E: EthSpec> TryFrom> for NewPayloadRequest<'a, E> { +impl<'a, E: EthSpec> TryFrom> for NewPayloadRequest { + type Error = BeaconStateError; + + fn try_from(envelope: ExecutionEnvelopeRef<'a, E>) -> Result { + match envelope { + ExecutionEnvelopeRef::EIP7732(envelope) => { + Ok(Self::EIP7732(NewPayloadRequestEIP7732 { + execution_payload: envelope.payload.clone(), + versioned_hashes: envelope + .blob_kzg_commitments + .iter() + .map(kzg_commitment_to_versioned_hash) + .collect(), + parent_beacon_block_root: envelope.beacon_block_root, + execution_requests_list: envelope.execution_requests.clone(), + })) + } + ExecutionEnvelopeRef::NextFork(_) => Err(Self::Error::IncorrectStateVariant), + } + } +} + +impl<'a, E: EthSpec> TryFrom> for NewPayloadRequest { type Error = BeaconStateError; fn try_from(payload: ExecutionPayloadRef<'a, E>) -> Result { match payload { ExecutionPayloadRef::Bellatrix(payload) => { Ok(Self::Bellatrix(NewPayloadRequestBellatrix { - execution_payload: payload, + execution_payload: payload.clone(), })) } ExecutionPayloadRef::Capella(payload) => Ok(Self::Capella(NewPayloadRequestCapella { - execution_payload: payload, + execution_payload: payload.clone(), })), ExecutionPayloadRef::Deneb(_) => Err(Self::Error::IncorrectStateVariant), ExecutionPayloadRef::Electra(_) => Err(Self::Error::IncorrectStateVariant), diff --git a/beacon_node/execution_layer/src/lib.rs b/beacon_node/execution_layer/src/lib.rs index 43305cce13..0c51d9451c 100644 --- a/beacon_node/execution_layer/src/lib.rs +++ b/beacon_node/execution_layer/src/lib.rs @@ -1358,7 +1358,7 @@ impl ExecutionLayer { /// Maps to the `engine_newPayload` JSON-RPC call. pub async fn notify_new_payload( &self, - new_payload_request: NewPayloadRequest<'_, E>, + new_payload_request: NewPayloadRequest, ) -> Result { let _timer = metrics::start_timer_vec( &metrics::EXECUTION_LAYER_REQUEST_TIMES, diff --git a/consensus/fork_choice/src/fork_choice_store.rs b/consensus/fork_choice/src/fork_choice_store.rs index 27f3d34dbc..7d6465c4dc 100644 --- a/consensus/fork_choice/src/fork_choice_store.rs +++ b/consensus/fork_choice/src/fork_choice_store.rs @@ -59,6 +59,15 @@ pub trait ForkChoiceStore: Sized { /// Returns the `proposer_boost_root`. fn proposer_boost_root(&self) -> Hash256; + /// Returns the `payload_withhold_boost_root`. + fn payload_withhold_boost_root(&self) -> Hash256; + + /// Returns the `payload_withhold_boost_full`. + fn payload_withhold_boost_full(&self) -> bool; + + /// Returns the `payload_reveal_boost_root`. + fn payload_reveal_boost_root(&self) -> Hash256; + /// Sets `finalized_checkpoint`. fn set_finalized_checkpoint(&mut self, checkpoint: Checkpoint); @@ -74,6 +83,15 @@ pub trait ForkChoiceStore: Sized { /// Sets the proposer boost root. fn set_proposer_boost_root(&mut self, proposer_boost_root: Hash256); + /// Sets the payload withhold boost root. + fn set_payload_withhold_boost_root(&mut self, payload_withhold_boost_root: Hash256); + + /// Sets the payload withhold boost full. + fn set_payload_withhold_boost_full(&mut self, payload_withhold_boost_full: bool); + + /// Sets the payload reveal boost root. + fn set_payload_reveal_boost_root(&mut self, payload_reveal_boost_root: Hash256); + /// Gets the equivocating indices. fn equivocating_indices(&self) -> &BTreeSet; diff --git a/consensus/state_processing/src/execution_processing.rs b/consensus/state_processing/src/execution_processing.rs deleted file mode 100644 index a8ceff0fff..0000000000 --- a/consensus/state_processing/src/execution_processing.rs +++ /dev/null @@ -1,136 +0,0 @@ -use super::signature_sets::{execution_envelope_signature_set, get_pubkey_from_state}; -use crate::per_block_processing::compute_timestamp_at_slot; -use crate::per_block_processing::errors::{BlockProcessingError, ExecutionEnvelopeError}; -use crate::VerifySignatures; -use tree_hash::TreeHash; -use types::{BeaconState, ChainSpec, EthSpec, Hash256, SignedExecutionEnvelope}; - -pub fn process_execution_envelope( - state: &mut BeaconState, - signed_envelope: SignedExecutionEnvelope, - spec: &ChainSpec, - verify_signatures: VerifySignatures, -) -> Result<(), BlockProcessingError> { - if verify_signatures.is_true() { - block_verify!( - execution_envelope_signature_set( - state, - |i| get_pubkey_from_state(state, i), - &signed_envelope, - spec - )? - .verify(), - ExecutionEnvelopeError::BadSignature.into() - ) - } - - let envelope = signed_envelope.message(); - let payload = &envelope.payload; - 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 - block_verify!( - envelope.tree_hash_root() == state.latest_block_header().tree_hash_root(), - ExecutionEnvelopeError::LatestBlockHeaderMismatch { - envelope_root: envelope.tree_hash_root(), - block_header_root: state.latest_block_header().tree_hash_root(), - } - .into() - ); - - // Verify consistency with the committed bid - let committed_bid = state.latest_execution_bid()?; - block_verify!( - envelope.builder_index == committed_bid.builder_index, - ExecutionEnvelopeError::BuilderIndexMismatch { - committed_bid: committed_bid.builder_index, - envelope: envelope.builder_index, - } - .into() - ); - block_verify!( - committed_bid.blob_kzg_commitments_root == envelope.blob_kzg_commitments.tree_hash_root(), - ExecutionEnvelopeError::BlobKzgCommitmentsRootMismatch { - committed_bid: committed_bid.blob_kzg_commitments_root, - envelope: envelope.blob_kzg_commitments.tree_hash_root(), - } - .into() - ); - - if !envelope.payment_withheld { - // Verify the withdrawals root - block_verify!( - payload.withdrawals.tree_hash_root() == state.latest_withdrawals_root()?, - ExecutionEnvelopeError::WithdrawalsRootMismatch { - state: state.latest_withdrawals_root()?, - envelope: payload.withdrawals.tree_hash_root(), - } - .into() - ); - - // Verify the gas limit - block_verify!( - payload.gas_limit == committed_bid.gas_limit, - ExecutionEnvelopeError::GasLimitMismatch { - committed_bid: committed_bid.gas_limit, - envelope: payload.gas_limit, - } - .into() - ); - - block_verify!( - committed_bid.block_hash == payload.block_hash, - ExecutionEnvelopeError::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 - block_verify!( - payload.parent_hash == state.latest_block_hash()?, - ExecutionEnvelopeError::ParentHashMismatch { - state: state.latest_block_hash()?, - envelope: payload.parent_hash, - } - .into() - ); - - // Verify prev_randao - block_verify!( - payload.prev_randao == *state.get_randao_mix(state.current_epoch())?, - ExecutionEnvelopeError::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(), spec)?; - block_verify!( - payload.timestamp == state_timestamp, - ExecutionEnvelopeError::TimestampMismatch { - state: state_timestamp, - envelope: payload.timestamp, - } - .into() - ); - - // Verify the commitments are under limit - block_verify!( - envelope.blob_kzg_commitments.len() <= E::max_blob_commitments_per_block(), - ExecutionEnvelopeError::BlobLimitExceeded { - max: E::max_blob_commitments_per_block(), - envelope: envelope.blob_kzg_commitments.len(), - } - .into() - ); - } - - Ok(()) -} diff --git a/consensus/state_processing/src/lib.rs b/consensus/state_processing/src/lib.rs index 075d8410be..adabf6862d 100644 --- a/consensus/state_processing/src/lib.rs +++ b/consensus/state_processing/src/lib.rs @@ -21,7 +21,6 @@ pub mod block_replayer; pub mod common; pub mod consensus_context; pub mod epoch_cache; -pub mod execution_processing; pub mod genesis; pub mod per_block_processing; pub mod per_epoch_processing; @@ -33,7 +32,6 @@ pub mod verify_operation; pub use all_caches::AllCaches; pub use block_replayer::{BlockReplayError, BlockReplayer}; pub use consensus_context::{ConsensusContext, ContextError}; -pub use execution_processing::process_execution_envelope; pub use genesis::{ eth2_genesis_time, initialize_beacon_state_from_eth1, is_valid_genesis_state, process_activations, diff --git a/consensus/state_processing/src/per_block_processing/errors.rs b/consensus/state_processing/src/per_block_processing/errors.rs index 5ceff37763..e98181b920 100644 --- a/consensus/state_processing/src/per_block_processing/errors.rs +++ b/consensus/state_processing/src/per_block_processing/errors.rs @@ -63,7 +63,6 @@ pub enum BlockProcessingError { ExecutionBidInvalid { reason: ExecutionBidInvalid, }, - ExecutionEnvelopeError(ExecutionEnvelopeError), BeaconStateError(BeaconStateError), SignatureSetError(SignatureSetError), SszTypesError(ssz_types::Error), @@ -160,12 +159,6 @@ impl From for BlockProcessingError { } } -impl From for BlockProcessingError { - fn from(e: ExecutionEnvelopeError) -> Self { - BlockProcessingError::ExecutionEnvelopeError(e) - } -} - impl From> for BlockProcessingError { fn from(e: BlockOperationError) -> BlockProcessingError { match e { @@ -548,59 +541,3 @@ pub enum ExecutionBidInvalid { bid_parent_root: Hash256, }, } - -#[derive(Debug, PartialEq, Clone)] -pub enum ExecutionEnvelopeError { - /// 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, - }, -} 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 5eefb7d0f4..0305ae2345 100644 --- a/consensus/state_processing/src/per_block_processing/signature_sets.rs +++ b/consensus/state_processing/src/per_block_processing/signature_sets.rs @@ -406,8 +406,8 @@ where 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), + let pubkey = get_pubkey(signed_envelope.message().builder_index() as usize).ok_or( + Error::ValidatorUnknown(signed_envelope.message().builder_index()), )?; Ok(SignatureSet::single_pubkey( diff --git a/consensus/types/src/execution_envelope.rs b/consensus/types/src/execution_envelope.rs index 58e74f7d1a..2206495809 100644 --- a/consensus/types/src/execution_envelope.rs +++ b/consensus/types/src/execution_envelope.rs @@ -8,8 +8,9 @@ use superstruct::superstruct; use test_random_derive::TestRandom; use tree_hash_derive::TreeHash; +// in all likelihood, this will be superstructed so might as well start early eh? #[superstruct( - variants(EIP7732), + variants(EIP7732, NextFork), variant_attributes( derive( Debug, @@ -27,6 +28,10 @@ use tree_hash_derive::TreeHash; serde(bound = "E: EthSpec", deny_unknown_fields), arbitrary(bound = "E: EthSpec") ), + ref_attributes( + derive(Debug, PartialEq, TreeHash), + tree_hash(enum_behaviour = "transparent") + ), cast_error(ty = "Error", expr = "BeaconStateError::IncorrectStateVariant"), partial_getter_error(ty = "Error", expr = "BeaconStateError::IncorrectStateVariant") )] @@ -41,12 +46,32 @@ use tree_hash_derive::TreeHash; pub struct ExecutionEnvelope { #[superstruct(only(EIP7732), partial_getter(rename = "payload_eip7732"))] pub payload: ExecutionPayloadEIP7732, + #[superstruct(only(NextFork), partial_getter(rename = "payload_next_fork"))] + pub payload: ExecutionPayloadEIP7732, + pub execution_requests: ExecutionRequests, #[serde(with = "serde_utils::quoted_u64")] + #[superstruct(getter(copy))] pub builder_index: u64, + #[superstruct(getter(copy))] pub beacon_block_root: Hash256, pub blob_kzg_commitments: KzgCommitments, - pub payment_withheld: bool, + #[superstruct(getter(copy))] + pub payload_withheld: bool, + #[superstruct(getter(copy))] pub state_root: Hash256, } -impl SignedRoot for ExecutionEnvelopeEIP7732 {} +impl<'a, E: EthSpec> SignedRoot for ExecutionEnvelopeRef<'a, E> {} + +impl<'a, E: EthSpec> ExecutionEnvelopeRef<'a, E> { + pub fn payload(&self) -> ExecutionPayloadRef<'a, E> { + match self { + ExecutionEnvelopeRef::EIP7732(envelope) => { + ExecutionPayloadRef::EIP7732(&envelope.payload) + } + ExecutionEnvelopeRef::NextFork(envelope) => { + ExecutionPayloadRef::EIP7732(&envelope.payload) + } + } + } +} diff --git a/consensus/types/src/lib.rs b/consensus/types/src/lib.rs index afda77ddfb..1ad6ec256e 100644 --- a/consensus/types/src/lib.rs +++ b/consensus/types/src/lib.rs @@ -172,7 +172,9 @@ pub use crate::eth_spec::EthSpecId; pub use crate::execution_bid::ExecutionBid; pub use crate::execution_block_hash::ExecutionBlockHash; pub use crate::execution_block_header::{EncodableExecutionBlockHeader, ExecutionBlockHeader}; -pub use crate::execution_envelope::{ExecutionEnvelope, ExecutionEnvelopeEIP7732}; +pub use crate::execution_envelope::{ + ExecutionEnvelope, ExecutionEnvelopeEIP7732, ExecutionEnvelopeRef, +}; pub use crate::execution_payload::{ ExecutionPayload, ExecutionPayloadBellatrix, ExecutionPayloadCapella, ExecutionPayloadDeneb, ExecutionPayloadEIP7732, ExecutionPayloadElectra, ExecutionPayloadRef, Transaction, diff --git a/consensus/types/src/signed_execution_envelope.rs b/consensus/types/src/signed_execution_envelope.rs index 9781b74078..9feaca022b 100644 --- a/consensus/types/src/signed_execution_envelope.rs +++ b/consensus/types/src/signed_execution_envelope.rs @@ -7,8 +7,9 @@ use superstruct::superstruct; use test_random_derive::TestRandom; use tree_hash_derive::TreeHash; +// in all likelihood, this will be superstructed so might as well start early eh? #[superstruct( - variants(EIP7732), + variants(EIP7732, NextFork), variant_attributes( derive( Debug, @@ -40,5 +41,51 @@ use tree_hash_derive::TreeHash; pub struct SignedExecutionEnvelope { #[superstruct(only(EIP7732), partial_getter(rename = "message_eip7732"))] pub message: ExecutionEnvelopeEIP7732, + #[superstruct(only(NextFork), partial_getter(rename = "message_next_fork"))] + pub message: crate::execution_envelope::ExecutionEnvelopeNextFork, pub signature: Signature, } + +impl SignedExecutionEnvelope { + pub fn message(&self) -> ExecutionEnvelopeRef { + match self { + SignedExecutionEnvelope::EIP7732(ref signed) => { + ExecutionEnvelopeRef::EIP7732(&signed.message) + } + SignedExecutionEnvelope::NextFork(ref signed) => { + ExecutionEnvelopeRef::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, + genesis_validators_root: Hash256, + spec: &ChainSpec, + ) -> Result { + let domain = spec.get_domain( + parent_state.current_epoch(), + Domain::BeaconBuilder, + &parent_state.fork(), + 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)) + } +}