mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-03 00:31:50 +00:00
merge with upstream
This commit is contained in:
@@ -7,11 +7,9 @@ use reqwest::StatusCode;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::IntoStaticStr;
|
||||
use superstruct::superstruct;
|
||||
#[cfg(feature = "withdrawals")]
|
||||
use types::Withdrawal;
|
||||
pub use types::{
|
||||
Address, EthSpec, ExecutionBlockHash, ExecutionPayload, ExecutionPayloadHeader, FixedVector,
|
||||
ForkName, Hash256, Uint256, VariableList,
|
||||
ForkName, Hash256, Uint256, VariableList, Withdrawal,
|
||||
};
|
||||
|
||||
pub mod auth;
|
||||
@@ -245,11 +243,11 @@ impl<T: EthSpec> From<ExecutionPayload<T>> for ExecutionBlockWithTransactions<T>
|
||||
|
||||
#[superstruct(
|
||||
variants(V1, V2),
|
||||
variant_attributes(derive(Clone, Debug, PartialEq),),
|
||||
variant_attributes(derive(Clone, Debug, Eq, Hash, PartialEq),),
|
||||
cast_error(ty = "Error", expr = "Error::IncorrectStateVariant"),
|
||||
partial_getter_error(ty = "Error", expr = "Error::IncorrectStateVariant")
|
||||
)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct PayloadAttributes {
|
||||
#[superstruct(getter(copy))]
|
||||
pub timestamp: u64,
|
||||
@@ -257,20 +255,33 @@ pub struct PayloadAttributes {
|
||||
pub prev_randao: Hash256,
|
||||
#[superstruct(getter(copy))]
|
||||
pub suggested_fee_recipient: Address,
|
||||
#[cfg(feature = "withdrawals")]
|
||||
#[superstruct(only(V2))]
|
||||
pub withdrawals: Option<Vec<Withdrawal>>,
|
||||
}
|
||||
|
||||
impl PayloadAttributes {
|
||||
pub fn new(
|
||||
timestamp: u64,
|
||||
prev_randao: Hash256,
|
||||
suggested_fee_recipient: Address,
|
||||
withdrawals: Option<Vec<Withdrawal>>,
|
||||
) -> Self {
|
||||
// this should always return the highest version
|
||||
PayloadAttributes::V2(PayloadAttributesV2 {
|
||||
timestamp,
|
||||
prev_randao,
|
||||
suggested_fee_recipient,
|
||||
withdrawals,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn downgrade_to_v1(self) -> Result<Self, Error> {
|
||||
match self {
|
||||
PayloadAttributes::V1(_) => Ok(self),
|
||||
PayloadAttributes::V2(v2) => {
|
||||
#[cfg(features = "withdrawals")]
|
||||
if v2.withdrawals.is_some() {
|
||||
return Err(Error::BadConversion(
|
||||
"Downgrading from PayloadAttributesV2 with non-null withdrawaals"
|
||||
"Downgrading from PayloadAttributesV2 with non-null withdrawals"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -375,8 +375,9 @@ pub struct JsonPayloadAttributes {
|
||||
pub timestamp: u64,
|
||||
pub prev_randao: Hash256,
|
||||
pub suggested_fee_recipient: Address,
|
||||
#[cfg(feature = "withdrawals")]
|
||||
#[superstruct(only(V2))]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(default)]
|
||||
pub withdrawals: Option<Vec<JsonWithdrawal>>,
|
||||
}
|
||||
|
||||
@@ -392,7 +393,6 @@ impl From<PayloadAttributes> for JsonPayloadAttributes {
|
||||
timestamp: pa.timestamp,
|
||||
prev_randao: pa.prev_randao,
|
||||
suggested_fee_recipient: pa.suggested_fee_recipient,
|
||||
#[cfg(feature = "withdrawals")]
|
||||
withdrawals: pa
|
||||
.withdrawals
|
||||
.map(|w| w.into_iter().map(Into::into).collect()),
|
||||
@@ -413,7 +413,6 @@ impl From<JsonPayloadAttributes> for PayloadAttributes {
|
||||
timestamp: jpa.timestamp,
|
||||
prev_randao: jpa.prev_randao,
|
||||
suggested_fee_recipient: jpa.suggested_fee_recipient,
|
||||
#[cfg(feature = "withdrawals")]
|
||||
withdrawals: jpa
|
||||
.withdrawals
|
||||
.map(|jw| jw.into_iter().map(Into::into).collect()),
|
||||
|
||||
@@ -16,6 +16,7 @@ use types::{Address, ExecutionBlockHash, Hash256};
|
||||
/// The number of payload IDs that will be stored for each `Engine`.
|
||||
///
|
||||
/// Since the size of each value is small (~100 bytes) a large number is used for safety.
|
||||
/// FIXME: check this assumption now that the key includes entire payload attributes which now includes withdrawals
|
||||
const PAYLOAD_ID_LRU_CACHE_SIZE: usize = 512;
|
||||
|
||||
/// Stores the remembered state of a engine.
|
||||
@@ -97,9 +98,7 @@ pub struct ForkchoiceState {
|
||||
#[derive(Hash, PartialEq, std::cmp::Eq)]
|
||||
struct PayloadIdCacheKey {
|
||||
pub head_block_hash: ExecutionBlockHash,
|
||||
pub timestamp: u64,
|
||||
pub prev_randao: Hash256,
|
||||
pub suggested_fee_recipient: Address,
|
||||
pub payload_attributes: PayloadAttributes,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -142,20 +141,13 @@ impl Engine {
|
||||
|
||||
pub async fn get_payload_id(
|
||||
&self,
|
||||
head_block_hash: ExecutionBlockHash,
|
||||
timestamp: u64,
|
||||
prev_randao: Hash256,
|
||||
suggested_fee_recipient: Address,
|
||||
head_block_hash: &ExecutionBlockHash,
|
||||
payload_attributes: &PayloadAttributes,
|
||||
) -> Option<PayloadId> {
|
||||
self.payload_id_cache
|
||||
.lock()
|
||||
.await
|
||||
.get(&PayloadIdCacheKey {
|
||||
head_block_hash,
|
||||
timestamp,
|
||||
prev_randao,
|
||||
suggested_fee_recipient,
|
||||
})
|
||||
.get(&PayloadIdCacheKey::new(head_block_hash, payload_attributes))
|
||||
.cloned()
|
||||
}
|
||||
|
||||
@@ -171,8 +163,8 @@ impl Engine {
|
||||
.await?;
|
||||
|
||||
if let Some(payload_id) = response.payload_id {
|
||||
if let Some(key) =
|
||||
payload_attributes.map(|pa| PayloadIdCacheKey::new(&forkchoice_state, &pa))
|
||||
if let Some(key) = payload_attributes
|
||||
.map(|pa| PayloadIdCacheKey::new(&forkchoice_state.head_block_hash, &pa))
|
||||
{
|
||||
self.payload_id_cache.lock().await.put(key, payload_id);
|
||||
} else {
|
||||
@@ -347,14 +339,11 @@ impl Engine {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: revisit this - do we need to key on withdrawals as well here?
|
||||
impl PayloadIdCacheKey {
|
||||
fn new(state: &ForkchoiceState, attributes: &PayloadAttributes) -> Self {
|
||||
fn new(head_block_hash: &ExecutionBlockHash, attributes: &PayloadAttributes) -> Self {
|
||||
Self {
|
||||
head_block_hash: state.head_block_hash,
|
||||
timestamp: attributes.timestamp(),
|
||||
prev_randao: attributes.prev_randao(),
|
||||
suggested_fee_recipient: attributes.suggested_fee_recipient(),
|
||||
head_block_hash: head_block_hash.clone(),
|
||||
payload_attributes: attributes.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ pub use engine_api::*;
|
||||
pub use engine_api::{http, http::deposit_methods, http::HttpJsonRpc};
|
||||
use engines::{Engine, EngineError};
|
||||
pub use engines::{EngineState, ForkchoiceState};
|
||||
use eth2::types::{builder_bid::SignedBuilderBid, ForkVersionedResponse};
|
||||
use fork_choice::ForkchoiceUpdateParameters;
|
||||
use lru::LruCache;
|
||||
use payload_status::process_payload_status;
|
||||
@@ -21,11 +22,13 @@ use serde::{Deserialize, Serialize};
|
||||
use slog::{crit, debug, error, info, trace, warn, Logger};
|
||||
use slot_clock::SlotClock;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
use std::future::Future;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
|
||||
use strum::AsRefStr;
|
||||
use task_executor::TaskExecutor;
|
||||
use tokio::{
|
||||
sync::{Mutex, MutexGuard, RwLock},
|
||||
@@ -34,12 +37,14 @@ use tokio::{
|
||||
use tokio_stream::wrappers::WatchStream;
|
||||
#[cfg(feature = "withdrawals")]
|
||||
use types::Withdrawal;
|
||||
use types::{AbstractExecPayload, Blob, ExecPayload, ExecutionPayloadEip4844, KzgCommitment};
|
||||
use types::{AbstractExecPayload, Blob, ExecPayload, KzgCommitment};
|
||||
use types::{
|
||||
BlindedPayload, BlockType, ChainSpec, Epoch, ExecutionBlockHash, ForkName,
|
||||
ProposerPreparationData, PublicKeyBytes, SignedBeaconBlock, Slot,
|
||||
ProposerPreparationData, PublicKeyBytes, Signature, SignedBeaconBlock, Slot, Uint256,
|
||||
};
|
||||
use types::{
|
||||
ExecutionPayload, ExecutionPayloadCapella, ExecutionPayloadEip4844, ExecutionPayloadMerge,
|
||||
};
|
||||
use types::{ExecutionPayload, ExecutionPayloadCapella, ExecutionPayloadMerge};
|
||||
|
||||
mod engine_api;
|
||||
mod engines;
|
||||
@@ -70,6 +75,14 @@ const DEFAULT_SUGGESTED_FEE_RECIPIENT: [u8; 20] =
|
||||
|
||||
const CONFIG_POLL_INTERVAL: Duration = Duration::from_secs(60);
|
||||
|
||||
/// A payload alongside some information about where it came from.
|
||||
enum ProvenancedPayload<P> {
|
||||
/// A good ol' fashioned farm-to-table payload from your local EE.
|
||||
Local(P),
|
||||
/// A payload from a builder (e.g. mev-boost).
|
||||
Builder(P),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
NoEngine,
|
||||
@@ -77,6 +90,7 @@ pub enum Error {
|
||||
ApiError(ApiError),
|
||||
Builder(builder_client::Error),
|
||||
NoHeaderFromBuilder,
|
||||
CannotProduceHeader,
|
||||
EngineError(Box<EngineError>),
|
||||
NotSynced,
|
||||
ShuttingDown,
|
||||
@@ -602,22 +616,16 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
///
|
||||
/// The result will be returned from the first node that returns successfully. No more nodes
|
||||
/// will be contacted.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn get_payload<Payload: AbstractExecPayload<T>>(
|
||||
&self,
|
||||
parent_hash: ExecutionBlockHash,
|
||||
timestamp: u64,
|
||||
prev_randao: Hash256,
|
||||
proposer_index: u64,
|
||||
payload_attributes: &PayloadAttributes,
|
||||
forkchoice_update_params: ForkchoiceUpdateParameters,
|
||||
builder_params: BuilderParams,
|
||||
current_fork: ForkName,
|
||||
#[cfg(feature = "withdrawals")] withdrawals: Option<Vec<Withdrawal>>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<BlockProposalContents<T, Payload>, Error> {
|
||||
let suggested_fee_recipient = self.get_suggested_fee_recipient(proposer_index).await;
|
||||
|
||||
match Payload::block_type() {
|
||||
let payload_result = match Payload::block_type() {
|
||||
BlockType::Blinded => {
|
||||
let _timer = metrics::start_timer_vec(
|
||||
&metrics::EXECUTION_LAYER_REQUEST_TIMES,
|
||||
@@ -625,14 +633,10 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
);
|
||||
self.get_blinded_payload(
|
||||
parent_hash,
|
||||
timestamp,
|
||||
prev_randao,
|
||||
suggested_fee_recipient,
|
||||
payload_attributes,
|
||||
forkchoice_update_params,
|
||||
builder_params,
|
||||
current_fork,
|
||||
#[cfg(feature = "withdrawals")]
|
||||
withdrawals,
|
||||
spec,
|
||||
)
|
||||
.await
|
||||
@@ -644,32 +648,58 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
);
|
||||
self.get_full_payload(
|
||||
parent_hash,
|
||||
timestamp,
|
||||
prev_randao,
|
||||
suggested_fee_recipient,
|
||||
payload_attributes,
|
||||
forkchoice_update_params,
|
||||
current_fork,
|
||||
#[cfg(feature = "withdrawals")]
|
||||
withdrawals,
|
||||
)
|
||||
.await
|
||||
.map(ProvenancedPayload::Local)
|
||||
}
|
||||
};
|
||||
|
||||
// Track some metrics and return the result.
|
||||
match payload_result {
|
||||
Ok(ProvenancedPayload::Local(block_proposal_contents)) => {
|
||||
metrics::inc_counter_vec(
|
||||
&metrics::EXECUTION_LAYER_GET_PAYLOAD_OUTCOME,
|
||||
&[metrics::SUCCESS],
|
||||
);
|
||||
metrics::inc_counter_vec(
|
||||
&metrics::EXECUTION_LAYER_GET_PAYLOAD_SOURCE,
|
||||
&[metrics::LOCAL],
|
||||
);
|
||||
Ok(block_proposal_contents)
|
||||
}
|
||||
Ok(ProvenancedPayload::Builder(block_proposal_contents)) => {
|
||||
metrics::inc_counter_vec(
|
||||
&metrics::EXECUTION_LAYER_GET_PAYLOAD_OUTCOME,
|
||||
&[metrics::SUCCESS],
|
||||
);
|
||||
metrics::inc_counter_vec(
|
||||
&metrics::EXECUTION_LAYER_GET_PAYLOAD_SOURCE,
|
||||
&[metrics::BUILDER],
|
||||
);
|
||||
Ok(block_proposal_contents)
|
||||
}
|
||||
Err(e) => {
|
||||
metrics::inc_counter_vec(
|
||||
&metrics::EXECUTION_LAYER_GET_PAYLOAD_OUTCOME,
|
||||
&[metrics::FAILURE],
|
||||
);
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn get_blinded_payload<Payload: AbstractExecPayload<T>>(
|
||||
&self,
|
||||
parent_hash: ExecutionBlockHash,
|
||||
timestamp: u64,
|
||||
prev_randao: Hash256,
|
||||
suggested_fee_recipient: Address,
|
||||
payload_attributes: &PayloadAttributes,
|
||||
forkchoice_update_params: ForkchoiceUpdateParameters,
|
||||
builder_params: BuilderParams,
|
||||
current_fork: ForkName,
|
||||
#[cfg(feature = "withdrawals")] withdrawals: Option<Vec<Withdrawal>>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<BlockProposalContents<T, Payload>, Error> {
|
||||
) -> Result<ProvenancedPayload<BlockProposalContents<T, Payload>>, Error> {
|
||||
if let Some(builder) = self.builder() {
|
||||
let slot = builder_params.slot;
|
||||
let pubkey = builder_params.pubkey;
|
||||
@@ -683,173 +713,238 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
"pubkey" => ?pubkey,
|
||||
"parent_hash" => ?parent_hash,
|
||||
);
|
||||
let (relay_result, local_result) = tokio::join!(
|
||||
builder.get_builder_header::<T, Payload>(slot, parent_hash, &pubkey),
|
||||
self.get_full_payload_caching(
|
||||
parent_hash,
|
||||
timestamp,
|
||||
prev_randao,
|
||||
suggested_fee_recipient,
|
||||
forkchoice_update_params,
|
||||
current_fork,
|
||||
#[cfg(feature = "withdrawals")]
|
||||
withdrawals,
|
||||
)
|
||||
|
||||
// Wait for the builder *and* local EL to produce a payload (or return an error).
|
||||
let ((relay_result, relay_duration), (local_result, local_duration)) = tokio::join!(
|
||||
timed_future(metrics::GET_BLINDED_PAYLOAD_BUILDER, async {
|
||||
builder
|
||||
.get_builder_header::<T, Payload>(slot, parent_hash, &pubkey)
|
||||
.await
|
||||
}),
|
||||
timed_future(metrics::GET_BLINDED_PAYLOAD_LOCAL, async {
|
||||
self.get_full_payload_caching::<Payload>(
|
||||
parent_hash,
|
||||
payload_attributes,
|
||||
forkchoice_update_params,
|
||||
current_fork,
|
||||
)
|
||||
.await
|
||||
})
|
||||
);
|
||||
|
||||
info!(
|
||||
self.log(),
|
||||
"Requested blinded execution payload";
|
||||
"relay_fee_recipient" => match &relay_result {
|
||||
Ok(Some(r)) => format!("{:?}", r.data.message.header.fee_recipient()),
|
||||
Ok(None) => "empty response".to_string(),
|
||||
Err(_) => "request failed".to_string(),
|
||||
},
|
||||
"relay_response_ms" => relay_duration.as_millis(),
|
||||
"local_fee_recipient" => match &local_result {
|
||||
Ok(proposal_contents) => format!("{:?}", proposal_contents.payload().fee_recipient()),
|
||||
Err(_) => "request failed".to_string()
|
||||
},
|
||||
"local_response_ms" => local_duration.as_millis(),
|
||||
"parent_hash" => ?parent_hash,
|
||||
);
|
||||
|
||||
return match (relay_result, local_result) {
|
||||
(Err(e), Ok(local)) => {
|
||||
warn!(
|
||||
self.log(),
|
||||
"Unable to retrieve a payload from a connected \
|
||||
builder, falling back to the local execution client: {e:?}"
|
||||
"Builder error when requesting payload";
|
||||
"info" => "falling back to local execution client",
|
||||
"relay_error" => ?e,
|
||||
"local_block_hash" => ?local.payload().block_hash(),
|
||||
"parent_hash" => ?parent_hash,
|
||||
);
|
||||
Ok(local)
|
||||
Ok(ProvenancedPayload::Local(local))
|
||||
}
|
||||
(Ok(None), Ok(local)) => {
|
||||
info!(
|
||||
self.log(),
|
||||
"No payload provided by connected builder. \
|
||||
Attempting to propose through local execution engine"
|
||||
"Builder did not return a payload";
|
||||
"info" => "falling back to local execution client",
|
||||
"local_block_hash" => ?local.payload().block_hash(),
|
||||
"parent_hash" => ?parent_hash,
|
||||
);
|
||||
Ok(local)
|
||||
Ok(ProvenancedPayload::Local(local))
|
||||
}
|
||||
(Ok(Some(relay)), Ok(local)) => {
|
||||
let local_payload = local.payload();
|
||||
let is_signature_valid = relay.data.verify_signature(spec);
|
||||
let header = relay.data.message.header;
|
||||
let header = &relay.data.message.header;
|
||||
|
||||
info!(
|
||||
self.log(),
|
||||
"Received a payload header from the connected builder";
|
||||
"block_hash" => ?header.block_hash(),
|
||||
"Received local and builder payloads";
|
||||
"relay_block_hash" => ?header.block_hash(),
|
||||
"local_block_hash" => ?local.payload().block_hash(),
|
||||
"parent_hash" => ?parent_hash,
|
||||
);
|
||||
|
||||
let relay_value = relay.data.message.value;
|
||||
let configured_value = self.inner.builder_profit_threshold;
|
||||
if relay_value < configured_value {
|
||||
info!(
|
||||
self.log(),
|
||||
"The value offered by the connected builder does not meet \
|
||||
the configured profit threshold. Using local payload.";
|
||||
"configured_value" => ?configured_value, "relay_value" => ?relay_value
|
||||
);
|
||||
Ok(local)
|
||||
} else if header.parent_hash() != parent_hash {
|
||||
warn!(
|
||||
self.log(),
|
||||
"Invalid parent hash from connected builder, \
|
||||
falling back to local execution engine."
|
||||
);
|
||||
Ok(local)
|
||||
} else if header.prev_randao() != prev_randao {
|
||||
warn!(
|
||||
self.log(),
|
||||
"Invalid prev randao from connected builder, \
|
||||
falling back to local execution engine."
|
||||
);
|
||||
Ok(local)
|
||||
} else if header.timestamp() != local_payload.timestamp() {
|
||||
warn!(
|
||||
self.log(),
|
||||
"Invalid timestamp from connected builder, \
|
||||
falling back to local execution engine."
|
||||
);
|
||||
Ok(local)
|
||||
} else if header.block_number() != local_payload.block_number() {
|
||||
warn!(
|
||||
self.log(),
|
||||
"Invalid block number from connected builder, \
|
||||
falling back to local execution engine."
|
||||
);
|
||||
Ok(local)
|
||||
} else if !matches!(relay.version, Some(ForkName::Merge)) {
|
||||
// Once fork information is added to the payload, we will need to
|
||||
// check that the local and relay payloads match. At this point, if
|
||||
// we are requesting a payload at all, we have to assume this is
|
||||
// the Bellatrix fork.
|
||||
warn!(
|
||||
self.log(),
|
||||
"Invalid fork from connected builder, falling \
|
||||
back to local execution engine."
|
||||
);
|
||||
Ok(local)
|
||||
} else if !is_signature_valid {
|
||||
let pubkey_bytes = relay.data.message.pubkey;
|
||||
warn!(self.log(), "Invalid signature for pubkey {pubkey_bytes} on \
|
||||
bid from connected builder, falling back to local execution engine.");
|
||||
Ok(local)
|
||||
} else {
|
||||
if header.fee_recipient() != suggested_fee_recipient {
|
||||
match verify_builder_bid(
|
||||
&relay,
|
||||
parent_hash,
|
||||
payload_attributes.prev_randao(),
|
||||
payload_attributes.timestamp(),
|
||||
Some(local.payload().block_number()),
|
||||
self.inner.builder_profit_threshold,
|
||||
spec,
|
||||
) {
|
||||
Ok(()) => Ok(ProvenancedPayload::Builder(
|
||||
//FIXME(sean) the builder API needs to be updated
|
||||
// NOTE the comment above was removed in the
|
||||
// rebase with unstable.. I think it goes
|
||||
// here now?
|
||||
BlockProposalContents::Payload(relay.data.message.header),
|
||||
)),
|
||||
Err(reason) if !reason.payload_invalid() => {
|
||||
info!(
|
||||
self.log(),
|
||||
"Fee recipient from connected builder does \
|
||||
not match, using it anyways."
|
||||
"Builder payload ignored";
|
||||
"info" => "using local payload",
|
||||
"reason" => %reason,
|
||||
"relay_block_hash" => ?header.block_hash(),
|
||||
"parent_hash" => ?parent_hash,
|
||||
);
|
||||
Ok(ProvenancedPayload::Local(local))
|
||||
}
|
||||
Err(reason) => {
|
||||
metrics::inc_counter_vec(
|
||||
&metrics::EXECUTION_LAYER_GET_PAYLOAD_BUILDER_REJECTIONS,
|
||||
&[reason.as_ref().as_ref()],
|
||||
);
|
||||
warn!(
|
||||
self.log(),
|
||||
"Builder returned invalid payload";
|
||||
"info" => "using local payload",
|
||||
"reason" => %reason,
|
||||
"relay_block_hash" => ?header.block_hash(),
|
||||
"parent_hash" => ?parent_hash,
|
||||
);
|
||||
Ok(ProvenancedPayload::Local(local))
|
||||
}
|
||||
//FIXME(sean) the builder API needs to be updated
|
||||
Ok(BlockProposalContents::Payload(header))
|
||||
}
|
||||
}
|
||||
(relay_result, Err(local_error)) => {
|
||||
warn!(self.log(), "Failure from local execution engine. Attempting to \
|
||||
propose through connected builder"; "error" => ?local_error);
|
||||
relay_result
|
||||
.map_err(Error::Builder)?
|
||||
.ok_or(Error::NoHeaderFromBuilder)
|
||||
.map(|d| {
|
||||
(Ok(Some(relay)), Err(local_error)) => {
|
||||
let header = &relay.data.message.header;
|
||||
|
||||
info!(
|
||||
self.log(),
|
||||
"Received builder payload with local error";
|
||||
"relay_block_hash" => ?header.block_hash(),
|
||||
"local_error" => ?local_error,
|
||||
"parent_hash" => ?parent_hash,
|
||||
);
|
||||
|
||||
match verify_builder_bid(
|
||||
&relay,
|
||||
parent_hash,
|
||||
payload_attributes.prev_randao(),
|
||||
payload_attributes.timestamp(),
|
||||
None,
|
||||
self.inner.builder_profit_threshold,
|
||||
spec,
|
||||
) {
|
||||
Ok(()) => Ok(ProvenancedPayload::Builder(
|
||||
//FIXME(sean) the builder API needs to be updated
|
||||
BlockProposalContents::Payload(d.data.message.header)
|
||||
})
|
||||
// NOTE the comment above was removed in the
|
||||
// rebase with unstable.. I think it goes
|
||||
// here now?
|
||||
BlockProposalContents::Payload(relay.data.message.header),
|
||||
)),
|
||||
// If the payload is valid then use it. The local EE failed
|
||||
// to produce a payload so we have no alternative.
|
||||
Err(e) if !e.payload_invalid() => Ok(ProvenancedPayload::Builder(
|
||||
//FIXME(sean) the builder API needs to be updated
|
||||
// NOTE the comment above was removed in the
|
||||
// rebase with unstable.. I think it goes
|
||||
// here now?
|
||||
BlockProposalContents::Payload(relay.data.message.header),
|
||||
)),
|
||||
Err(reason) => {
|
||||
metrics::inc_counter_vec(
|
||||
&metrics::EXECUTION_LAYER_GET_PAYLOAD_BUILDER_REJECTIONS,
|
||||
&[reason.as_ref().as_ref()],
|
||||
);
|
||||
crit!(
|
||||
self.log(),
|
||||
"Builder returned invalid payload";
|
||||
"info" => "no local payload either - unable to propose block",
|
||||
"reason" => %reason,
|
||||
"relay_block_hash" => ?header.block_hash(),
|
||||
"parent_hash" => ?parent_hash,
|
||||
);
|
||||
Err(Error::CannotProduceHeader)
|
||||
}
|
||||
}
|
||||
}
|
||||
(Err(relay_error), Err(local_error)) => {
|
||||
crit!(
|
||||
self.log(),
|
||||
"Unable to produce execution payload";
|
||||
"info" => "the local EL and builder both failed - unable to propose block",
|
||||
"relay_error" => ?relay_error,
|
||||
"local_error" => ?local_error,
|
||||
"parent_hash" => ?parent_hash,
|
||||
);
|
||||
|
||||
Err(Error::CannotProduceHeader)
|
||||
}
|
||||
(Ok(None), Err(local_error)) => {
|
||||
crit!(
|
||||
self.log(),
|
||||
"Unable to produce execution payload";
|
||||
"info" => "the local EL failed and the builder returned nothing - \
|
||||
the block proposal will be missed",
|
||||
"local_error" => ?local_error,
|
||||
"parent_hash" => ?parent_hash,
|
||||
);
|
||||
|
||||
Err(Error::CannotProduceHeader)
|
||||
}
|
||||
};
|
||||
}
|
||||
ChainHealth::Unhealthy(condition) => {
|
||||
info!(self.log(), "Due to poor chain health the local execution engine will be used \
|
||||
for payload construction. To adjust chain health conditions \
|
||||
Use `builder-fallback` prefixed flags";
|
||||
"failed_condition" => ?condition)
|
||||
}
|
||||
ChainHealth::Unhealthy(condition) => info!(
|
||||
self.log(),
|
||||
"Chain is unhealthy, using local payload";
|
||||
"info" => "this helps protect the network. the --builder-fallback flags \
|
||||
can adjust the expected health conditions.",
|
||||
"failed_condition" => ?condition
|
||||
),
|
||||
// Intentional no-op, so we never attempt builder API proposals pre-merge.
|
||||
ChainHealth::PreMerge => (),
|
||||
ChainHealth::Optimistic => info!(self.log(), "The local execution engine is syncing \
|
||||
so the builder network cannot safely be used. Attempting \
|
||||
to build a block with the local execution engine"),
|
||||
ChainHealth::Optimistic => info!(
|
||||
self.log(),
|
||||
"Chain is optimistic; can't build payload";
|
||||
"info" => "the local execution engine is syncing and the builder network \
|
||||
cannot safely be used - unable to propose block"
|
||||
),
|
||||
}
|
||||
}
|
||||
self.get_full_payload_caching(
|
||||
parent_hash,
|
||||
timestamp,
|
||||
prev_randao,
|
||||
suggested_fee_recipient,
|
||||
payload_attributes,
|
||||
forkchoice_update_params,
|
||||
current_fork,
|
||||
#[cfg(feature = "withdrawals")]
|
||||
withdrawals,
|
||||
)
|
||||
.await
|
||||
.map(ProvenancedPayload::Local)
|
||||
}
|
||||
|
||||
/// Get a full payload without caching its result in the execution layer's payload cache.
|
||||
async fn get_full_payload<Payload: AbstractExecPayload<T>>(
|
||||
&self,
|
||||
parent_hash: ExecutionBlockHash,
|
||||
timestamp: u64,
|
||||
prev_randao: Hash256,
|
||||
suggested_fee_recipient: Address,
|
||||
payload_attributes: &PayloadAttributes,
|
||||
forkchoice_update_params: ForkchoiceUpdateParameters,
|
||||
current_fork: ForkName,
|
||||
#[cfg(feature = "withdrawals")] withdrawals: Option<Vec<Withdrawal>>,
|
||||
) -> Result<BlockProposalContents<T, Payload>, Error> {
|
||||
self.get_full_payload_with(
|
||||
parent_hash,
|
||||
timestamp,
|
||||
prev_randao,
|
||||
suggested_fee_recipient,
|
||||
payload_attributes,
|
||||
forkchoice_update_params,
|
||||
current_fork,
|
||||
#[cfg(feature = "withdrawals")]
|
||||
withdrawals,
|
||||
noop,
|
||||
)
|
||||
.await
|
||||
@@ -859,22 +954,15 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
async fn get_full_payload_caching<Payload: AbstractExecPayload<T>>(
|
||||
&self,
|
||||
parent_hash: ExecutionBlockHash,
|
||||
timestamp: u64,
|
||||
prev_randao: Hash256,
|
||||
suggested_fee_recipient: Address,
|
||||
payload_attributes: &PayloadAttributes,
|
||||
forkchoice_update_params: ForkchoiceUpdateParameters,
|
||||
current_fork: ForkName,
|
||||
#[cfg(feature = "withdrawals")] withdrawals: Option<Vec<Withdrawal>>,
|
||||
) -> Result<BlockProposalContents<T, Payload>, Error> {
|
||||
self.get_full_payload_with(
|
||||
parent_hash,
|
||||
timestamp,
|
||||
prev_randao,
|
||||
suggested_fee_recipient,
|
||||
payload_attributes,
|
||||
forkchoice_update_params,
|
||||
current_fork,
|
||||
#[cfg(feature = "withdrawals")]
|
||||
withdrawals,
|
||||
Self::cache_payload,
|
||||
)
|
||||
.await
|
||||
@@ -883,20 +971,15 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
async fn get_full_payload_with<Payload: AbstractExecPayload<T>>(
|
||||
&self,
|
||||
parent_hash: ExecutionBlockHash,
|
||||
timestamp: u64,
|
||||
prev_randao: Hash256,
|
||||
suggested_fee_recipient: Address,
|
||||
payload_attributes: &PayloadAttributes,
|
||||
forkchoice_update_params: ForkchoiceUpdateParameters,
|
||||
current_fork: ForkName,
|
||||
#[cfg(feature = "withdrawals")] withdrawals: Option<Vec<Withdrawal>>,
|
||||
f: fn(&ExecutionLayer<T>, &ExecutionPayload<T>) -> Option<ExecutionPayload<T>>,
|
||||
) -> Result<BlockProposalContents<T, Payload>, Error> {
|
||||
#[cfg(feature = "withdrawals")]
|
||||
let withdrawals_ref = &withdrawals;
|
||||
self.engine()
|
||||
.request(move |engine| async move {
|
||||
let payload_id = if let Some(id) = engine
|
||||
.get_payload_id(parent_hash, timestamp, prev_randao, suggested_fee_recipient)
|
||||
.get_payload_id(&parent_hash, payload_attributes)
|
||||
.await
|
||||
{
|
||||
// The payload id has been cached for this engine.
|
||||
@@ -921,20 +1004,11 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
.finalized_hash
|
||||
.unwrap_or_else(ExecutionBlockHash::zero),
|
||||
};
|
||||
// This must always be the latest PayloadAttributes
|
||||
// FIXME: How to non-capella EIP4844 testnets handle this?
|
||||
let payload_attributes = PayloadAttributes::V2(PayloadAttributesV2 {
|
||||
timestamp,
|
||||
prev_randao,
|
||||
suggested_fee_recipient,
|
||||
#[cfg(feature = "withdrawals")]
|
||||
withdrawals: withdrawals_ref.clone(),
|
||||
});
|
||||
|
||||
let response = engine
|
||||
.notify_forkchoice_updated(
|
||||
fork_choice_state,
|
||||
Some(payload_attributes),
|
||||
Some(payload_attributes.clone()),
|
||||
self.log(),
|
||||
)
|
||||
.await?;
|
||||
@@ -963,9 +1037,9 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
debug!(
|
||||
self.log(),
|
||||
"Issuing engine_getBlobsBundle";
|
||||
"suggested_fee_recipient" => ?suggested_fee_recipient,
|
||||
"prev_randao" => ?prev_randao,
|
||||
"timestamp" => timestamp,
|
||||
"suggested_fee_recipient" => ?payload_attributes.suggested_fee_recipient(),
|
||||
"prev_randao" => ?payload_attributes.prev_randao(),
|
||||
"timestamp" => payload_attributes.timestamp(),
|
||||
"parent_hash" => ?parent_hash,
|
||||
);
|
||||
Some(engine.api.get_blobs_bundle_v1::<T>(payload_id).await)
|
||||
@@ -976,16 +1050,16 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
debug!(
|
||||
self.log(),
|
||||
"Issuing engine_getPayload";
|
||||
"suggested_fee_recipient" => ?suggested_fee_recipient,
|
||||
"prev_randao" => ?prev_randao,
|
||||
"timestamp" => timestamp,
|
||||
"suggested_fee_recipient" => ?payload_attributes.suggested_fee_recipient(),
|
||||
"prev_randao" => ?payload_attributes.prev_randao(),
|
||||
"timestamp" => payload_attributes.timestamp(),
|
||||
"parent_hash" => ?parent_hash,
|
||||
);
|
||||
engine.api.get_payload::<T>(current_fork, payload_id).await
|
||||
};
|
||||
let (blob, payload) = tokio::join!(blob_fut, payload_fut);
|
||||
let payload = payload.map(|full_payload| {
|
||||
if full_payload.fee_recipient() != suggested_fee_recipient {
|
||||
if full_payload.fee_recipient() != payload_attributes.suggested_fee_recipient() {
|
||||
error!(
|
||||
self.log(),
|
||||
"Inconsistent fee recipient";
|
||||
@@ -995,7 +1069,7 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
ensure that the value of suggested_fee_recipient is set correctly and \
|
||||
that the Execution Engine is trusted.",
|
||||
"fee_recipient" => ?full_payload.fee_recipient(),
|
||||
"suggested_fee_recipient" => ?suggested_fee_recipient,
|
||||
"suggested_fee_recipient" => ?payload_attributes.suggested_fee_recipient(),
|
||||
);
|
||||
}
|
||||
if f(self, &full_payload).is_some() {
|
||||
@@ -1597,18 +1671,223 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
"Sending block to builder";
|
||||
"root" => ?block_root,
|
||||
);
|
||||
|
||||
if let Some(builder) = self.builder() {
|
||||
builder
|
||||
.post_builder_blinded_blocks(block)
|
||||
.await
|
||||
.map_err(Error::Builder)
|
||||
.map(|d| d.data)
|
||||
let (payload_result, duration) =
|
||||
timed_future(metrics::POST_BLINDED_PAYLOAD_BUILDER, async {
|
||||
builder
|
||||
.post_builder_blinded_blocks(block)
|
||||
.await
|
||||
.map_err(Error::Builder)
|
||||
.map(|d| d.data)
|
||||
})
|
||||
.await;
|
||||
|
||||
match &payload_result {
|
||||
Ok(payload) => {
|
||||
metrics::inc_counter_vec(
|
||||
&metrics::EXECUTION_LAYER_BUILDER_REVEAL_PAYLOAD_OUTCOME,
|
||||
&[metrics::SUCCESS],
|
||||
);
|
||||
info!(
|
||||
self.log(),
|
||||
"Builder successfully revealed payload";
|
||||
"relay_response_ms" => duration.as_millis(),
|
||||
"block_root" => ?block_root,
|
||||
"fee_recipient" => ?payload.fee_recipient(),
|
||||
"block_hash" => ?payload.block_hash(),
|
||||
"parent_hash" => ?payload.parent_hash()
|
||||
)
|
||||
}
|
||||
Err(e) => {
|
||||
metrics::inc_counter_vec(
|
||||
&metrics::EXECUTION_LAYER_BUILDER_REVEAL_PAYLOAD_OUTCOME,
|
||||
&[metrics::FAILURE],
|
||||
);
|
||||
crit!(
|
||||
self.log(),
|
||||
"Builder failed to reveal payload";
|
||||
"info" => "this relay failure may cause a missed proposal",
|
||||
"error" => ?e,
|
||||
"relay_response_ms" => duration.as_millis(),
|
||||
"block_root" => ?block_root,
|
||||
"parent_hash" => ?block
|
||||
.message()
|
||||
.execution_payload()
|
||||
.map(|payload| format!("{}", payload.parent_hash()))
|
||||
.unwrap_or_else(|_| "unknown".to_string())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
payload_result
|
||||
} else {
|
||||
Err(Error::NoPayloadBuilder)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(AsRefStr)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
enum InvalidBuilderPayload {
|
||||
LowValue {
|
||||
profit_threshold: Uint256,
|
||||
payload_value: Uint256,
|
||||
},
|
||||
ParentHash {
|
||||
payload: ExecutionBlockHash,
|
||||
expected: ExecutionBlockHash,
|
||||
},
|
||||
PrevRandao {
|
||||
payload: Hash256,
|
||||
expected: Hash256,
|
||||
},
|
||||
Timestamp {
|
||||
payload: u64,
|
||||
expected: u64,
|
||||
},
|
||||
BlockNumber {
|
||||
payload: u64,
|
||||
expected: Option<u64>,
|
||||
},
|
||||
Fork {
|
||||
payload: Option<ForkName>,
|
||||
expected: ForkName,
|
||||
},
|
||||
Signature {
|
||||
signature: Signature,
|
||||
pubkey: PublicKeyBytes,
|
||||
},
|
||||
}
|
||||
|
||||
impl InvalidBuilderPayload {
|
||||
/// Returns `true` if a payload is objectively invalid and should never be included on chain.
|
||||
fn payload_invalid(&self) -> bool {
|
||||
match self {
|
||||
// A low-value payload isn't invalid, it should just be avoided if possible.
|
||||
InvalidBuilderPayload::LowValue { .. } => false,
|
||||
InvalidBuilderPayload::ParentHash { .. } => true,
|
||||
InvalidBuilderPayload::PrevRandao { .. } => true,
|
||||
InvalidBuilderPayload::Timestamp { .. } => true,
|
||||
InvalidBuilderPayload::BlockNumber { .. } => true,
|
||||
InvalidBuilderPayload::Fork { .. } => true,
|
||||
InvalidBuilderPayload::Signature { .. } => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for InvalidBuilderPayload {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
InvalidBuilderPayload::LowValue {
|
||||
profit_threshold,
|
||||
payload_value,
|
||||
} => write!(
|
||||
f,
|
||||
"payload value of {} does not meet user-configured profit-threshold of {}",
|
||||
payload_value, profit_threshold
|
||||
),
|
||||
InvalidBuilderPayload::ParentHash { payload, expected } => {
|
||||
write!(f, "payload block hash was {} not {}", payload, expected)
|
||||
}
|
||||
InvalidBuilderPayload::PrevRandao { payload, expected } => {
|
||||
write!(f, "payload prev randao was {} not {}", payload, expected)
|
||||
}
|
||||
InvalidBuilderPayload::Timestamp { payload, expected } => {
|
||||
write!(f, "payload timestamp was {} not {}", payload, expected)
|
||||
}
|
||||
InvalidBuilderPayload::BlockNumber { payload, expected } => {
|
||||
write!(f, "payload block number was {} not {:?}", payload, expected)
|
||||
}
|
||||
InvalidBuilderPayload::Fork { payload, expected } => {
|
||||
write!(f, "payload fork was {:?} not {}", payload, expected)
|
||||
}
|
||||
InvalidBuilderPayload::Signature { signature, pubkey } => write!(
|
||||
f,
|
||||
"invalid payload signature {} for pubkey {}",
|
||||
signature, pubkey
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform some cursory, non-exhaustive validation of the bid returned from the builder.
|
||||
fn verify_builder_bid<T: EthSpec, Payload: AbstractExecPayload<T>>(
|
||||
bid: &ForkVersionedResponse<SignedBuilderBid<T, Payload>>,
|
||||
parent_hash: ExecutionBlockHash,
|
||||
prev_randao: Hash256,
|
||||
timestamp: u64,
|
||||
block_number: Option<u64>,
|
||||
profit_threshold: Uint256,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Box<InvalidBuilderPayload>> {
|
||||
let is_signature_valid = bid.data.verify_signature(spec);
|
||||
let header = &bid.data.message.header;
|
||||
let payload_value = bid.data.message.value;
|
||||
|
||||
// Avoid logging values that we can't represent with our Prometheus library.
|
||||
let payload_value_gwei = bid.data.message.value / 1_000_000_000;
|
||||
if payload_value_gwei <= Uint256::from(i64::max_value()) {
|
||||
metrics::set_gauge_vec(
|
||||
&metrics::EXECUTION_LAYER_PAYLOAD_BIDS,
|
||||
&[metrics::BUILDER],
|
||||
payload_value_gwei.low_u64() as i64,
|
||||
);
|
||||
}
|
||||
|
||||
if payload_value < profit_threshold {
|
||||
Err(Box::new(InvalidBuilderPayload::LowValue {
|
||||
profit_threshold,
|
||||
payload_value,
|
||||
}))
|
||||
} else if header.parent_hash() != parent_hash {
|
||||
Err(Box::new(InvalidBuilderPayload::ParentHash {
|
||||
payload: header.parent_hash(),
|
||||
expected: parent_hash,
|
||||
}))
|
||||
} else if header.prev_randao() != prev_randao {
|
||||
Err(Box::new(InvalidBuilderPayload::PrevRandao {
|
||||
payload: header.prev_randao(),
|
||||
expected: prev_randao,
|
||||
}))
|
||||
} else if header.timestamp() != timestamp {
|
||||
Err(Box::new(InvalidBuilderPayload::Timestamp {
|
||||
payload: header.timestamp(),
|
||||
expected: timestamp,
|
||||
}))
|
||||
} else if block_number.map_or(false, |n| n != header.block_number()) {
|
||||
Err(Box::new(InvalidBuilderPayload::BlockNumber {
|
||||
payload: header.block_number(),
|
||||
expected: block_number,
|
||||
}))
|
||||
} else if !matches!(bid.version, Some(ForkName::Merge)) {
|
||||
// Once fork information is added to the payload, we will need to
|
||||
// check that the local and relay payloads match. At this point, if
|
||||
// we are requesting a payload at all, we have to assume this is
|
||||
// the Bellatrix fork.
|
||||
Err(Box::new(InvalidBuilderPayload::Fork {
|
||||
payload: bid.version,
|
||||
expected: ForkName::Merge,
|
||||
}))
|
||||
} else if !is_signature_valid {
|
||||
Err(Box::new(InvalidBuilderPayload::Signature {
|
||||
signature: bid.data.signature.clone(),
|
||||
pubkey: bid.data.message.pubkey,
|
||||
}))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// A helper function to record the time it takes to execute a future.
|
||||
async fn timed_future<F: Future<Output = T>, T>(metric: &str, future: F) -> (T, Duration) {
|
||||
let start = Instant::now();
|
||||
let result = future.await;
|
||||
let duration = start.elapsed();
|
||||
metrics::observe_timer_vec(&metrics::EXECUTION_LAYER_REQUEST_TIMES, &[metric], duration);
|
||||
(result, duration)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
@@ -4,10 +4,17 @@ pub const HIT: &str = "hit";
|
||||
pub const MISS: &str = "miss";
|
||||
pub const GET_PAYLOAD: &str = "get_payload";
|
||||
pub const GET_BLINDED_PAYLOAD: &str = "get_blinded_payload";
|
||||
pub const GET_BLINDED_PAYLOAD_LOCAL: &str = "get_blinded_payload_local";
|
||||
pub const GET_BLINDED_PAYLOAD_BUILDER: &str = "get_blinded_payload_builder";
|
||||
pub const POST_BLINDED_PAYLOAD_BUILDER: &str = "post_blinded_payload_builder";
|
||||
pub const NEW_PAYLOAD: &str = "new_payload";
|
||||
pub const FORKCHOICE_UPDATED: &str = "forkchoice_updated";
|
||||
pub const GET_TERMINAL_POW_BLOCK_HASH: &str = "get_terminal_pow_block_hash";
|
||||
pub const IS_VALID_TERMINAL_POW_BLOCK_HASH: &str = "is_valid_terminal_pow_block_hash";
|
||||
pub const LOCAL: &str = "local";
|
||||
pub const BUILDER: &str = "builder";
|
||||
pub const SUCCESS: &str = "success";
|
||||
pub const FAILURE: &str = "failure";
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref EXECUTION_LAYER_PROPOSER_INSERTED: Result<IntCounter> = try_create_int_counter(
|
||||
@@ -18,9 +25,11 @@ lazy_static::lazy_static! {
|
||||
"execution_layer_proposer_data_updated",
|
||||
"Count of times new proposer data is supplied",
|
||||
);
|
||||
pub static ref EXECUTION_LAYER_REQUEST_TIMES: Result<HistogramVec> = try_create_histogram_vec(
|
||||
pub static ref EXECUTION_LAYER_REQUEST_TIMES: Result<HistogramVec> =
|
||||
try_create_histogram_vec_with_buckets(
|
||||
"execution_layer_request_times",
|
||||
"Duration of calls to ELs",
|
||||
decimal_buckets(-2, 1),
|
||||
&["method"]
|
||||
);
|
||||
pub static ref EXECUTION_LAYER_PAYLOAD_ATTRIBUTES_LOOKAHEAD: Result<Histogram> = try_create_histogram(
|
||||
@@ -41,4 +50,29 @@ lazy_static::lazy_static! {
|
||||
"Indicates the payload status returned for a particular method",
|
||||
&["method", "status"]
|
||||
);
|
||||
pub static ref EXECUTION_LAYER_GET_PAYLOAD_OUTCOME: Result<IntCounterVec> = try_create_int_counter_vec(
|
||||
"execution_layer_get_payload_outcome",
|
||||
"The success/failure outcomes from calling get_payload",
|
||||
&["outcome"]
|
||||
);
|
||||
pub static ref EXECUTION_LAYER_BUILDER_REVEAL_PAYLOAD_OUTCOME: Result<IntCounterVec> = try_create_int_counter_vec(
|
||||
"execution_layer_builder_reveal_payload_outcome",
|
||||
"The success/failure outcomes from a builder un-blinding a payload",
|
||||
&["outcome"]
|
||||
);
|
||||
pub static ref EXECUTION_LAYER_GET_PAYLOAD_SOURCE: Result<IntCounterVec> = try_create_int_counter_vec(
|
||||
"execution_layer_get_payload_source",
|
||||
"The source of each payload returned from get_payload",
|
||||
&["source"]
|
||||
);
|
||||
pub static ref EXECUTION_LAYER_GET_PAYLOAD_BUILDER_REJECTIONS: Result<IntCounterVec> = try_create_int_counter_vec(
|
||||
"execution_layer_get_payload_builder_rejections",
|
||||
"The reasons why a payload from a builder was rejected",
|
||||
&["reason"]
|
||||
);
|
||||
pub static ref EXECUTION_LAYER_PAYLOAD_BIDS: Result<IntGaugeVec> = try_create_int_gauge_vec(
|
||||
"execution_layer_payload_bids",
|
||||
"The gwei bid value of payloads received by local EEs or builders. Only shows values up to i64::max_value.",
|
||||
&["source"]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::test_utils::DEFAULT_JWT_SECRET;
|
||||
use crate::{Config, ExecutionLayer, PayloadAttributes, PayloadAttributesV1};
|
||||
use crate::{Config, ExecutionLayer, PayloadAttributes};
|
||||
use async_trait::async_trait;
|
||||
use eth2::types::{BlockId, StateId, ValidatorId};
|
||||
use eth2::{BeaconNodeHttpClient, Timeouts};
|
||||
@@ -289,11 +289,8 @@ impl<E: EthSpec> mev_build_rs::BlindedBlockProvider for MockBuilder<E> {
|
||||
.map_err(convert_err)?;
|
||||
|
||||
// FIXME: think about proper fork here
|
||||
let payload_attributes = PayloadAttributes::V1(PayloadAttributesV1 {
|
||||
timestamp,
|
||||
prev_randao: *prev_randao,
|
||||
suggested_fee_recipient: fee_recipient,
|
||||
});
|
||||
let payload_attributes =
|
||||
PayloadAttributes::new(timestamp, *prev_randao, fee_recipient, None);
|
||||
|
||||
self.el
|
||||
.insert_proposer(slot, head_block_root, val_index, payload_attributes)
|
||||
@@ -306,18 +303,17 @@ impl<E: EthSpec> mev_build_rs::BlindedBlockProvider for MockBuilder<E> {
|
||||
finalized_hash: Some(finalized_execution_hash),
|
||||
};
|
||||
|
||||
let payload_attributes =
|
||||
PayloadAttributes::new(timestamp, *prev_randao, fee_recipient, None);
|
||||
|
||||
let payload = self
|
||||
.el
|
||||
.get_full_payload_caching::<BlindedPayload<E>>(
|
||||
head_execution_hash,
|
||||
timestamp,
|
||||
*prev_randao,
|
||||
fee_recipient,
|
||||
&payload_attributes,
|
||||
forkchoice_update_params,
|
||||
// TODO: do we need to write a test for this if this is Capella fork?
|
||||
ForkName::Merge,
|
||||
#[cfg(feature = "withdrawals")]
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map_err(convert_err)?
|
||||
|
||||
@@ -98,33 +98,16 @@ impl<T: EthSpec> MockExecutionLayer<T> {
|
||||
justified_hash: None,
|
||||
finalized_hash: None,
|
||||
};
|
||||
|
||||
// FIXME: this is just best guess for how to deal with forks here..
|
||||
let payload_attributes = match &latest_execution_block {
|
||||
&Block::PoS(ref pos_block) => match pos_block {
|
||||
&ExecutionPayload::Merge(_) => PayloadAttributes::V1(PayloadAttributesV1 {
|
||||
timestamp,
|
||||
prev_randao,
|
||||
suggested_fee_recipient: Address::repeat_byte(42),
|
||||
}),
|
||||
&ExecutionPayload::Capella(_) | &ExecutionPayload::Eip4844(_) => {
|
||||
PayloadAttributes::V2(PayloadAttributesV2 {
|
||||
timestamp,
|
||||
prev_randao,
|
||||
suggested_fee_recipient: Address::repeat_byte(42),
|
||||
// FIXME: think about adding withdrawals here..
|
||||
#[cfg(feature = "withdrawals")]
|
||||
withdrawals: Some(vec![]),
|
||||
})
|
||||
}
|
||||
},
|
||||
// I guess a PoW blocks means we should use Merge?
|
||||
&Block::PoW(_) => PayloadAttributes::V1(PayloadAttributesV1 {
|
||||
timestamp,
|
||||
prev_randao,
|
||||
suggested_fee_recipient: Address::repeat_byte(42),
|
||||
}),
|
||||
};
|
||||
let payload_attributes = PayloadAttributes::new(
|
||||
timestamp,
|
||||
prev_randao,
|
||||
Address::repeat_byte(42),
|
||||
// FIXME: think about how to handle different forks / withdrawals here..
|
||||
#[cfg(feature = "withdrawals")]
|
||||
Some(vec![]),
|
||||
#[cfg(not(feature = "withdrawals"))]
|
||||
None,
|
||||
);
|
||||
|
||||
// Insert a proposer to ensure the fork choice updated command works.
|
||||
let slot = Slot::new(0);
|
||||
@@ -150,19 +133,18 @@ impl<T: EthSpec> MockExecutionLayer<T> {
|
||||
slot,
|
||||
chain_health: ChainHealth::Healthy,
|
||||
};
|
||||
let suggested_fee_recipient = self.el.get_suggested_fee_recipient(validator_index).await;
|
||||
let payload_attributes =
|
||||
PayloadAttributes::new(timestamp, prev_randao, suggested_fee_recipient, None);
|
||||
let payload: ExecutionPayload<T> = self
|
||||
.el
|
||||
.get_payload::<FullPayload<T>>(
|
||||
parent_hash,
|
||||
timestamp,
|
||||
prev_randao,
|
||||
validator_index,
|
||||
&payload_attributes,
|
||||
forkchoice_update_params,
|
||||
builder_params,
|
||||
// FIXME: do we need to consider other forks somehow? What about withdrawals?
|
||||
ForkName::Merge,
|
||||
#[cfg(feature = "withdrawals")]
|
||||
Some(vec![]),
|
||||
&self.spec,
|
||||
)
|
||||
.await
|
||||
@@ -186,19 +168,18 @@ impl<T: EthSpec> MockExecutionLayer<T> {
|
||||
slot,
|
||||
chain_health: ChainHealth::Healthy,
|
||||
};
|
||||
let suggested_fee_recipient = self.el.get_suggested_fee_recipient(validator_index).await;
|
||||
let payload_attributes =
|
||||
PayloadAttributes::new(timestamp, prev_randao, suggested_fee_recipient, None);
|
||||
let payload_header = self
|
||||
.el
|
||||
.get_payload::<BlindedPayload<T>>(
|
||||
parent_hash,
|
||||
timestamp,
|
||||
prev_randao,
|
||||
validator_index,
|
||||
&payload_attributes,
|
||||
forkchoice_update_params,
|
||||
builder_params,
|
||||
// FIXME: do we need to consider other forks somehow? What about withdrawals?
|
||||
ForkName::Merge,
|
||||
#[cfg(feature = "withdrawals")]
|
||||
Some(vec![]),
|
||||
&self.spec,
|
||||
)
|
||||
.await
|
||||
|
||||
Reference in New Issue
Block a user