mirror of
https://github.com/sigp/lighthouse.git
synced 2026-05-07 00:42:42 +00:00
payload verification with commitments
This commit is contained in:
@@ -11,7 +11,6 @@ use kzg::Kzg;
|
||||
use slog::{debug, error};
|
||||
use slot_clock::SlotClock;
|
||||
use ssz_types::{Error, FixedVector, VariableList};
|
||||
use state_processing::per_block_processing::deneb::deneb::verify_kzg_commitments_against_transactions;
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
use strum::IntoStaticStr;
|
||||
@@ -21,7 +20,7 @@ use types::blob_sidecar::{BlobIdentifier, BlobSidecar, FixedBlobSidecarList};
|
||||
use types::consts::deneb::MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS;
|
||||
use types::ssz_tagged_signed_beacon_block;
|
||||
use types::{
|
||||
BeaconBlockRef, BlobSidecarList, ChainSpec, Epoch, EthSpec, ExecPayload, FullPayload, Hash256,
|
||||
BeaconBlockRef, BlobSidecarList, ChainSpec, Epoch, EthSpec, FullPayload, Hash256,
|
||||
SignedBeaconBlock, SignedBeaconBlockHeader, Slot,
|
||||
};
|
||||
|
||||
@@ -291,35 +290,20 @@ impl<T: BeaconChainTypes> DataAvailabilityChecker<T> {
|
||||
&self,
|
||||
block: &Arc<SignedBeaconBlock<T::EthSpec, FullPayload<T::EthSpec>>>,
|
||||
) -> Result<BlobRequirements, AvailabilityCheckError> {
|
||||
let verified_blobs = if let (Ok(block_kzg_commitments), Ok(payload)) = (
|
||||
block.message().body().blob_kzg_commitments(),
|
||||
block.message().body().execution_payload(),
|
||||
) {
|
||||
if let Some(transactions) = payload.transactions() {
|
||||
let verified = verify_kzg_commitments_against_transactions::<T::EthSpec>(
|
||||
transactions,
|
||||
block_kzg_commitments,
|
||||
)
|
||||
.map_err(|e| AvailabilityCheckError::TxKzgCommitmentMismatch(format!("{e:?}")))?;
|
||||
if !verified {
|
||||
return Err(AvailabilityCheckError::TxKzgCommitmentMismatch(
|
||||
"a commitment and version didn't match".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if self.da_check_required(block.epoch()) {
|
||||
if block_kzg_commitments.is_empty() {
|
||||
BlobRequirements::EmptyBlobs
|
||||
let verified_blobs =
|
||||
if let Ok(block_kzg_commitments) = block.message().body().blob_kzg_commitments() {
|
||||
if self.da_check_required(block.epoch()) {
|
||||
if block_kzg_commitments.is_empty() {
|
||||
BlobRequirements::EmptyBlobs
|
||||
} else {
|
||||
BlobRequirements::Required
|
||||
}
|
||||
} else {
|
||||
BlobRequirements::Required
|
||||
BlobRequirements::NotRequired
|
||||
}
|
||||
} else {
|
||||
BlobRequirements::NotRequired
|
||||
}
|
||||
} else {
|
||||
BlobRequirements::PreDeneb
|
||||
};
|
||||
BlobRequirements::PreDeneb
|
||||
};
|
||||
Ok(verified_blobs)
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ use proto_array::{Block as ProtoBlock, ExecutionStatus};
|
||||
use slog::{debug, warn};
|
||||
use slot_clock::SlotClock;
|
||||
use state_processing::per_block_processing::{
|
||||
compute_timestamp_at_slot, get_expected_withdrawals, is_execution_enabled,
|
||||
self, compute_timestamp_at_slot, get_expected_withdrawals, is_execution_enabled,
|
||||
is_merge_transition_complete, partially_verify_execution_payload,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
@@ -68,15 +68,16 @@ impl<T: BeaconChainTypes> PayloadNotifier<T> {
|
||||
// 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 block_message = block.message();
|
||||
let payload = block_message.execution_payload()?;
|
||||
partially_verify_execution_payload::<_, FullPayload<_>>(
|
||||
state,
|
||||
block.slot(),
|
||||
payload,
|
||||
block_message.body(),
|
||||
&chain.spec,
|
||||
)
|
||||
.map_err(BlockError::PerBlockProcessingError)?;
|
||||
|
||||
let payload = block_message.execution_payload()?;
|
||||
|
||||
match notify_execution_layer {
|
||||
NotifyExecutionLayer::No if chain.config.optimistic_finalized_sync => {
|
||||
// Verify the block hash here in Lighthouse and immediately mark the block as
|
||||
@@ -139,6 +140,14 @@ async fn notify_new_payload<'a, T: BeaconChainTypes>(
|
||||
block: BeaconBlockRef<'a, T::EthSpec>,
|
||||
) -> Result<PayloadVerificationStatus, BlockError<T::EthSpec>> {
|
||||
let execution_payload = block.execution_payload()?;
|
||||
let versioned_hashes = block.body().blob_kzg_commitments().ok().map(|commitments| {
|
||||
commitments
|
||||
.into_iter()
|
||||
.map(|commitment| {
|
||||
per_block_processing::deneb::deneb::kzg_commitment_to_versioned_hash(commitment)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
|
||||
let execution_layer = chain
|
||||
.execution_layer
|
||||
@@ -146,7 +155,7 @@ async fn notify_new_payload<'a, T: BeaconChainTypes>(
|
||||
.ok_or(ExecutionPayloadError::NoExecutionConnection)?;
|
||||
|
||||
let new_payload_response = execution_layer
|
||||
.notify_new_payload(&execution_payload.into())
|
||||
.notify_new_payload(&execution_payload.into(), versioned_hashes)
|
||||
.await;
|
||||
|
||||
match new_payload_response {
|
||||
|
||||
@@ -11,7 +11,7 @@ use std::collections::HashSet;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use std::time::{Duration, Instant};
|
||||
use types::EthSpec;
|
||||
use types::{EthSpec, VersionedHash};
|
||||
|
||||
pub use deposit_log::{DepositLog, Log};
|
||||
pub use reqwest::Client;
|
||||
@@ -808,8 +808,12 @@ impl HttpJsonRpc {
|
||||
pub async fn new_payload_v3<T: EthSpec>(
|
||||
&self,
|
||||
execution_payload: ExecutionPayload<T>,
|
||||
versioned_hashes: Vec<VersionedHash>,
|
||||
) -> Result<PayloadStatusV1, Error> {
|
||||
let params = json!([JsonExecutionPayload::from(execution_payload)]);
|
||||
let params = json!([
|
||||
JsonExecutionPayload::from(execution_payload),
|
||||
versioned_hashes
|
||||
]);
|
||||
|
||||
let response: JsonPayloadStatusV1 = self
|
||||
.rpc_request(
|
||||
@@ -1099,10 +1103,15 @@ impl HttpJsonRpc {
|
||||
pub async fn new_payload<T: EthSpec>(
|
||||
&self,
|
||||
execution_payload: ExecutionPayload<T>,
|
||||
versioned_hashes_opt: Option<Vec<VersionedHash>>,
|
||||
) -> Result<PayloadStatusV1, Error> {
|
||||
let engine_capabilities = self.get_engine_capabilities(None).await?;
|
||||
if engine_capabilities.new_payload_v3 {
|
||||
self.new_payload_v3(execution_payload).await
|
||||
let Some(versioned_hashes) = versioned_hashes_opt else {
|
||||
return Err(Error::IncorrectStateVariant);
|
||||
};
|
||||
self.new_payload_v3(execution_payload, versioned_hashes)
|
||||
.await
|
||||
} else if engine_capabilities.new_payload_v2 {
|
||||
self.new_payload_v2(execution_payload).await
|
||||
} else if engine_capabilities.new_payload_v1 {
|
||||
|
||||
@@ -1210,6 +1210,7 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
pub async fn notify_new_payload(
|
||||
&self,
|
||||
execution_payload: &ExecutionPayload<T>,
|
||||
versioned_hashes: Option<Vec<VersionedHash>>,
|
||||
) -> Result<PayloadStatus, Error> {
|
||||
let _timer = metrics::start_timer_vec(
|
||||
&metrics::EXECUTION_LAYER_REQUEST_TIMES,
|
||||
@@ -1226,7 +1227,11 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
|
||||
let result = self
|
||||
.engine()
|
||||
.request(|engine| engine.api.new_payload(execution_payload.clone()))
|
||||
.request(|engine| {
|
||||
engine
|
||||
.api
|
||||
.new_payload(execution_payload.clone(), versioned_hashes)
|
||||
})
|
||||
.await;
|
||||
|
||||
if let Ok(status) = &result {
|
||||
@@ -1237,6 +1242,8 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
}
|
||||
*self.inner.last_new_payload_errored.write().await = result.is_err();
|
||||
|
||||
//TODO(sean) process notify commitments updatE?
|
||||
|
||||
process_payload_status(execution_payload.block_hash(), result, self.log())
|
||||
.map_err(Box::new)
|
||||
.map_err(Error::EngineError)
|
||||
|
||||
@@ -204,7 +204,7 @@ impl<T: EthSpec> MockExecutionLayer<T> {
|
||||
Some(payload.clone())
|
||||
);
|
||||
|
||||
let status = self.el.notify_new_payload(&payload).await.unwrap();
|
||||
let status = self.el.notify_new_payload(&payload, None).await.unwrap();
|
||||
assert_eq!(status, PayloadStatus::Valid);
|
||||
|
||||
// Use junk values for slot/head-root to ensure there is no payload supplied.
|
||||
|
||||
@@ -13,7 +13,6 @@ pub use self::verify_attester_slashing::{
|
||||
pub use self::verify_proposer_slashing::verify_proposer_slashing;
|
||||
pub use altair::sync_committee::process_sync_aggregate;
|
||||
pub use block_signature_verifier::{BlockSignatureVerifier, ParallelSignatureSets};
|
||||
pub use deneb::deneb::process_blob_kzg_commitments;
|
||||
pub use is_valid_indexed_attestation::is_valid_indexed_attestation;
|
||||
pub use process_operations::process_operations;
|
||||
pub use verify_attestation::{
|
||||
@@ -163,11 +162,11 @@ pub fn per_block_processing<T: EthSpec, Payload: AbstractExecPayload<T>>(
|
||||
// `process_randao` as the former depends on the `randao_mix` computed with the reveal of the
|
||||
// previous block.
|
||||
if is_execution_enabled(state, block.body()) {
|
||||
let payload = block.body().execution_payload()?;
|
||||
let body = block.body();
|
||||
if state_processing_strategy == StateProcessingStrategy::Accurate {
|
||||
process_withdrawals::<T, Payload>(state, payload, spec)?;
|
||||
process_withdrawals::<T, Payload>(state, body.execution_payload()?, spec)?;
|
||||
}
|
||||
process_execution_payload::<T, Payload>(state, payload, spec)?;
|
||||
process_execution_payload::<T, Payload>(state, body, spec)?;
|
||||
}
|
||||
|
||||
process_randao(state, block, verify_randao, ctxt, spec)?;
|
||||
@@ -184,8 +183,6 @@ pub fn per_block_processing<T: EthSpec, Payload: AbstractExecPayload<T>>(
|
||||
)?;
|
||||
}
|
||||
|
||||
process_blob_kzg_commitments(block.body(), ctxt)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -350,9 +347,10 @@ pub fn get_new_eth1_data<T: EthSpec>(
|
||||
pub fn partially_verify_execution_payload<T: EthSpec, Payload: AbstractExecPayload<T>>(
|
||||
state: &BeaconState<T>,
|
||||
block_slot: Slot,
|
||||
payload: Payload::Ref<'_>,
|
||||
body: BeaconBlockBodyRef<T, Payload>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), BlockProcessingError> {
|
||||
let payload = body.execution_payload()?;
|
||||
if is_merge_transition_complete(state) {
|
||||
block_verify!(
|
||||
payload.parent_hash() == state.latest_execution_payload_header()?.block_hash(),
|
||||
@@ -379,6 +377,17 @@ pub fn partially_verify_execution_payload<T: EthSpec, Payload: AbstractExecPaylo
|
||||
}
|
||||
);
|
||||
|
||||
if let Ok(blob_commitments) = body.blob_kzg_commitments() {
|
||||
// Verify commitments are under the limit.
|
||||
block_verify!(
|
||||
blob_commitments.len() <= T::max_blobs_per_block(),
|
||||
BlockProcessingError::ExecutionInvalidBlobsLen {
|
||||
max: T::max_blobs_per_block(),
|
||||
actual: blob_commitments.len(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -391,11 +400,11 @@ pub fn partially_verify_execution_payload<T: EthSpec, Payload: AbstractExecPaylo
|
||||
/// https://github.com/ethereum/consensus-specs/blob/v1.1.5/specs/merge/beacon-chain.md#process_execution_payload
|
||||
pub fn process_execution_payload<T: EthSpec, Payload: AbstractExecPayload<T>>(
|
||||
state: &mut BeaconState<T>,
|
||||
payload: Payload::Ref<'_>,
|
||||
body: BeaconBlockBodyRef<T, Payload>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), BlockProcessingError> {
|
||||
partially_verify_execution_payload::<T, Payload>(state, state.slot(), payload, spec)?;
|
||||
|
||||
partially_verify_execution_payload::<T, Payload>(state, state.slot(), body, spec)?;
|
||||
let payload = body.execution_payload()?;
|
||||
match state.latest_execution_payload_header_mut()? {
|
||||
ExecutionPayloadHeaderRefMut::Merge(header_mut) => {
|
||||
match payload.to_execution_payload_header() {
|
||||
|
||||
@@ -1,125 +1,8 @@
|
||||
use crate::{BlockProcessingError, ConsensusContext};
|
||||
use ethereum_hashing::hash_fixed;
|
||||
use itertools::{EitherOrBoth, Itertools};
|
||||
use safe_arith::SafeArith;
|
||||
use ssz::Decode;
|
||||
use types::consts::deneb::{BLOB_TX_TYPE, VERSIONED_HASH_VERSION_KZG};
|
||||
use types::{
|
||||
AbstractExecPayload, BeaconBlockBodyRef, EthSpec, ExecPayload, KzgCommitment, Transaction,
|
||||
Transactions, VersionedHash,
|
||||
};
|
||||
use types::consts::deneb::VERSIONED_HASH_VERSION_KZG;
|
||||
use types::{KzgCommitment, VersionedHash};
|
||||
|
||||
pub fn process_blob_kzg_commitments<T: EthSpec, Payload: AbstractExecPayload<T>>(
|
||||
block_body: BeaconBlockBodyRef<T, Payload>,
|
||||
ctxt: &mut ConsensusContext<T>,
|
||||
) -> Result<(), BlockProcessingError> {
|
||||
// Return early if this check has already been run.
|
||||
if ctxt.kzg_commitments_consistent() {
|
||||
return Ok(());
|
||||
}
|
||||
if let (Ok(payload), Ok(kzg_commitments)) = (
|
||||
block_body.execution_payload(),
|
||||
block_body.blob_kzg_commitments(),
|
||||
) {
|
||||
if let Some(transactions) = payload.transactions() {
|
||||
if !verify_kzg_commitments_against_transactions::<T>(transactions, kzg_commitments)? {
|
||||
return Err(BlockProcessingError::BlobVersionHashMismatch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn verify_kzg_commitments_against_transactions<T: EthSpec>(
|
||||
transactions: &Transactions<T>,
|
||||
kzg_commitments: &[KzgCommitment],
|
||||
) -> Result<bool, BlockProcessingError> {
|
||||
let nested_iter = transactions
|
||||
.into_iter()
|
||||
.filter(|tx| {
|
||||
tx.first()
|
||||
.map(|tx_type| *tx_type == BLOB_TX_TYPE)
|
||||
.unwrap_or(false)
|
||||
})
|
||||
.map(|tx| tx_peek_versioned_hashes::<T>(tx));
|
||||
|
||||
itertools::process_results(nested_iter, |iter| {
|
||||
let zipped_iter = iter
|
||||
.flatten()
|
||||
// Need to use `itertools::zip_longest` here because just zipping hides if one iter is shorter
|
||||
// and `itertools::zip_eq` panics.
|
||||
.zip_longest(kzg_commitments.iter())
|
||||
.enumerate()
|
||||
.map(|(index, next)| match next {
|
||||
EitherOrBoth::Both(hash, commitment) => Ok((hash?, commitment)),
|
||||
// The number of versioned hashes from the blob transactions exceeds the number of
|
||||
// commitments in the block.
|
||||
EitherOrBoth::Left(_) => Err(BlockProcessingError::BlobNumCommitmentsMismatch {
|
||||
commitments_processed_in_block: index,
|
||||
commitments_processed_in_transactions: index.safe_add(1)?,
|
||||
}),
|
||||
// The number of commitments in the block exceeds the number of versioned hashes
|
||||
// in the blob transactions.
|
||||
EitherOrBoth::Right(_) => Err(BlockProcessingError::BlobNumCommitmentsMismatch {
|
||||
commitments_processed_in_block: index.safe_add(1)?,
|
||||
commitments_processed_in_transactions: index,
|
||||
}),
|
||||
});
|
||||
|
||||
itertools::process_results(zipped_iter, |mut iter| {
|
||||
iter.all(|(tx_versioned_hash, commitment)| {
|
||||
tx_versioned_hash == kzg_commitment_to_versioned_hash(commitment)
|
||||
})
|
||||
})
|
||||
})?
|
||||
}
|
||||
|
||||
/// Only transactions of type `BLOB_TX_TYPE` should be passed into this function.
|
||||
fn tx_peek_versioned_hashes<T: EthSpec>(
|
||||
opaque_tx: &Transaction<T::MaxBytesPerTransaction>,
|
||||
) -> Result<
|
||||
impl IntoIterator<Item = Result<VersionedHash, BlockProcessingError>> + '_,
|
||||
BlockProcessingError,
|
||||
> {
|
||||
let tx_len = opaque_tx.len();
|
||||
let message_offset = 1.safe_add(u32::from_ssz_bytes(opaque_tx.get(1..5).ok_or(
|
||||
BlockProcessingError::BlobVersionHashIndexOutOfBounds {
|
||||
length: tx_len,
|
||||
index: 5,
|
||||
},
|
||||
)?)?)?;
|
||||
|
||||
let message_offset_usize = message_offset as usize;
|
||||
|
||||
// field offset: 32 + 8 + 32 + 32 + 8 + 4 + 32 + 4 + 4 + 32 = 188
|
||||
let versioned_hashes_offset = message_offset.safe_add(u32::from_ssz_bytes(
|
||||
opaque_tx
|
||||
.get(message_offset_usize.safe_add(188)?..message_offset_usize.safe_add(192)?)
|
||||
.ok_or(BlockProcessingError::BlobVersionHashIndexOutOfBounds {
|
||||
length: tx_len,
|
||||
index: message_offset_usize.safe_add(192)?,
|
||||
})?,
|
||||
)?)?;
|
||||
|
||||
let num_hashes = tx_len
|
||||
.safe_sub(versioned_hashes_offset as usize)?
|
||||
.safe_div(32)?;
|
||||
|
||||
Ok((0..num_hashes).map(move |i| {
|
||||
let next_version_hash_index =
|
||||
(versioned_hashes_offset as usize).safe_add(i.safe_mul(32)?)?;
|
||||
let bytes = opaque_tx
|
||||
.get(next_version_hash_index..next_version_hash_index.safe_add(32)?)
|
||||
.ok_or(BlockProcessingError::BlobVersionHashIndexOutOfBounds {
|
||||
length: tx_len,
|
||||
index: (next_version_hash_index).safe_add(32)?,
|
||||
})?;
|
||||
Ok(VersionedHash::from_slice(bytes))
|
||||
}))
|
||||
}
|
||||
|
||||
fn kzg_commitment_to_versioned_hash(kzg_commitment: &KzgCommitment) -> VersionedHash {
|
||||
pub fn kzg_commitment_to_versioned_hash(kzg_commitment: &KzgCommitment) -> VersionedHash {
|
||||
let mut hashed_commitment = hash_fixed(&kzg_commitment.0);
|
||||
hashed_commitment[0] = VERSIONED_HASH_VERSION_KZG;
|
||||
VersionedHash::from(hashed_commitment)
|
||||
|
||||
@@ -76,6 +76,10 @@ pub enum BlockProcessingError {
|
||||
expected: u64,
|
||||
found: u64,
|
||||
},
|
||||
ExecutionInvalidBlobsLen {
|
||||
max: usize,
|
||||
actual: usize,
|
||||
},
|
||||
ExecutionInvalid,
|
||||
ConsensusContext(ContextError),
|
||||
WithdrawalsRootMismatch {
|
||||
|
||||
@@ -73,12 +73,12 @@ pub mod validator_subscription;
|
||||
pub mod voluntary_exit;
|
||||
#[macro_use]
|
||||
pub mod slot_epoch_macros;
|
||||
pub mod body;
|
||||
pub mod config_and_preset;
|
||||
pub mod execution_block_header;
|
||||
pub mod fork_context;
|
||||
pub mod participation_flags;
|
||||
pub mod participation_list;
|
||||
pub mod payload;
|
||||
pub mod preset;
|
||||
pub mod slot_epoch;
|
||||
pub mod subnet_id;
|
||||
@@ -121,6 +121,11 @@ pub use crate::beacon_committee::{BeaconCommittee, OwnedBeaconCommittee};
|
||||
pub use crate::beacon_state::{BeaconTreeHashCache, Error as BeaconStateError, *};
|
||||
pub use crate::blob_sidecar::{BlobSidecar, BlobSidecarList};
|
||||
pub use crate::bls_to_execution_change::BlsToExecutionChange;
|
||||
pub use crate::body::{
|
||||
AbstractExecPayload, BlindedPayload, BlindedPayloadCapella, BlindedPayloadDeneb,
|
||||
BlindedPayloadMerge, BlindedPayloadRef, BlockType, ExecPayload, FullPayload,
|
||||
FullPayloadCapella, FullPayloadDeneb, FullPayloadMerge, FullPayloadRef, OwnedExecPayload,
|
||||
};
|
||||
pub use crate::chain_spec::{ChainSpec, Config, Domain};
|
||||
pub use crate::checkpoint::Checkpoint;
|
||||
pub use crate::config_and_preset::{
|
||||
@@ -156,11 +161,6 @@ pub use crate::light_client_finality_update::LightClientFinalityUpdate;
|
||||
pub use crate::light_client_optimistic_update::LightClientOptimisticUpdate;
|
||||
pub use crate::participation_flags::ParticipationFlags;
|
||||
pub use crate::participation_list::ParticipationList;
|
||||
pub use crate::payload::{
|
||||
AbstractExecPayload, BlindedPayload, BlindedPayloadCapella, BlindedPayloadDeneb,
|
||||
BlindedPayloadMerge, BlindedPayloadRef, BlockType, ExecPayload, FullPayload,
|
||||
FullPayloadCapella, FullPayloadDeneb, FullPayloadMerge, FullPayloadRef, OwnedExecPayload,
|
||||
};
|
||||
pub use crate::pending_attestation::PendingAttestation;
|
||||
pub use crate::preset::{AltairPreset, BasePreset, BellatrixPreset, CapellaPreset};
|
||||
pub use crate::proposer_preparation_data::ProposerPreparationData;
|
||||
|
||||
@@ -1,957 +0,0 @@
|
||||
use crate::{test_utils::TestRandom, *};
|
||||
use derivative::Derivative;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ssz::{Decode, Encode};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use std::borrow::Cow;
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt::Debug;
|
||||
use std::hash::Hash;
|
||||
use test_random_derive::TestRandom;
|
||||
use tree_hash::TreeHash;
|
||||
use tree_hash_derive::TreeHash;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum BlockType {
|
||||
Blinded,
|
||||
Full,
|
||||
}
|
||||
|
||||
/// A trait representing behavior of an `ExecutionPayload` that either has a full list of transactions
|
||||
/// or a transaction hash in it's place.
|
||||
pub trait ExecPayload<T: EthSpec>: Debug + Clone + PartialEq + Hash + TreeHash + Send {
|
||||
fn block_type() -> BlockType;
|
||||
|
||||
/// Convert the payload into a payload header.
|
||||
fn to_execution_payload_header(&self) -> ExecutionPayloadHeader<T>;
|
||||
|
||||
/// We provide a subset of field accessors, for the fields used in `consensus`.
|
||||
///
|
||||
/// More fields can be added here if you wish.
|
||||
fn parent_hash(&self) -> ExecutionBlockHash;
|
||||
fn prev_randao(&self) -> Hash256;
|
||||
fn block_number(&self) -> u64;
|
||||
fn timestamp(&self) -> u64;
|
||||
fn block_hash(&self) -> ExecutionBlockHash;
|
||||
fn fee_recipient(&self) -> Address;
|
||||
fn gas_limit(&self) -> u64;
|
||||
fn transactions(&self) -> Option<&Transactions<T>>;
|
||||
/// fork-specific fields
|
||||
fn withdrawals_root(&self) -> Result<Hash256, Error>;
|
||||
|
||||
/// Is this a default payload with 0x0 roots for transactions and withdrawals?
|
||||
fn is_default_with_zero_roots(&self) -> bool;
|
||||
|
||||
/// Is this a default payload with the hash of the empty list for transactions and withdrawals?
|
||||
fn is_default_with_empty_roots(&self) -> bool;
|
||||
}
|
||||
|
||||
/// `ExecPayload` functionality the requires ownership.
|
||||
pub trait OwnedExecPayload<T: EthSpec>:
|
||||
ExecPayload<T>
|
||||
+ Default
|
||||
+ Serialize
|
||||
+ DeserializeOwned
|
||||
+ Encode
|
||||
+ Decode
|
||||
+ TestRandom
|
||||
+ for<'a> arbitrary::Arbitrary<'a>
|
||||
+ 'static
|
||||
{
|
||||
}
|
||||
|
||||
impl<T: EthSpec, P> OwnedExecPayload<T> for P where
|
||||
P: ExecPayload<T>
|
||||
+ Default
|
||||
+ Serialize
|
||||
+ DeserializeOwned
|
||||
+ Encode
|
||||
+ Decode
|
||||
+ TestRandom
|
||||
+ for<'a> arbitrary::Arbitrary<'a>
|
||||
+ 'static
|
||||
{
|
||||
}
|
||||
|
||||
pub trait AbstractExecPayload<T: EthSpec>:
|
||||
ExecPayload<T>
|
||||
+ Sized
|
||||
+ From<ExecutionPayload<T>>
|
||||
+ TryFrom<ExecutionPayloadHeader<T>>
|
||||
+ TryInto<Self::Merge>
|
||||
+ TryInto<Self::Capella>
|
||||
+ TryInto<Self::Deneb>
|
||||
{
|
||||
type Ref<'a>: ExecPayload<T>
|
||||
+ Copy
|
||||
+ From<&'a Self::Merge>
|
||||
+ From<&'a Self::Capella>
|
||||
+ From<&'a Self::Deneb>;
|
||||
|
||||
type Merge: OwnedExecPayload<T>
|
||||
+ Into<Self>
|
||||
+ for<'a> From<Cow<'a, ExecutionPayloadMerge<T>>>
|
||||
+ TryFrom<ExecutionPayloadHeaderMerge<T>>;
|
||||
type Capella: OwnedExecPayload<T>
|
||||
+ Into<Self>
|
||||
+ for<'a> From<Cow<'a, ExecutionPayloadCapella<T>>>
|
||||
+ TryFrom<ExecutionPayloadHeaderCapella<T>>;
|
||||
type Deneb: OwnedExecPayload<T>
|
||||
+ Into<Self>
|
||||
+ for<'a> From<Cow<'a, ExecutionPayloadDeneb<T>>>
|
||||
+ TryFrom<ExecutionPayloadHeaderDeneb<T>>;
|
||||
|
||||
fn default_at_fork(fork_name: ForkName) -> Result<Self, Error>;
|
||||
}
|
||||
|
||||
#[superstruct(
|
||||
variants(Merge, Capella, Deneb),
|
||||
variant_attributes(
|
||||
derive(
|
||||
Debug,
|
||||
Clone,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Encode,
|
||||
Decode,
|
||||
TestRandom,
|
||||
TreeHash,
|
||||
Derivative,
|
||||
arbitrary::Arbitrary,
|
||||
),
|
||||
derivative(PartialEq, Hash(bound = "T: EthSpec")),
|
||||
serde(bound = "T: EthSpec", deny_unknown_fields),
|
||||
arbitrary(bound = "T: EthSpec"),
|
||||
ssz(struct_behaviour = "transparent"),
|
||||
),
|
||||
ref_attributes(
|
||||
derive(Debug, Derivative, TreeHash),
|
||||
derivative(PartialEq, Hash(bound = "T: EthSpec")),
|
||||
tree_hash(enum_behaviour = "transparent"),
|
||||
),
|
||||
map_into(ExecutionPayload),
|
||||
map_ref_into(ExecutionPayloadRef),
|
||||
cast_error(ty = "Error", expr = "BeaconStateError::IncorrectStateVariant"),
|
||||
partial_getter_error(ty = "Error", expr = "BeaconStateError::IncorrectStateVariant")
|
||||
)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TreeHash, Derivative, arbitrary::Arbitrary)]
|
||||
#[derivative(PartialEq, Hash(bound = "T: EthSpec"))]
|
||||
#[serde(bound = "T: EthSpec")]
|
||||
#[arbitrary(bound = "T: EthSpec")]
|
||||
#[tree_hash(enum_behaviour = "transparent")]
|
||||
pub struct FullPayload<T: EthSpec> {
|
||||
#[superstruct(only(Merge), partial_getter(rename = "execution_payload_merge"))]
|
||||
pub execution_payload: ExecutionPayloadMerge<T>,
|
||||
#[superstruct(only(Capella), partial_getter(rename = "execution_payload_capella"))]
|
||||
pub execution_payload: ExecutionPayloadCapella<T>,
|
||||
#[superstruct(only(Deneb), partial_getter(rename = "execution_payload_deneb"))]
|
||||
pub execution_payload: ExecutionPayloadDeneb<T>,
|
||||
}
|
||||
|
||||
impl<T: EthSpec> From<FullPayload<T>> for ExecutionPayload<T> {
|
||||
fn from(full_payload: FullPayload<T>) -> Self {
|
||||
map_full_payload_into_execution_payload!(full_payload, move |payload, cons| {
|
||||
cons(payload.execution_payload)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: EthSpec> From<FullPayloadRef<'a, T>> for ExecutionPayload<T> {
|
||||
fn from(full_payload_ref: FullPayloadRef<'a, T>) -> Self {
|
||||
map_full_payload_ref!(&'a _, full_payload_ref, move |payload, cons| {
|
||||
cons(payload);
|
||||
payload.execution_payload.clone().into()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: EthSpec> From<FullPayloadRef<'a, T>> for FullPayload<T> {
|
||||
fn from(full_payload_ref: FullPayloadRef<'a, T>) -> Self {
|
||||
map_full_payload_ref!(&'a _, full_payload_ref, move |payload, cons| {
|
||||
cons(payload);
|
||||
payload.clone().into()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: EthSpec> ExecPayload<T> for FullPayload<T> {
|
||||
fn block_type() -> BlockType {
|
||||
BlockType::Full
|
||||
}
|
||||
|
||||
fn to_execution_payload_header<'a>(&'a self) -> ExecutionPayloadHeader<T> {
|
||||
map_full_payload_ref!(&'a _, self.to_ref(), move |inner, cons| {
|
||||
cons(inner);
|
||||
let exec_payload_ref: ExecutionPayloadRef<'a, T> = From::from(&inner.execution_payload);
|
||||
ExecutionPayloadHeader::from(exec_payload_ref)
|
||||
})
|
||||
}
|
||||
|
||||
fn parent_hash<'a>(&'a self) -> ExecutionBlockHash {
|
||||
map_full_payload_ref!(&'a _, self.to_ref(), move |payload, cons| {
|
||||
cons(payload);
|
||||
payload.execution_payload.parent_hash
|
||||
})
|
||||
}
|
||||
|
||||
fn prev_randao<'a>(&'a self) -> Hash256 {
|
||||
map_full_payload_ref!(&'a _, self.to_ref(), move |payload, cons| {
|
||||
cons(payload);
|
||||
payload.execution_payload.prev_randao
|
||||
})
|
||||
}
|
||||
|
||||
fn block_number<'a>(&'a self) -> u64 {
|
||||
map_full_payload_ref!(&'a _, self.to_ref(), move |payload, cons| {
|
||||
cons(payload);
|
||||
payload.execution_payload.block_number
|
||||
})
|
||||
}
|
||||
|
||||
fn timestamp<'a>(&'a self) -> u64 {
|
||||
map_full_payload_ref!(&'a _, self.to_ref(), move |payload, cons| {
|
||||
cons(payload);
|
||||
payload.execution_payload.timestamp
|
||||
})
|
||||
}
|
||||
|
||||
fn block_hash<'a>(&'a self) -> ExecutionBlockHash {
|
||||
map_full_payload_ref!(&'a _, self.to_ref(), move |payload, cons| {
|
||||
cons(payload);
|
||||
payload.execution_payload.block_hash
|
||||
})
|
||||
}
|
||||
|
||||
fn fee_recipient<'a>(&'a self) -> Address {
|
||||
map_full_payload_ref!(&'a _, self.to_ref(), move |payload, cons| {
|
||||
cons(payload);
|
||||
payload.execution_payload.fee_recipient
|
||||
})
|
||||
}
|
||||
|
||||
fn gas_limit<'a>(&'a self) -> u64 {
|
||||
map_full_payload_ref!(&'a _, self.to_ref(), move |payload, cons| {
|
||||
cons(payload);
|
||||
payload.execution_payload.gas_limit
|
||||
})
|
||||
}
|
||||
|
||||
fn transactions<'a>(&'a self) -> Option<&'a Transactions<T>> {
|
||||
map_full_payload_ref!(&'a _, self.to_ref(), move |payload, cons| {
|
||||
cons(payload);
|
||||
Some(&payload.execution_payload.transactions)
|
||||
})
|
||||
}
|
||||
|
||||
fn withdrawals_root(&self) -> Result<Hash256, Error> {
|
||||
match self {
|
||||
FullPayload::Merge(_) => Err(Error::IncorrectStateVariant),
|
||||
FullPayload::Capella(ref inner) => {
|
||||
Ok(inner.execution_payload.withdrawals.tree_hash_root())
|
||||
}
|
||||
FullPayload::Deneb(ref inner) => {
|
||||
Ok(inner.execution_payload.withdrawals.tree_hash_root())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_default_with_zero_roots<'a>(&'a self) -> bool {
|
||||
map_full_payload_ref!(&'a _, self.to_ref(), move |payload, cons| {
|
||||
cons(payload);
|
||||
payload.execution_payload == <_>::default()
|
||||
})
|
||||
}
|
||||
|
||||
fn is_default_with_empty_roots(&self) -> bool {
|
||||
// For full payloads the empty/zero distinction does not exist.
|
||||
self.is_default_with_zero_roots()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: EthSpec> FullPayload<T> {
|
||||
pub fn execution_payload(self) -> ExecutionPayload<T> {
|
||||
map_full_payload_into_execution_payload!(self, |inner, cons| {
|
||||
cons(inner.execution_payload)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: EthSpec> FullPayloadRef<'a, T> {
|
||||
pub fn execution_payload_ref(self) -> ExecutionPayloadRef<'a, T> {
|
||||
map_full_payload_ref_into_execution_payload_ref!(&'a _, self, |inner, cons| {
|
||||
cons(&inner.execution_payload)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'b, T: EthSpec> ExecPayload<T> for FullPayloadRef<'b, T> {
|
||||
fn block_type() -> BlockType {
|
||||
BlockType::Full
|
||||
}
|
||||
|
||||
fn to_execution_payload_header<'a>(&'a self) -> ExecutionPayloadHeader<T> {
|
||||
map_full_payload_ref!(&'a _, self, move |payload, cons| {
|
||||
cons(payload);
|
||||
payload.to_execution_payload_header()
|
||||
})
|
||||
}
|
||||
|
||||
fn parent_hash<'a>(&'a self) -> ExecutionBlockHash {
|
||||
map_full_payload_ref!(&'a _, self, move |payload, cons| {
|
||||
cons(payload);
|
||||
payload.execution_payload.parent_hash
|
||||
})
|
||||
}
|
||||
|
||||
fn prev_randao<'a>(&'a self) -> Hash256 {
|
||||
map_full_payload_ref!(&'a _, self, move |payload, cons| {
|
||||
cons(payload);
|
||||
payload.execution_payload.prev_randao
|
||||
})
|
||||
}
|
||||
|
||||
fn block_number<'a>(&'a self) -> u64 {
|
||||
map_full_payload_ref!(&'a _, self, move |payload, cons| {
|
||||
cons(payload);
|
||||
payload.execution_payload.block_number
|
||||
})
|
||||
}
|
||||
|
||||
fn timestamp<'a>(&'a self) -> u64 {
|
||||
map_full_payload_ref!(&'a _, self, move |payload, cons| {
|
||||
cons(payload);
|
||||
payload.execution_payload.timestamp
|
||||
})
|
||||
}
|
||||
|
||||
fn block_hash<'a>(&'a self) -> ExecutionBlockHash {
|
||||
map_full_payload_ref!(&'a _, self, move |payload, cons| {
|
||||
cons(payload);
|
||||
payload.execution_payload.block_hash
|
||||
})
|
||||
}
|
||||
|
||||
fn fee_recipient<'a>(&'a self) -> Address {
|
||||
map_full_payload_ref!(&'a _, self, move |payload, cons| {
|
||||
cons(payload);
|
||||
payload.execution_payload.fee_recipient
|
||||
})
|
||||
}
|
||||
|
||||
fn gas_limit<'a>(&'a self) -> u64 {
|
||||
map_full_payload_ref!(&'a _, self, move |payload, cons| {
|
||||
cons(payload);
|
||||
payload.execution_payload.gas_limit
|
||||
})
|
||||
}
|
||||
|
||||
fn transactions<'a>(&'a self) -> Option<&'a Transactions<T>> {
|
||||
map_full_payload_ref!(&'a _, self, move |payload, cons| {
|
||||
cons(payload);
|
||||
Some(&payload.execution_payload.transactions)
|
||||
})
|
||||
}
|
||||
|
||||
fn withdrawals_root(&self) -> Result<Hash256, Error> {
|
||||
match self {
|
||||
FullPayloadRef::Merge(_) => Err(Error::IncorrectStateVariant),
|
||||
FullPayloadRef::Capella(inner) => {
|
||||
Ok(inner.execution_payload.withdrawals.tree_hash_root())
|
||||
}
|
||||
FullPayloadRef::Deneb(inner) => {
|
||||
Ok(inner.execution_payload.withdrawals.tree_hash_root())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_default_with_zero_roots<'a>(&'a self) -> bool {
|
||||
map_full_payload_ref!(&'a _, self, move |payload, cons| {
|
||||
cons(payload);
|
||||
payload.execution_payload == <_>::default()
|
||||
})
|
||||
}
|
||||
|
||||
fn is_default_with_empty_roots(&self) -> bool {
|
||||
// For full payloads the empty/zero distinction does not exist.
|
||||
self.is_default_with_zero_roots()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: EthSpec> AbstractExecPayload<T> for FullPayload<T> {
|
||||
type Ref<'a> = FullPayloadRef<'a, T>;
|
||||
type Merge = FullPayloadMerge<T>;
|
||||
type Capella = FullPayloadCapella<T>;
|
||||
type Deneb = FullPayloadDeneb<T>;
|
||||
|
||||
fn default_at_fork(fork_name: ForkName) -> Result<Self, Error> {
|
||||
match fork_name {
|
||||
ForkName::Base | ForkName::Altair => Err(Error::IncorrectStateVariant),
|
||||
ForkName::Merge => Ok(FullPayloadMerge::default().into()),
|
||||
ForkName::Capella => Ok(FullPayloadCapella::default().into()),
|
||||
ForkName::Deneb => Ok(FullPayloadDeneb::default().into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: EthSpec> From<ExecutionPayload<T>> for FullPayload<T> {
|
||||
fn from(execution_payload: ExecutionPayload<T>) -> Self {
|
||||
map_execution_payload_into_full_payload!(execution_payload, |inner, cons| {
|
||||
cons(inner.into())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: EthSpec> TryFrom<ExecutionPayloadHeader<T>> for FullPayload<T> {
|
||||
type Error = ();
|
||||
fn try_from(_: ExecutionPayloadHeader<T>) -> Result<Self, Self::Error> {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
#[superstruct(
|
||||
variants(Merge, Capella, Deneb),
|
||||
variant_attributes(
|
||||
derive(
|
||||
Debug,
|
||||
Clone,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Encode,
|
||||
Decode,
|
||||
TestRandom,
|
||||
TreeHash,
|
||||
Derivative,
|
||||
arbitrary::Arbitrary
|
||||
),
|
||||
derivative(PartialEq, Hash(bound = "T: EthSpec")),
|
||||
serde(bound = "T: EthSpec", deny_unknown_fields),
|
||||
arbitrary(bound = "T: EthSpec"),
|
||||
ssz(struct_behaviour = "transparent"),
|
||||
),
|
||||
ref_attributes(
|
||||
derive(Debug, Derivative, TreeHash),
|
||||
derivative(PartialEq, Hash(bound = "T: EthSpec")),
|
||||
tree_hash(enum_behaviour = "transparent"),
|
||||
),
|
||||
map_into(ExecutionPayloadHeader),
|
||||
cast_error(ty = "Error", expr = "BeaconStateError::IncorrectStateVariant"),
|
||||
partial_getter_error(ty = "Error", expr = "BeaconStateError::IncorrectStateVariant")
|
||||
)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TreeHash, Derivative, arbitrary::Arbitrary)]
|
||||
#[derivative(PartialEq, Hash(bound = "T: EthSpec"))]
|
||||
#[serde(bound = "T: EthSpec")]
|
||||
#[arbitrary(bound = "T: EthSpec")]
|
||||
#[tree_hash(enum_behaviour = "transparent")]
|
||||
pub struct BlindedPayload<T: EthSpec> {
|
||||
#[superstruct(only(Merge), partial_getter(rename = "execution_payload_merge"))]
|
||||
pub execution_payload_header: ExecutionPayloadHeaderMerge<T>,
|
||||
#[superstruct(only(Capella), partial_getter(rename = "execution_payload_capella"))]
|
||||
pub execution_payload_header: ExecutionPayloadHeaderCapella<T>,
|
||||
#[superstruct(only(Deneb), partial_getter(rename = "execution_payload_deneb"))]
|
||||
pub execution_payload_header: ExecutionPayloadHeaderDeneb<T>,
|
||||
}
|
||||
|
||||
impl<'a, T: EthSpec> From<BlindedPayloadRef<'a, T>> for BlindedPayload<T> {
|
||||
fn from(blinded_payload_ref: BlindedPayloadRef<'a, T>) -> Self {
|
||||
map_blinded_payload_ref!(&'a _, blinded_payload_ref, move |payload, cons| {
|
||||
cons(payload);
|
||||
payload.clone().into()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: EthSpec> ExecPayload<T> for BlindedPayload<T> {
|
||||
fn block_type() -> BlockType {
|
||||
BlockType::Blinded
|
||||
}
|
||||
|
||||
fn to_execution_payload_header(&self) -> ExecutionPayloadHeader<T> {
|
||||
map_blinded_payload_into_execution_payload_header!(self.clone(), |inner, cons| {
|
||||
cons(inner.execution_payload_header)
|
||||
})
|
||||
}
|
||||
|
||||
fn parent_hash<'a>(&'a self) -> ExecutionBlockHash {
|
||||
map_blinded_payload_ref!(&'a _, self.to_ref(), move |payload, cons| {
|
||||
cons(payload);
|
||||
payload.execution_payload_header.parent_hash
|
||||
})
|
||||
}
|
||||
|
||||
fn prev_randao<'a>(&'a self) -> Hash256 {
|
||||
map_blinded_payload_ref!(&'a _, self.to_ref(), move |payload, cons| {
|
||||
cons(payload);
|
||||
payload.execution_payload_header.prev_randao
|
||||
})
|
||||
}
|
||||
|
||||
fn block_number<'a>(&'a self) -> u64 {
|
||||
map_blinded_payload_ref!(&'a _, self.to_ref(), move |payload, cons| {
|
||||
cons(payload);
|
||||
payload.execution_payload_header.block_number
|
||||
})
|
||||
}
|
||||
|
||||
fn timestamp<'a>(&'a self) -> u64 {
|
||||
map_blinded_payload_ref!(&'a _, self.to_ref(), move |payload, cons| {
|
||||
cons(payload);
|
||||
payload.execution_payload_header.timestamp
|
||||
})
|
||||
}
|
||||
|
||||
fn block_hash<'a>(&'a self) -> ExecutionBlockHash {
|
||||
map_blinded_payload_ref!(&'a _, self.to_ref(), move |payload, cons| {
|
||||
cons(payload);
|
||||
payload.execution_payload_header.block_hash
|
||||
})
|
||||
}
|
||||
|
||||
fn fee_recipient<'a>(&'a self) -> Address {
|
||||
map_blinded_payload_ref!(&'a _, self.to_ref(), move |payload, cons| {
|
||||
cons(payload);
|
||||
payload.execution_payload_header.fee_recipient
|
||||
})
|
||||
}
|
||||
|
||||
fn gas_limit<'a>(&'a self) -> u64 {
|
||||
map_blinded_payload_ref!(&'a _, self.to_ref(), move |payload, cons| {
|
||||
cons(payload);
|
||||
payload.execution_payload_header.gas_limit
|
||||
})
|
||||
}
|
||||
|
||||
fn transactions(&self) -> Option<&Transactions<T>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn withdrawals_root(&self) -> Result<Hash256, Error> {
|
||||
match self {
|
||||
BlindedPayload::Merge(_) => Err(Error::IncorrectStateVariant),
|
||||
BlindedPayload::Capella(ref inner) => {
|
||||
Ok(inner.execution_payload_header.withdrawals_root)
|
||||
}
|
||||
BlindedPayload::Deneb(ref inner) => Ok(inner.execution_payload_header.withdrawals_root),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_default_with_zero_roots(&self) -> bool {
|
||||
self.to_ref().is_default_with_zero_roots()
|
||||
}
|
||||
|
||||
// For blinded payloads we must check "defaultness" against the default `ExecutionPayload`
|
||||
// which has been blinded into an `ExecutionPayloadHeader`, NOT against the default
|
||||
// `ExecutionPayloadHeader` which has a zeroed out `transactions_root`. The transactions root
|
||||
// should be the root of the empty list.
|
||||
fn is_default_with_empty_roots(&self) -> bool {
|
||||
self.to_ref().is_default_with_empty_roots()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'b, T: EthSpec> ExecPayload<T> for BlindedPayloadRef<'b, T> {
|
||||
fn block_type() -> BlockType {
|
||||
BlockType::Blinded
|
||||
}
|
||||
|
||||
fn to_execution_payload_header<'a>(&'a self) -> ExecutionPayloadHeader<T> {
|
||||
map_blinded_payload_ref!(&'a _, self, move |payload, cons| {
|
||||
cons(payload);
|
||||
payload.to_execution_payload_header()
|
||||
})
|
||||
}
|
||||
|
||||
fn parent_hash<'a>(&'a self) -> ExecutionBlockHash {
|
||||
map_blinded_payload_ref!(&'a _, self, move |payload, cons| {
|
||||
cons(payload);
|
||||
payload.execution_payload_header.parent_hash
|
||||
})
|
||||
}
|
||||
|
||||
fn prev_randao<'a>(&'a self) -> Hash256 {
|
||||
map_blinded_payload_ref!(&'a _, self, move |payload, cons| {
|
||||
cons(payload);
|
||||
payload.execution_payload_header.prev_randao
|
||||
})
|
||||
}
|
||||
|
||||
fn block_number<'a>(&'a self) -> u64 {
|
||||
map_blinded_payload_ref!(&'a _, self, move |payload, cons| {
|
||||
cons(payload);
|
||||
payload.execution_payload_header.block_number
|
||||
})
|
||||
}
|
||||
|
||||
fn timestamp<'a>(&'a self) -> u64 {
|
||||
map_blinded_payload_ref!(&'a _, self, move |payload, cons| {
|
||||
cons(payload);
|
||||
payload.execution_payload_header.timestamp
|
||||
})
|
||||
}
|
||||
|
||||
fn block_hash<'a>(&'a self) -> ExecutionBlockHash {
|
||||
map_blinded_payload_ref!(&'a _, self, move |payload, cons| {
|
||||
cons(payload);
|
||||
payload.execution_payload_header.block_hash
|
||||
})
|
||||
}
|
||||
|
||||
fn fee_recipient<'a>(&'a self) -> Address {
|
||||
map_blinded_payload_ref!(&'a _, self, move |payload, cons| {
|
||||
cons(payload);
|
||||
payload.execution_payload_header.fee_recipient
|
||||
})
|
||||
}
|
||||
|
||||
fn gas_limit<'a>(&'a self) -> u64 {
|
||||
map_blinded_payload_ref!(&'a _, self, move |payload, cons| {
|
||||
cons(payload);
|
||||
payload.execution_payload_header.gas_limit
|
||||
})
|
||||
}
|
||||
|
||||
fn transactions(&self) -> Option<&Transactions<T>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn withdrawals_root(&self) -> Result<Hash256, Error> {
|
||||
match self {
|
||||
BlindedPayloadRef::Merge(_) => Err(Error::IncorrectStateVariant),
|
||||
BlindedPayloadRef::Capella(inner) => {
|
||||
Ok(inner.execution_payload_header.withdrawals_root)
|
||||
}
|
||||
BlindedPayloadRef::Deneb(inner) => Ok(inner.execution_payload_header.withdrawals_root),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_default_with_zero_roots<'a>(&'a self) -> bool {
|
||||
map_blinded_payload_ref!(&'b _, self, move |payload, cons| {
|
||||
cons(payload);
|
||||
payload.execution_payload_header == <_>::default()
|
||||
})
|
||||
}
|
||||
|
||||
fn is_default_with_empty_roots<'a>(&'a self) -> bool {
|
||||
map_blinded_payload_ref!(&'b _, self, move |payload, cons| {
|
||||
cons(payload);
|
||||
payload.is_default_with_empty_roots()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_exec_payload_common {
|
||||
($wrapper_type:ident, // BlindedPayloadMerge | FullPayloadMerge
|
||||
$wrapped_type:ident, // ExecutionPayloadHeaderMerge | ExecutionPayloadMerge
|
||||
$wrapped_type_full:ident, // ExecutionPayloadMerge | ExecutionPayloadMerge
|
||||
$wrapped_type_header:ident, // ExecutionPayloadHeaderMerge | ExecutionPayloadHeaderMerge
|
||||
$wrapped_field:ident, // execution_payload_header | execution_payload
|
||||
$fork_variant:ident, // Merge | Merge
|
||||
$block_type_variant:ident, // Blinded | Full
|
||||
$is_default_with_empty_roots:block,
|
||||
$f:block,
|
||||
$g:block) => {
|
||||
impl<T: EthSpec> ExecPayload<T> for $wrapper_type<T> {
|
||||
fn block_type() -> BlockType {
|
||||
BlockType::$block_type_variant
|
||||
}
|
||||
|
||||
fn to_execution_payload_header(&self) -> ExecutionPayloadHeader<T> {
|
||||
ExecutionPayloadHeader::$fork_variant($wrapped_type_header::from(
|
||||
&self.$wrapped_field,
|
||||
))
|
||||
}
|
||||
|
||||
fn parent_hash(&self) -> ExecutionBlockHash {
|
||||
self.$wrapped_field.parent_hash
|
||||
}
|
||||
|
||||
fn prev_randao(&self) -> Hash256 {
|
||||
self.$wrapped_field.prev_randao
|
||||
}
|
||||
|
||||
fn block_number(&self) -> u64 {
|
||||
self.$wrapped_field.block_number
|
||||
}
|
||||
|
||||
fn timestamp(&self) -> u64 {
|
||||
self.$wrapped_field.timestamp
|
||||
}
|
||||
|
||||
fn block_hash(&self) -> ExecutionBlockHash {
|
||||
self.$wrapped_field.block_hash
|
||||
}
|
||||
|
||||
fn fee_recipient(&self) -> Address {
|
||||
self.$wrapped_field.fee_recipient
|
||||
}
|
||||
|
||||
fn gas_limit(&self) -> u64 {
|
||||
self.$wrapped_field.gas_limit
|
||||
}
|
||||
|
||||
fn is_default_with_zero_roots(&self) -> bool {
|
||||
self.$wrapped_field == $wrapped_type::default()
|
||||
}
|
||||
|
||||
fn is_default_with_empty_roots(&self) -> bool {
|
||||
let f = $is_default_with_empty_roots;
|
||||
f(self)
|
||||
}
|
||||
|
||||
fn transactions(&self) -> Option<&Transactions<T>> {
|
||||
let f = $f;
|
||||
f(self)
|
||||
}
|
||||
|
||||
fn withdrawals_root(&self) -> Result<Hash256, Error> {
|
||||
let g = $g;
|
||||
g(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: EthSpec> From<$wrapped_type<T>> for $wrapper_type<T> {
|
||||
fn from($wrapped_field: $wrapped_type<T>) -> Self {
|
||||
Self { $wrapped_field }
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! impl_exec_payload_for_fork {
|
||||
// BlindedPayloadMerge, FullPayloadMerge, ExecutionPayloadHeaderMerge, ExecutionPayloadMerge, Merge
|
||||
($wrapper_type_header:ident, $wrapper_type_full:ident, $wrapped_type_header:ident, $wrapped_type_full:ident, $fork_variant:ident) => {
|
||||
//*************** Blinded payload implementations ******************//
|
||||
|
||||
impl_exec_payload_common!(
|
||||
$wrapper_type_header, // BlindedPayloadMerge
|
||||
$wrapped_type_header, // ExecutionPayloadHeaderMerge
|
||||
$wrapped_type_full, // ExecutionPayloadMerge
|
||||
$wrapped_type_header, // ExecutionPayloadHeaderMerge
|
||||
execution_payload_header,
|
||||
$fork_variant, // Merge
|
||||
Blinded,
|
||||
{
|
||||
|wrapper: &$wrapper_type_header<T>| {
|
||||
wrapper.execution_payload_header
|
||||
== $wrapped_type_header::from(&$wrapped_type_full::default())
|
||||
}
|
||||
},
|
||||
{ |_| { None } },
|
||||
{
|
||||
let c: for<'a> fn(&'a $wrapper_type_header<T>) -> Result<Hash256, Error> =
|
||||
|payload: &$wrapper_type_header<T>| {
|
||||
let wrapper_ref_type = BlindedPayloadRef::$fork_variant(&payload);
|
||||
wrapper_ref_type.withdrawals_root()
|
||||
};
|
||||
c
|
||||
}
|
||||
);
|
||||
|
||||
impl<T: EthSpec> TryInto<$wrapper_type_header<T>> for BlindedPayload<T> {
|
||||
type Error = Error;
|
||||
|
||||
fn try_into(self) -> Result<$wrapper_type_header<T>, Self::Error> {
|
||||
match self {
|
||||
BlindedPayload::$fork_variant(payload) => Ok(payload),
|
||||
_ => Err(Error::IncorrectStateVariant),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: the `Default` implementation for `BlindedPayload` needs to be different from the `Default`
|
||||
// implementation for `ExecutionPayloadHeader` because payloads are checked for equality against the
|
||||
// default payload in `is_merge_transition_block` to determine whether the merge has occurred.
|
||||
//
|
||||
// The default `BlindedPayload` is therefore the payload header that results from blinding the
|
||||
// default `ExecutionPayload`, which differs from the default `ExecutionPayloadHeader` in that
|
||||
// its `transactions_root` is the hash of the empty list rather than 0x0.
|
||||
impl<T: EthSpec> Default for $wrapper_type_header<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
execution_payload_header: $wrapped_type_header::from(
|
||||
&$wrapped_type_full::default(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: EthSpec> TryFrom<ExecutionPayloadHeader<T>> for $wrapper_type_header<T> {
|
||||
type Error = Error;
|
||||
fn try_from(header: ExecutionPayloadHeader<T>) -> Result<Self, Self::Error> {
|
||||
match header {
|
||||
ExecutionPayloadHeader::$fork_variant(execution_payload_header) => {
|
||||
Ok(execution_payload_header.into())
|
||||
}
|
||||
_ => Err(Error::PayloadConversionLogicFlaw),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BlindedPayload* from CoW reference to ExecutionPayload* (hopefully just a reference).
|
||||
impl<'a, T: EthSpec> From<Cow<'a, $wrapped_type_full<T>>> for $wrapper_type_header<T> {
|
||||
fn from(execution_payload: Cow<'a, $wrapped_type_full<T>>) -> Self {
|
||||
Self {
|
||||
execution_payload_header: $wrapped_type_header::from(&*execution_payload),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//*************** Full payload implementations ******************//
|
||||
|
||||
impl_exec_payload_common!(
|
||||
$wrapper_type_full, // FullPayloadMerge
|
||||
$wrapped_type_full, // ExecutionPayloadMerge
|
||||
$wrapped_type_full, // ExecutionPayloadMerge
|
||||
$wrapped_type_header, // ExecutionPayloadHeaderMerge
|
||||
execution_payload,
|
||||
$fork_variant, // Merge
|
||||
Full,
|
||||
{
|
||||
|wrapper: &$wrapper_type_full<T>| {
|
||||
wrapper.execution_payload == $wrapped_type_full::default()
|
||||
}
|
||||
},
|
||||
{
|
||||
let c: for<'a> fn(&'a $wrapper_type_full<T>) -> Option<&'a Transactions<T>> =
|
||||
|payload: &$wrapper_type_full<T>| Some(&payload.execution_payload.transactions);
|
||||
c
|
||||
},
|
||||
{
|
||||
let c: for<'a> fn(&'a $wrapper_type_full<T>) -> Result<Hash256, Error> =
|
||||
|payload: &$wrapper_type_full<T>| {
|
||||
let wrapper_ref_type = FullPayloadRef::$fork_variant(&payload);
|
||||
wrapper_ref_type.withdrawals_root()
|
||||
};
|
||||
c
|
||||
}
|
||||
);
|
||||
|
||||
impl<T: EthSpec> Default for $wrapper_type_full<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
execution_payload: $wrapped_type_full::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FullPayload * from CoW reference to ExecutionPayload* (hopefully already owned).
|
||||
impl<'a, T: EthSpec> From<Cow<'a, $wrapped_type_full<T>>> for $wrapper_type_full<T> {
|
||||
fn from(execution_payload: Cow<'a, $wrapped_type_full<T>>) -> Self {
|
||||
Self {
|
||||
execution_payload: $wrapped_type_full::from(execution_payload.into_owned()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: EthSpec> TryFrom<ExecutionPayloadHeader<T>> for $wrapper_type_full<T> {
|
||||
type Error = Error;
|
||||
fn try_from(_: ExecutionPayloadHeader<T>) -> Result<Self, Self::Error> {
|
||||
Err(Error::PayloadConversionLogicFlaw)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: EthSpec> TryFrom<$wrapped_type_header<T>> for $wrapper_type_full<T> {
|
||||
type Error = Error;
|
||||
fn try_from(_: $wrapped_type_header<T>) -> Result<Self, Self::Error> {
|
||||
Err(Error::PayloadConversionLogicFlaw)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: EthSpec> TryInto<$wrapper_type_full<T>> for FullPayload<T> {
|
||||
type Error = Error;
|
||||
|
||||
fn try_into(self) -> Result<$wrapper_type_full<T>, Self::Error> {
|
||||
match self {
|
||||
FullPayload::$fork_variant(payload) => Ok(payload),
|
||||
_ => Err(Error::PayloadConversionLogicFlaw),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_exec_payload_for_fork!(
|
||||
BlindedPayloadMerge,
|
||||
FullPayloadMerge,
|
||||
ExecutionPayloadHeaderMerge,
|
||||
ExecutionPayloadMerge,
|
||||
Merge
|
||||
);
|
||||
impl_exec_payload_for_fork!(
|
||||
BlindedPayloadCapella,
|
||||
FullPayloadCapella,
|
||||
ExecutionPayloadHeaderCapella,
|
||||
ExecutionPayloadCapella,
|
||||
Capella
|
||||
);
|
||||
impl_exec_payload_for_fork!(
|
||||
BlindedPayloadDeneb,
|
||||
FullPayloadDeneb,
|
||||
ExecutionPayloadHeaderDeneb,
|
||||
ExecutionPayloadDeneb,
|
||||
Deneb
|
||||
);
|
||||
|
||||
impl<T: EthSpec> AbstractExecPayload<T> for BlindedPayload<T> {
|
||||
type Ref<'a> = BlindedPayloadRef<'a, T>;
|
||||
type Merge = BlindedPayloadMerge<T>;
|
||||
type Capella = BlindedPayloadCapella<T>;
|
||||
type Deneb = BlindedPayloadDeneb<T>;
|
||||
|
||||
fn default_at_fork(fork_name: ForkName) -> Result<Self, Error> {
|
||||
match fork_name {
|
||||
ForkName::Base | ForkName::Altair => Err(Error::IncorrectStateVariant),
|
||||
ForkName::Merge => Ok(BlindedPayloadMerge::default().into()),
|
||||
ForkName::Capella => Ok(BlindedPayloadCapella::default().into()),
|
||||
ForkName::Deneb => Ok(BlindedPayloadDeneb::default().into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: EthSpec> From<ExecutionPayload<T>> for BlindedPayload<T> {
|
||||
fn from(payload: ExecutionPayload<T>) -> Self {
|
||||
// This implementation is a bit wasteful in that it discards the payload body.
|
||||
// Required by the top-level constraint on AbstractExecPayload but could maybe be loosened
|
||||
// in future.
|
||||
map_execution_payload_into_blinded_payload!(payload, |inner, cons| cons(From::from(
|
||||
Cow::Owned(inner)
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: EthSpec> From<ExecutionPayloadHeader<T>> for BlindedPayload<T> {
|
||||
fn from(execution_payload_header: ExecutionPayloadHeader<T>) -> Self {
|
||||
match execution_payload_header {
|
||||
ExecutionPayloadHeader::Merge(execution_payload_header) => {
|
||||
Self::Merge(BlindedPayloadMerge {
|
||||
execution_payload_header,
|
||||
})
|
||||
}
|
||||
ExecutionPayloadHeader::Capella(execution_payload_header) => {
|
||||
Self::Capella(BlindedPayloadCapella {
|
||||
execution_payload_header,
|
||||
})
|
||||
}
|
||||
ExecutionPayloadHeader::Deneb(execution_payload_header) => {
|
||||
Self::Deneb(BlindedPayloadDeneb {
|
||||
execution_payload_header,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: EthSpec> From<BlindedPayload<T>> for ExecutionPayloadHeader<T> {
|
||||
fn from(blinded: BlindedPayload<T>) -> Self {
|
||||
match blinded {
|
||||
BlindedPayload::Merge(blinded_payload) => {
|
||||
ExecutionPayloadHeader::Merge(blinded_payload.execution_payload_header)
|
||||
}
|
||||
BlindedPayload::Capella(blinded_payload) => {
|
||||
ExecutionPayloadHeader::Capella(blinded_payload.execution_payload_header)
|
||||
}
|
||||
BlindedPayload::Deneb(blinded_payload) => {
|
||||
ExecutionPayloadHeader::Deneb(blinded_payload.execution_payload_header)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ use crate::case_result::compare_beacon_state_results_without_caches;
|
||||
use crate::decode::{ssz_decode_file, ssz_decode_file_with, ssz_decode_state, yaml_decode_file};
|
||||
use crate::testing_spec;
|
||||
use serde_derive::Deserialize;
|
||||
use ssz::Decode;
|
||||
use state_processing::{
|
||||
per_block_processing::{
|
||||
errors::BlockProcessingError,
|
||||
@@ -19,9 +20,9 @@ use state_processing::{
|
||||
use std::fmt::Debug;
|
||||
use std::path::Path;
|
||||
use types::{
|
||||
Attestation, AttesterSlashing, BeaconBlock, BeaconState, BlindedPayload, ChainSpec, Deposit,
|
||||
EthSpec, ExecutionPayload, ForkName, FullPayload, ProposerSlashing, SignedBlsToExecutionChange,
|
||||
SignedVoluntaryExit, SyncAggregate,
|
||||
map_fork_name, map_fork_name_with, Attestation, AttesterSlashing, BeaconBlock, BeaconBlockBody,
|
||||
BeaconState, BlindedPayload, ChainSpec, Deposit, EthSpec, ExecutionPayload, ForkName,
|
||||
FullPayload, ProposerSlashing, SignedBlsToExecutionChange, SignedVoluntaryExit, SyncAggregate,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Default, Deserialize)]
|
||||
@@ -259,13 +260,13 @@ impl<E: EthSpec> Operation<E> for SyncAggregate<E> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: EthSpec> Operation<E> for FullPayload<E> {
|
||||
impl<E: EthSpec> Operation<E> for BeaconBlockBody<E, FullPayload<E>> {
|
||||
fn handler_name() -> String {
|
||||
"execution_payload".into()
|
||||
}
|
||||
|
||||
fn filename() -> String {
|
||||
"execution_payload.ssz_snappy".into()
|
||||
"body.ssz_snappy".into()
|
||||
}
|
||||
|
||||
fn is_enabled_for_fork(fork_name: ForkName) -> bool {
|
||||
@@ -274,9 +275,8 @@ impl<E: EthSpec> Operation<E> for FullPayload<E> {
|
||||
|
||||
fn decode(path: &Path, fork_name: ForkName, _spec: &ChainSpec) -> Result<Self, Error> {
|
||||
ssz_decode_file_with(path, |bytes| {
|
||||
ExecutionPayload::from_ssz_bytes(bytes, fork_name)
|
||||
Ok(map_fork_name!(fork_name, Self, <_>::from_ssz_bytes(bytes)?))
|
||||
})
|
||||
.map(Into::into)
|
||||
}
|
||||
|
||||
fn apply_to(
|
||||
@@ -296,13 +296,13 @@ impl<E: EthSpec> Operation<E> for FullPayload<E> {
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<E: EthSpec> Operation<E> for BlindedPayload<E> {
|
||||
impl<E: EthSpec> Operation<E> for BeaconBlockBody<E, BlindedPayload<E>> {
|
||||
fn handler_name() -> String {
|
||||
"execution_payload".into()
|
||||
}
|
||||
|
||||
fn filename() -> String {
|
||||
"execution_payload.ssz_snappy".into()
|
||||
"body.ssz_snappy".into()
|
||||
}
|
||||
|
||||
fn is_enabled_for_fork(fork_name: ForkName) -> bool {
|
||||
@@ -311,9 +311,8 @@ impl<E: EthSpec> Operation<E> for BlindedPayload<E> {
|
||||
|
||||
fn decode(path: &Path, fork_name: ForkName, _spec: &ChainSpec) -> Result<Self, Error> {
|
||||
ssz_decode_file_with(path, |bytes| {
|
||||
ExecutionPayload::from_ssz_bytes(bytes, fork_name)
|
||||
Ok(map_fork_name!(fork_name, Self, <_>::from_ssz_bytes(bytes)?))
|
||||
})
|
||||
.map(Into::into)
|
||||
}
|
||||
|
||||
fn apply_to(
|
||||
|
||||
@@ -371,7 +371,7 @@ impl<E: GenericExecutionEngine> TestRig<E> {
|
||||
let status = self
|
||||
.ee_a
|
||||
.execution_layer
|
||||
.notify_new_payload(&valid_payload)
|
||||
.notify_new_payload(&valid_payload, None)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(status, PayloadStatus::Valid);
|
||||
@@ -424,7 +424,7 @@ impl<E: GenericExecutionEngine> TestRig<E> {
|
||||
let status = self
|
||||
.ee_a
|
||||
.execution_layer
|
||||
.notify_new_payload(&invalid_payload)
|
||||
.notify_new_payload(&invalid_payload, None)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(matches!(
|
||||
@@ -486,7 +486,7 @@ impl<E: GenericExecutionEngine> TestRig<E> {
|
||||
let status = self
|
||||
.ee_a
|
||||
.execution_layer
|
||||
.notify_new_payload(&second_payload)
|
||||
.notify_new_payload(&second_payload, None)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(status, PayloadStatus::Valid);
|
||||
@@ -533,7 +533,7 @@ impl<E: GenericExecutionEngine> TestRig<E> {
|
||||
let status = self
|
||||
.ee_b
|
||||
.execution_layer
|
||||
.notify_new_payload(&second_payload)
|
||||
.notify_new_payload(&second_payload, None)
|
||||
.await
|
||||
.unwrap();
|
||||
// TODO: we should remove the `Accepted` status here once Geth fixes it
|
||||
@@ -574,7 +574,7 @@ impl<E: GenericExecutionEngine> TestRig<E> {
|
||||
let status = self
|
||||
.ee_b
|
||||
.execution_layer
|
||||
.notify_new_payload(&valid_payload)
|
||||
.notify_new_payload(&valid_payload, None)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(status, PayloadStatus::Valid);
|
||||
@@ -588,7 +588,7 @@ impl<E: GenericExecutionEngine> TestRig<E> {
|
||||
let status = self
|
||||
.ee_b
|
||||
.execution_layer
|
||||
.notify_new_payload(&second_payload)
|
||||
.notify_new_payload(&second_payload, None)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(status, PayloadStatus::Valid);
|
||||
|
||||
Reference in New Issue
Block a user