mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-11 18:04:18 +00:00
Merge branch 'capella' of https://github.com/sigp/lighthouse into eip4844
# Conflicts: # beacon_node/beacon_chain/src/beacon_chain.rs # beacon_node/beacon_chain/src/block_verification.rs # beacon_node/beacon_chain/src/test_utils.rs # beacon_node/execution_layer/src/engine_api.rs # beacon_node/execution_layer/src/engine_api/http.rs # beacon_node/execution_layer/src/lib.rs # beacon_node/execution_layer/src/test_utils/handle_rpc.rs # beacon_node/http_api/src/lib.rs # beacon_node/http_api/tests/fork_tests.rs # beacon_node/network/src/beacon_processor/mod.rs # beacon_node/network/src/beacon_processor/work_reprocessing_queue.rs # beacon_node/network/src/beacon_processor/worker/sync_methods.rs # beacon_node/operation_pool/src/bls_to_execution_changes.rs # beacon_node/operation_pool/src/lib.rs # beacon_node/operation_pool/src/persistence.rs # consensus/serde_utils/src/u256_hex_be_opt.rs # testing/antithesis/Dockerfile.libvoidstar
This commit is contained in:
@@ -1,4 +1,9 @@
|
||||
use crate::engines::ForkchoiceState;
|
||||
use crate::http::{
|
||||
ENGINE_EXCHANGE_TRANSITION_CONFIGURATION_V1, ENGINE_FORKCHOICE_UPDATED_V1,
|
||||
ENGINE_FORKCHOICE_UPDATED_V2, ENGINE_GET_PAYLOAD_V1, ENGINE_GET_PAYLOAD_V2,
|
||||
ENGINE_NEW_PAYLOAD_V1, ENGINE_NEW_PAYLOAD_V2,
|
||||
};
|
||||
use crate::BlobTxConversionError;
|
||||
pub use ethers_core::types::Transaction;
|
||||
use ethers_core::utils::rlp::{self, Decodable, Rlp};
|
||||
@@ -10,8 +15,8 @@ use std::convert::TryFrom;
|
||||
use strum::IntoStaticStr;
|
||||
use superstruct::superstruct;
|
||||
pub use types::{
|
||||
Address, EthSpec, ExecutionBlockHash, ExecutionPayload, ExecutionPayloadHeader, FixedVector,
|
||||
ForkName, Hash256, Uint256, VariableList, Withdrawal,
|
||||
Address, EthSpec, ExecutionBlockHash, ExecutionPayload, ExecutionPayloadHeader,
|
||||
ExecutionPayloadRef, FixedVector, ForkName, Hash256, Uint256, VariableList, Withdrawal,
|
||||
};
|
||||
use types::{ExecutionPayloadCapella, ExecutionPayloadEip4844, ExecutionPayloadMerge};
|
||||
|
||||
@@ -330,6 +335,8 @@ pub struct ProposeBlindedBlockResponse {
|
||||
#[superstruct(
|
||||
variants(Merge, Capella, Eip4844),
|
||||
variant_attributes(derive(Clone, Debug, PartialEq),),
|
||||
map_into(ExecutionPayload),
|
||||
map_ref_into(ExecutionPayloadRef),
|
||||
cast_error(ty = "Error", expr = "Error::IncorrectStateVariant"),
|
||||
partial_getter_error(ty = "Error", expr = "Error::IncorrectStateVariant")
|
||||
)]
|
||||
@@ -344,27 +351,49 @@ pub struct GetPayloadResponse<T: EthSpec> {
|
||||
pub block_value: Uint256,
|
||||
}
|
||||
|
||||
impl<T: EthSpec> GetPayloadResponse<T> {
|
||||
pub fn execution_payload(self) -> ExecutionPayload<T> {
|
||||
match self {
|
||||
GetPayloadResponse::Merge(response) => {
|
||||
ExecutionPayload::Merge(response.execution_payload)
|
||||
}
|
||||
GetPayloadResponse::Capella(response) => {
|
||||
ExecutionPayload::Capella(response.execution_payload)
|
||||
}
|
||||
GetPayloadResponse::Eip4844(response) => {
|
||||
ExecutionPayload::Eip4844(response.execution_payload)
|
||||
}
|
||||
impl<'a, T: EthSpec> From<GetPayloadResponseRef<'a, T>> for ExecutionPayloadRef<'a, T> {
|
||||
fn from(response: GetPayloadResponseRef<'a, T>) -> Self {
|
||||
map_get_payload_response_ref_into_execution_payload_ref!(&'a _, response, |inner, cons| {
|
||||
cons(&inner.execution_payload)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: EthSpec> From<GetPayloadResponse<T>> for ExecutionPayload<T> {
|
||||
fn from(response: GetPayloadResponse<T>) -> Self {
|
||||
map_get_payload_response_into_execution_payload!(response, |inner, cons| {
|
||||
cons(inner.execution_payload)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: EthSpec> From<GetPayloadResponse<T>> for (ExecutionPayload<T>, Uint256) {
|
||||
fn from(response: GetPayloadResponse<T>) -> Self {
|
||||
match response {
|
||||
GetPayloadResponse::Merge(inner) => (
|
||||
ExecutionPayload::Merge(inner.execution_payload),
|
||||
inner.block_value,
|
||||
),
|
||||
GetPayloadResponse::Capella(inner) => (
|
||||
ExecutionPayload::Capella(inner.execution_payload),
|
||||
inner.block_value,
|
||||
),
|
||||
GetPayloadResponse::Eip4844(inner) => (
|
||||
ExecutionPayload::Eip4844(inner.execution_payload),
|
||||
inner.block_value,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This name is work in progress, it could
|
||||
// change when this method is actually proposed
|
||||
// but I'm writing this as it has been described
|
||||
impl<T: EthSpec> GetPayloadResponse<T> {
|
||||
pub fn execution_payload_ref(&self) -> ExecutionPayloadRef<T> {
|
||||
self.to_ref().into()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct SupportedApis {
|
||||
pub struct EngineCapabilities {
|
||||
pub new_payload_v1: bool,
|
||||
pub new_payload_v2: bool,
|
||||
pub new_payload_v3: bool,
|
||||
@@ -375,3 +404,32 @@ pub struct SupportedApis {
|
||||
pub get_payload_v3: bool,
|
||||
pub exchange_transition_configuration_v1: bool,
|
||||
}
|
||||
|
||||
impl EngineCapabilities {
|
||||
pub fn to_response(&self) -> Vec<&str> {
|
||||
let mut response = Vec::new();
|
||||
if self.new_payload_v1 {
|
||||
response.push(ENGINE_NEW_PAYLOAD_V1);
|
||||
}
|
||||
if self.new_payload_v2 {
|
||||
response.push(ENGINE_NEW_PAYLOAD_V2);
|
||||
}
|
||||
if self.forkchoice_updated_v1 {
|
||||
response.push(ENGINE_FORKCHOICE_UPDATED_V1);
|
||||
}
|
||||
if self.forkchoice_updated_v2 {
|
||||
response.push(ENGINE_FORKCHOICE_UPDATED_V2);
|
||||
}
|
||||
if self.get_payload_v1 {
|
||||
response.push(ENGINE_GET_PAYLOAD_V1);
|
||||
}
|
||||
if self.get_payload_v2 {
|
||||
response.push(ENGINE_GET_PAYLOAD_V2);
|
||||
}
|
||||
if self.exchange_transition_configuration_v1 {
|
||||
response.push(ENGINE_EXCHANGE_TRANSITION_CONFIGURATION_V1);
|
||||
}
|
||||
|
||||
response
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,10 +7,11 @@ use reqwest::header::CONTENT_TYPE;
|
||||
use sensitive_url::SensitiveUrl;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde_json::json;
|
||||
use tokio::sync::RwLock;
|
||||
use std::collections::HashSet;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use std::time::Duration;
|
||||
use types::{ChainSpec, EthSpec};
|
||||
use std::time::{Duration, SystemTime};
|
||||
use types::EthSpec;
|
||||
|
||||
pub use deposit_log::{DepositLog, Log};
|
||||
pub use reqwest::Client;
|
||||
@@ -50,8 +51,37 @@ pub const ENGINE_EXCHANGE_TRANSITION_CONFIGURATION_V1: &str =
|
||||
"engine_exchangeTransitionConfigurationV1";
|
||||
pub const ENGINE_EXCHANGE_TRANSITION_CONFIGURATION_V1_TIMEOUT: Duration = Duration::from_secs(1);
|
||||
|
||||
pub const ENGINE_EXCHANGE_CAPABILITIES: &str = "engine_exchangeCapabilities";
|
||||
pub const ENGINE_EXCHANGE_CAPABILITIES_TIMEOUT: Duration = Duration::from_secs(1);
|
||||
|
||||
/// This error is returned during a `chainId` call by Geth.
|
||||
pub const EIP155_ERROR_STR: &str = "chain not synced beyond EIP-155 replay-protection fork block";
|
||||
/// This code is returned by all clients when a method is not supported
|
||||
/// (verified geth, nethermind, erigon, besu)
|
||||
pub const METHOD_NOT_FOUND_CODE: i64 = -32601;
|
||||
|
||||
pub static LIGHTHOUSE_CAPABILITIES: &[&str] = &[
|
||||
ENGINE_NEW_PAYLOAD_V1,
|
||||
ENGINE_NEW_PAYLOAD_V2,
|
||||
ENGINE_GET_PAYLOAD_V1,
|
||||
ENGINE_GET_PAYLOAD_V2,
|
||||
ENGINE_FORKCHOICE_UPDATED_V1,
|
||||
ENGINE_FORKCHOICE_UPDATED_V2,
|
||||
ENGINE_EXCHANGE_TRANSITION_CONFIGURATION_V1,
|
||||
];
|
||||
|
||||
/// This is necessary because a user might run a capella-enabled version of
|
||||
/// lighthouse before they update to a capella-enabled execution engine.
|
||||
// TODO (mark): rip this out once we are post-capella on mainnet
|
||||
pub static PRE_CAPELLA_ENGINE_CAPABILITIES: EngineCapabilities = EngineCapabilities {
|
||||
new_payload_v1: true,
|
||||
new_payload_v2: false,
|
||||
forkchoice_updated_v1: true,
|
||||
forkchoice_updated_v2: false,
|
||||
get_payload_v1: true,
|
||||
get_payload_v2: false,
|
||||
exchange_transition_configuration_v1: true,
|
||||
};
|
||||
|
||||
/// Contains methods to convert arbitrary bytes to an ETH2 deposit contract object.
|
||||
pub mod deposit_log {
|
||||
@@ -528,11 +558,47 @@ pub mod deposit_methods {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CapabilitiesCacheEntry {
|
||||
engine_capabilities: EngineCapabilities,
|
||||
fetch_time: SystemTime,
|
||||
}
|
||||
|
||||
impl CapabilitiesCacheEntry {
|
||||
pub fn new(engine_capabilities: EngineCapabilities) -> Self {
|
||||
Self {
|
||||
engine_capabilities,
|
||||
fetch_time: SystemTime::now(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn engine_capabilities(&self) -> &EngineCapabilities {
|
||||
&self.engine_capabilities
|
||||
}
|
||||
|
||||
pub fn age(&self) -> Duration {
|
||||
// duration_since() may fail because measurements taken earlier
|
||||
// are not guaranteed to always be before later measurements
|
||||
// due to anomalies such as the system clock being adjusted
|
||||
// either forwards or backwards
|
||||
//
|
||||
// In such cases, we'll just say the age is zero
|
||||
SystemTime::now()
|
||||
.duration_since(self.fetch_time)
|
||||
.unwrap_or(Duration::ZERO)
|
||||
}
|
||||
|
||||
/// returns `true` if the entry's age is >= age_limit
|
||||
pub fn older_than(&self, age_limit: Option<Duration>) -> bool {
|
||||
age_limit.map_or(false, |limit| self.age() >= limit)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct HttpJsonRpc {
|
||||
pub client: Client,
|
||||
pub url: SensitiveUrl,
|
||||
pub execution_timeout_multiplier: u32,
|
||||
pub cached_supported_apis: RwLock<Option<SupportedApis>>,
|
||||
pub engine_capabilities_cache: Mutex<Option<CapabilitiesCacheEntry>>,
|
||||
auth: Option<Auth>,
|
||||
}
|
||||
|
||||
@@ -540,29 +606,12 @@ impl HttpJsonRpc {
|
||||
pub fn new(
|
||||
url: SensitiveUrl,
|
||||
execution_timeout_multiplier: Option<u32>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<Self, Error> {
|
||||
// FIXME: remove this `cached_supported_apis` spec hack once the `engine_getCapabilities`
|
||||
// method is implemented in all execution clients:
|
||||
// https://github.com/ethereum/execution-apis/issues/321
|
||||
let cached_supported_apis = RwLock::new(Some(SupportedApis {
|
||||
new_payload_v1: true,
|
||||
new_payload_v2: spec.capella_fork_epoch.is_some() || spec.eip4844_fork_epoch.is_some(),
|
||||
new_payload_v3: spec.eip4844_fork_epoch.is_some(),
|
||||
forkchoice_updated_v1: true,
|
||||
forkchoice_updated_v2: spec.capella_fork_epoch.is_some()
|
||||
|| spec.eip4844_fork_epoch.is_some(),
|
||||
get_payload_v1: true,
|
||||
get_payload_v2: spec.capella_fork_epoch.is_some() || spec.eip4844_fork_epoch.is_some(),
|
||||
get_payload_v3: spec.eip4844_fork_epoch.is_some(),
|
||||
exchange_transition_configuration_v1: true,
|
||||
}));
|
||||
|
||||
Ok(Self {
|
||||
client: Client::builder().build()?,
|
||||
url,
|
||||
execution_timeout_multiplier: execution_timeout_multiplier.unwrap_or(1),
|
||||
cached_supported_apis,
|
||||
engine_capabilities_cache: Mutex::new(None),
|
||||
auth: None,
|
||||
})
|
||||
}
|
||||
@@ -571,29 +620,12 @@ impl HttpJsonRpc {
|
||||
url: SensitiveUrl,
|
||||
auth: Auth,
|
||||
execution_timeout_multiplier: Option<u32>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<Self, Error> {
|
||||
// FIXME: remove this `cached_supported_apis` spec hack once the `engine_getCapabilities`
|
||||
// method is implemented in all execution clients:
|
||||
// https://github.com/ethereum/execution-apis/issues/321
|
||||
let cached_supported_apis = RwLock::new(Some(SupportedApis {
|
||||
new_payload_v1: true,
|
||||
new_payload_v2: spec.capella_fork_epoch.is_some() || spec.eip4844_fork_epoch.is_some(),
|
||||
new_payload_v3: spec.eip4844_fork_epoch.is_some(),
|
||||
forkchoice_updated_v1: true,
|
||||
forkchoice_updated_v2: spec.capella_fork_epoch.is_some()
|
||||
|| spec.eip4844_fork_epoch.is_some(),
|
||||
get_payload_v1: true,
|
||||
get_payload_v2: spec.capella_fork_epoch.is_some() || spec.eip4844_fork_epoch.is_some(),
|
||||
get_payload_v3: spec.eip4844_fork_epoch.is_some(),
|
||||
exchange_transition_configuration_v1: true,
|
||||
}));
|
||||
|
||||
Ok(Self {
|
||||
client: Client::builder().build()?,
|
||||
url,
|
||||
execution_timeout_multiplier: execution_timeout_multiplier.unwrap_or(1),
|
||||
cached_supported_apis,
|
||||
engine_capabilities_cache: Mutex::new(None),
|
||||
auth: Some(auth),
|
||||
})
|
||||
}
|
||||
@@ -791,7 +823,7 @@ impl HttpJsonRpc {
|
||||
pub async fn get_payload_v1<T: EthSpec>(
|
||||
&self,
|
||||
payload_id: PayloadId,
|
||||
) -> Result<ExecutionPayload<T>, Error> {
|
||||
) -> Result<GetPayloadResponse<T>, Error> {
|
||||
let params = json!([JsonPayloadIdRequest::from(payload_id)]);
|
||||
|
||||
let payload_v1: JsonExecutionPayloadV1<T> = self
|
||||
@@ -802,7 +834,11 @@ impl HttpJsonRpc {
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(JsonExecutionPayload::V1(payload_v1).into())
|
||||
Ok(GetPayloadResponse::Merge(GetPayloadResponseMerge {
|
||||
execution_payload: payload_v1.into(),
|
||||
// Have to guess zero here as we don't know the value
|
||||
block_value: Uint256::zero(),
|
||||
}))
|
||||
}
|
||||
|
||||
pub async fn get_payload_v2<T: EthSpec>(
|
||||
@@ -961,37 +997,67 @@ impl HttpJsonRpc {
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
// TODO: This is currently a stub for the `engine_getCapabilities`
|
||||
// method. This stub is unused because we set cached_supported_apis
|
||||
// in the constructor based on the `spec`
|
||||
// Implement this once the execution clients support it
|
||||
// https://github.com/ethereum/execution-apis/issues/321
|
||||
pub async fn get_capabilities(&self) -> Result<SupportedApis, Error> {
|
||||
Ok(SupportedApis {
|
||||
new_payload_v1: true,
|
||||
new_payload_v2: true,
|
||||
new_payload_v3: true,
|
||||
forkchoice_updated_v1: true,
|
||||
forkchoice_updated_v2: true,
|
||||
get_payload_v1: true,
|
||||
get_payload_v2: true,
|
||||
get_payload_v3: true,
|
||||
exchange_transition_configuration_v1: true,
|
||||
})
|
||||
pub async fn exchange_capabilities(&self) -> Result<EngineCapabilities, Error> {
|
||||
let params = json!([LIGHTHOUSE_CAPABILITIES]);
|
||||
|
||||
let response: Result<HashSet<String>, _> = self
|
||||
.rpc_request(
|
||||
ENGINE_EXCHANGE_CAPABILITIES,
|
||||
params,
|
||||
ENGINE_EXCHANGE_CAPABILITIES_TIMEOUT * self.execution_timeout_multiplier,
|
||||
)
|
||||
.await;
|
||||
|
||||
match response {
|
||||
// TODO (mark): rip this out once we are post capella on mainnet
|
||||
Err(error) => match error {
|
||||
Error::ServerMessage { code, message: _ } if code == METHOD_NOT_FOUND_CODE => {
|
||||
Ok(PRE_CAPELLA_ENGINE_CAPABILITIES)
|
||||
}
|
||||
_ => Err(error),
|
||||
},
|
||||
Ok(capabilities) => Ok(EngineCapabilities {
|
||||
new_payload_v1: capabilities.contains(ENGINE_NEW_PAYLOAD_V1),
|
||||
new_payload_v2: capabilities.contains(ENGINE_NEW_PAYLOAD_V2),
|
||||
forkchoice_updated_v1: capabilities.contains(ENGINE_FORKCHOICE_UPDATED_V1),
|
||||
forkchoice_updated_v2: capabilities.contains(ENGINE_FORKCHOICE_UPDATED_V2),
|
||||
get_payload_v1: capabilities.contains(ENGINE_GET_PAYLOAD_V1),
|
||||
get_payload_v2: capabilities.contains(ENGINE_GET_PAYLOAD_V2),
|
||||
exchange_transition_configuration_v1: capabilities
|
||||
.contains(ENGINE_EXCHANGE_TRANSITION_CONFIGURATION_V1),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn set_cached_supported_apis(&self, supported_apis: Option<SupportedApis>) {
|
||||
*self.cached_supported_apis.write().await = supported_apis;
|
||||
pub async fn clear_exchange_capabilties_cache(&self) {
|
||||
*self.engine_capabilities_cache.lock().await = None;
|
||||
}
|
||||
|
||||
pub async fn get_cached_supported_apis(&self) -> Result<SupportedApis, Error> {
|
||||
let cached_opt = *self.cached_supported_apis.read().await;
|
||||
if let Some(supported_apis) = cached_opt {
|
||||
Ok(supported_apis)
|
||||
/// Returns the execution engine capabilities resulting from a call to
|
||||
/// engine_exchangeCapabilities. If the capabilities cache is not populated,
|
||||
/// or if it is populated with a cached result of age >= `age_limit`, this
|
||||
/// method will fetch the result from the execution engine and populate the
|
||||
/// cache before returning it. Otherwise it will return a cached result from
|
||||
/// a previous call.
|
||||
///
|
||||
/// Set `age_limit` to `None` to always return the cached result
|
||||
/// Set `age_limit` to `Some(Duration::ZERO)` to force fetching from EE
|
||||
pub async fn get_engine_capabilities(
|
||||
&self,
|
||||
age_limit: Option<Duration>,
|
||||
) -> Result<EngineCapabilities, Error> {
|
||||
let mut lock = self.engine_capabilities_cache.lock().await;
|
||||
|
||||
if lock
|
||||
.as_ref()
|
||||
.map_or(true, |entry| entry.older_than(age_limit))
|
||||
{
|
||||
let engine_capabilities = self.exchange_capabilities().await?;
|
||||
*lock = Some(CapabilitiesCacheEntry::new(engine_capabilities));
|
||||
Ok(engine_capabilities)
|
||||
} else {
|
||||
let supported_apis = self.get_capabilities().await?;
|
||||
self.set_cached_supported_apis(Some(supported_apis)).await;
|
||||
Ok(supported_apis)
|
||||
// here entry is guaranteed to exist so unwrap() is safe
|
||||
Ok(*lock.as_ref().unwrap().engine_capabilities())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1001,12 +1067,12 @@ impl HttpJsonRpc {
|
||||
&self,
|
||||
execution_payload: ExecutionPayload<T>,
|
||||
) -> Result<PayloadStatusV1, Error> {
|
||||
let supported_apis = self.get_cached_supported_apis().await?;
|
||||
if supported_apis.new_payload_v3 {
|
||||
let engine_capabilities = self.get_engine_capabilities(None).await?;
|
||||
if engine_capabilities.new_payload_v3 {
|
||||
self.new_payload_v3(execution_payload).await
|
||||
} else if supported_apis.new_payload_v2 {
|
||||
}else if engine_capabilities.new_payload_v2 {
|
||||
self.new_payload_v2(execution_payload).await
|
||||
} else if supported_apis.new_payload_v1 {
|
||||
} else if engine_capabilities.new_payload_v1 {
|
||||
self.new_payload_v1(execution_payload).await
|
||||
} else {
|
||||
Err(Error::RequiredMethodUnsupported("engine_newPayload"))
|
||||
@@ -1019,22 +1085,13 @@ impl HttpJsonRpc {
|
||||
&self,
|
||||
fork_name: ForkName,
|
||||
payload_id: PayloadId,
|
||||
) -> Result<ExecutionPayload<T>, Error> {
|
||||
let supported_apis = self.get_cached_supported_apis().await?;
|
||||
if supported_apis.get_payload_v3 {
|
||||
Ok(self
|
||||
.get_payload_v3(fork_name, payload_id)
|
||||
.await?
|
||||
.execution_payload())
|
||||
} else if supported_apis.get_payload_v2 {
|
||||
// TODO: modify this method to return GetPayloadResponse instead
|
||||
// of throwing away the `block_value` and returning only the
|
||||
// ExecutionPayload
|
||||
Ok(self
|
||||
.get_payload_v2(fork_name, payload_id)
|
||||
.await?
|
||||
.execution_payload())
|
||||
} else if supported_apis.get_payload_v1 {
|
||||
) -> Result<GetPayloadResponse<T>, Error> {
|
||||
let engine_capabilities = self.get_engine_capabilities(None).await?;
|
||||
if engine_capabilities.get_payload_v3 {
|
||||
self.get_payload_v3(fork_name, payload_id).await
|
||||
} else if engine_capabilities.get_payload_v2 {
|
||||
self.get_payload_v2(fork_name, payload_id).await
|
||||
} else if engine_capabilities.new_payload_v1 {
|
||||
self.get_payload_v1(payload_id).await
|
||||
} else {
|
||||
Err(Error::RequiredMethodUnsupported("engine_getPayload"))
|
||||
@@ -1048,11 +1105,11 @@ impl HttpJsonRpc {
|
||||
forkchoice_state: ForkchoiceState,
|
||||
payload_attributes: Option<PayloadAttributes>,
|
||||
) -> Result<ForkchoiceUpdatedResponse, Error> {
|
||||
let supported_apis = self.get_cached_supported_apis().await?;
|
||||
if supported_apis.forkchoice_updated_v2 {
|
||||
let engine_capabilities = self.get_engine_capabilities(None).await?;
|
||||
if engine_capabilities.forkchoice_updated_v2 {
|
||||
self.forkchoice_updated_v2(forkchoice_state, payload_attributes)
|
||||
.await
|
||||
} else if supported_apis.forkchoice_updated_v1 {
|
||||
} else if engine_capabilities.forkchoice_updated_v1 {
|
||||
self.forkchoice_updated_v1(forkchoice_state, payload_attributes)
|
||||
.await
|
||||
} else {
|
||||
@@ -1080,7 +1137,6 @@ mod test {
|
||||
impl Tester {
|
||||
pub fn new(with_auth: bool) -> Self {
|
||||
let server = MockServer::unit_testing();
|
||||
let spec = MainnetEthSpec::default_spec();
|
||||
|
||||
let rpc_url = SensitiveUrl::parse(&server.url()).unwrap();
|
||||
let echo_url = SensitiveUrl::parse(&format!("{}/echo", server.url())).unwrap();
|
||||
@@ -1091,13 +1147,13 @@ mod test {
|
||||
let echo_auth =
|
||||
Auth::new(JwtKey::from_slice(&DEFAULT_JWT_SECRET).unwrap(), None, None);
|
||||
(
|
||||
Arc::new(HttpJsonRpc::new_with_auth(rpc_url, rpc_auth, None, &spec).unwrap()),
|
||||
Arc::new(HttpJsonRpc::new_with_auth(echo_url, echo_auth, None, &spec).unwrap()),
|
||||
Arc::new(HttpJsonRpc::new_with_auth(rpc_url, rpc_auth, None).unwrap()),
|
||||
Arc::new(HttpJsonRpc::new_with_auth(echo_url, echo_auth, None).unwrap()),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
Arc::new(HttpJsonRpc::new(rpc_url, None, &spec).unwrap()),
|
||||
Arc::new(HttpJsonRpc::new(echo_url, None, &spec).unwrap()),
|
||||
Arc::new(HttpJsonRpc::new(rpc_url, None).unwrap()),
|
||||
Arc::new(HttpJsonRpc::new(echo_url, None).unwrap()),
|
||||
)
|
||||
};
|
||||
|
||||
@@ -1685,10 +1741,11 @@ mod test {
|
||||
}
|
||||
})],
|
||||
|client| async move {
|
||||
let payload = client
|
||||
let payload: ExecutionPayload<_> = client
|
||||
.get_payload_v1::<MainnetEthSpec>(str_to_payload_id("0xa247243752eb10b4"))
|
||||
.await
|
||||
.unwrap();
|
||||
.unwrap()
|
||||
.into();
|
||||
|
||||
let expected = ExecutionPayload::Merge(ExecutionPayloadMerge {
|
||||
parent_hash: ExecutionBlockHash::from_str("0x3b8fb240d288781d4aac94d3fd16809ee413bc99294a085798a589dae51ddd4a").unwrap(),
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
//! Provides generic behaviour for multiple execution engines, specifically fallback behaviour.
|
||||
|
||||
use crate::engine_api::{
|
||||
Error as EngineApiError, ForkchoiceUpdatedResponse, PayloadAttributes, PayloadId,
|
||||
EngineCapabilities, Error as EngineApiError, ForkchoiceUpdatedResponse, PayloadAttributes,
|
||||
PayloadId,
|
||||
};
|
||||
use crate::HttpJsonRpc;
|
||||
use lru::LruCache;
|
||||
use slog::{debug, error, info, Logger};
|
||||
use slog::{debug, error, info, warn, Logger};
|
||||
use std::future::Future;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use task_executor::TaskExecutor;
|
||||
use tokio::sync::{watch, Mutex, RwLock};
|
||||
use tokio_stream::wrappers::WatchStream;
|
||||
@@ -18,6 +20,7 @@ use types::ExecutionBlockHash;
|
||||
/// 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;
|
||||
const CACHED_ENGINE_CAPABILITIES_AGE_LIMIT: Duration = Duration::from_secs(900); // 15 minutes
|
||||
|
||||
/// Stores the remembered state of a engine.
|
||||
#[derive(Copy, Clone, PartialEq, Debug, Eq, Default)]
|
||||
@@ -29,6 +32,14 @@ enum EngineStateInternal {
|
||||
AuthFailed,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
|
||||
enum CapabilitiesCacheAction {
|
||||
#[default]
|
||||
None,
|
||||
Update,
|
||||
Clear,
|
||||
}
|
||||
|
||||
/// A subset of the engine state to inform other services if the engine is online or offline.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Copy)]
|
||||
pub enum EngineState {
|
||||
@@ -231,7 +242,7 @@ impl Engine {
|
||||
/// Run the `EngineApi::upcheck` function if the node's last known state is not synced. This
|
||||
/// might be used to recover the node if offline.
|
||||
pub async fn upcheck(&self) {
|
||||
let state: EngineStateInternal = match self.api.upcheck().await {
|
||||
let (state, cache_action) = match self.api.upcheck().await {
|
||||
Ok(()) => {
|
||||
let mut state = self.state.write().await;
|
||||
if **state != EngineStateInternal::Synced {
|
||||
@@ -249,12 +260,12 @@ impl Engine {
|
||||
);
|
||||
}
|
||||
state.update(EngineStateInternal::Synced);
|
||||
**state
|
||||
(**state, CapabilitiesCacheAction::Update)
|
||||
}
|
||||
Err(EngineApiError::IsSyncing) => {
|
||||
let mut state = self.state.write().await;
|
||||
state.update(EngineStateInternal::Syncing);
|
||||
**state
|
||||
(**state, CapabilitiesCacheAction::Update)
|
||||
}
|
||||
Err(EngineApiError::Auth(err)) => {
|
||||
error!(
|
||||
@@ -265,7 +276,7 @@ impl Engine {
|
||||
|
||||
let mut state = self.state.write().await;
|
||||
state.update(EngineStateInternal::AuthFailed);
|
||||
**state
|
||||
(**state, CapabilitiesCacheAction::None)
|
||||
}
|
||||
Err(e) => {
|
||||
error!(
|
||||
@@ -276,10 +287,30 @@ impl Engine {
|
||||
|
||||
let mut state = self.state.write().await;
|
||||
state.update(EngineStateInternal::Offline);
|
||||
**state
|
||||
// need to clear the engine capabilities cache if we detect the
|
||||
// execution engine is offline as it is likely the engine is being
|
||||
// updated to a newer version with new capabilities
|
||||
(**state, CapabilitiesCacheAction::Clear)
|
||||
}
|
||||
};
|
||||
|
||||
// do this after dropping state lock guard to avoid holding two locks at once
|
||||
match cache_action {
|
||||
CapabilitiesCacheAction::None => {}
|
||||
CapabilitiesCacheAction::Update => {
|
||||
if let Err(e) = self
|
||||
.get_engine_capabilities(Some(CACHED_ENGINE_CAPABILITIES_AGE_LIMIT))
|
||||
.await
|
||||
{
|
||||
warn!(self.log,
|
||||
"Error during exchange capabilities";
|
||||
"error" => ?e,
|
||||
)
|
||||
}
|
||||
}
|
||||
CapabilitiesCacheAction::Clear => self.api.clear_exchange_capabilties_cache().await,
|
||||
}
|
||||
|
||||
debug!(
|
||||
self.log,
|
||||
"Execution engine upcheck complete";
|
||||
@@ -287,6 +318,22 @@ impl Engine {
|
||||
);
|
||||
}
|
||||
|
||||
/// Returns the execution engine capabilities resulting from a call to
|
||||
/// engine_exchangeCapabilities. If the capabilities cache is not populated,
|
||||
/// or if it is populated with a cached result of age >= `age_limit`, this
|
||||
/// method will fetch the result from the execution engine and populate the
|
||||
/// cache before returning it. Otherwise it will return a cached result from
|
||||
/// a previous call.
|
||||
///
|
||||
/// Set `age_limit` to `None` to always return the cached result
|
||||
/// Set `age_limit` to `Some(Duration::ZERO)` to force fetching from EE
|
||||
pub async fn get_engine_capabilities(
|
||||
&self,
|
||||
age_limit: Option<Duration>,
|
||||
) -> Result<EngineCapabilities, EngineApiError> {
|
||||
self.api.get_engine_capabilities(age_limit).await
|
||||
}
|
||||
|
||||
/// Run `func` on the node regardless of the node's current state.
|
||||
///
|
||||
/// ## Note
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
use crate::payload_cache::PayloadCache;
|
||||
use auth::{strip_prefix, Auth, JwtKey};
|
||||
use builder_client::BuilderHttpClient;
|
||||
pub use engine_api::EngineCapabilities;
|
||||
use engine_api::Error as ApiError;
|
||||
pub use engine_api::*;
|
||||
pub use engine_api::{http, http::deposit_methods, http::HttpJsonRpc};
|
||||
@@ -123,9 +124,13 @@ impl From<ApiError> for Error {
|
||||
}
|
||||
|
||||
pub enum BlockProposalContents<T: EthSpec, Payload: AbstractExecPayload<T>> {
|
||||
Payload(Payload),
|
||||
Payload {
|
||||
payload: Payload,
|
||||
block_value: Uint256,
|
||||
},
|
||||
PayloadAndBlobs {
|
||||
payload: Payload,
|
||||
block_value: Uint256,
|
||||
kzg_commitments: VariableList<KzgCommitment, T::MaxBlobsPerBlock>,
|
||||
blobs: VariableList<Blob<T>, T::MaxBlobsPerBlock>,
|
||||
},
|
||||
@@ -151,9 +156,13 @@ impl<T: EthSpec, Payload: AbstractExecPayload<T>> BlockProposalContents<T, Paylo
|
||||
|
||||
pub fn payload(&self) -> &Payload {
|
||||
match self {
|
||||
Self::Payload(payload) => payload,
|
||||
Self::Payload {
|
||||
payload,
|
||||
block_value: _,
|
||||
} => payload,
|
||||
Self::PayloadAndBlobs {
|
||||
payload,
|
||||
block_value: _,
|
||||
kzg_commitments: _,
|
||||
blobs: _,
|
||||
} => payload,
|
||||
@@ -161,21 +170,43 @@ impl<T: EthSpec, Payload: AbstractExecPayload<T>> BlockProposalContents<T, Paylo
|
||||
}
|
||||
pub fn to_payload(self) -> Payload {
|
||||
match self {
|
||||
Self::Payload(payload) => payload,
|
||||
Self::Payload {
|
||||
payload,
|
||||
block_value: _,
|
||||
} => payload,
|
||||
Self::PayloadAndBlobs {
|
||||
payload,
|
||||
block_value: _,
|
||||
kzg_commitments: _,
|
||||
blobs: _,
|
||||
} => payload,
|
||||
}
|
||||
}
|
||||
pub fn block_value(&self) -> &Uint256 {
|
||||
match self {
|
||||
Self::Payload {
|
||||
payload: _,
|
||||
block_value,
|
||||
} => block_value,
|
||||
Self::PayloadAndBlobs {
|
||||
payload: _,
|
||||
block_value,
|
||||
kzg_commitments: _,
|
||||
blobs: _,
|
||||
} => block_value,
|
||||
}
|
||||
}
|
||||
pub fn default_at_fork(fork_name: ForkName) -> Result<Self, BeaconStateError> {
|
||||
Ok(match fork_name {
|
||||
ForkName::Base | ForkName::Altair | ForkName::Merge | ForkName::Capella => {
|
||||
BlockProposalContents::Payload(Payload::default_at_fork(fork_name)?)
|
||||
BlockProposalContents::Payload {
|
||||
payload: Payload::default_at_fork(fork_name)?,
|
||||
block_value: Uint256::zero(),
|
||||
}
|
||||
}
|
||||
ForkName::Eip4844 => BlockProposalContents::PayloadAndBlobs {
|
||||
payload: Payload::default_at_fork(fork_name)?,
|
||||
block_value: Uint256::zero(),
|
||||
blobs: VariableList::default(),
|
||||
kzg_commitments: VariableList::default(),
|
||||
},
|
||||
@@ -267,12 +298,7 @@ pub struct ExecutionLayer<T: EthSpec> {
|
||||
|
||||
impl<T: EthSpec> ExecutionLayer<T> {
|
||||
/// Instantiate `Self` with an Execution engine specified in `Config`, using JSON-RPC via HTTP.
|
||||
pub fn from_config(
|
||||
config: Config,
|
||||
executor: TaskExecutor,
|
||||
log: Logger,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<Self, Error> {
|
||||
pub fn from_config(config: Config, executor: TaskExecutor, log: Logger) -> Result<Self, Error> {
|
||||
let Config {
|
||||
execution_endpoints: urls,
|
||||
builder_url,
|
||||
@@ -327,13 +353,8 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
let engine: Engine = {
|
||||
let auth = Auth::new(jwt_key, jwt_id, jwt_version);
|
||||
debug!(log, "Loaded execution endpoint"; "endpoint" => %execution_url, "jwt_path" => ?secret_file.as_path());
|
||||
let api = HttpJsonRpc::new_with_auth(
|
||||
execution_url,
|
||||
auth,
|
||||
execution_timeout_multiplier,
|
||||
&spec,
|
||||
)
|
||||
.map_err(Error::ApiError)?;
|
||||
let api = HttpJsonRpc::new_with_auth(execution_url, auth, execution_timeout_multiplier)
|
||||
.map_err(Error::ApiError)?;
|
||||
Engine::new(api, executor.clone(), &log)
|
||||
};
|
||||
|
||||
@@ -377,12 +398,12 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
&self.inner.builder
|
||||
}
|
||||
|
||||
/// Cache a full payload, keyed on the `tree_hash_root` of its `transactions` field.
|
||||
fn cache_payload(&self, payload: &ExecutionPayload<T>) -> Option<ExecutionPayload<T>> {
|
||||
self.inner.payload_cache.put(payload.clone())
|
||||
/// Cache a full payload, keyed on the `tree_hash_root` of the payload
|
||||
fn cache_payload(&self, payload: ExecutionPayloadRef<T>) -> Option<ExecutionPayload<T>> {
|
||||
self.inner.payload_cache.put(payload.clone_from_ref())
|
||||
}
|
||||
|
||||
/// Attempt to retrieve a full payload from the payload cache by the `transactions_root`.
|
||||
/// Attempt to retrieve a full payload from the payload cache by the payload root
|
||||
pub fn get_payload_by_root(&self, root: &Hash256) -> Option<ExecutionPayload<T>> {
|
||||
self.inner.payload_cache.pop(root)
|
||||
}
|
||||
@@ -819,17 +840,32 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
"parent_hash" => ?parent_hash,
|
||||
);
|
||||
|
||||
let relay_value = relay.data.message.value;
|
||||
let local_value = *local.block_value();
|
||||
if local_value >= relay_value {
|
||||
info!(
|
||||
self.log(),
|
||||
"Local block is more profitable than relay block";
|
||||
"local_block_value" => %local_value,
|
||||
"relay_value" => %relay_value
|
||||
);
|
||||
return Ok(ProvenancedPayload::Local(local));
|
||||
}
|
||||
|
||||
match verify_builder_bid(
|
||||
&relay,
|
||||
parent_hash,
|
||||
payload_attributes.prev_randao(),
|
||||
payload_attributes.timestamp(),
|
||||
payload_attributes,
|
||||
Some(local.payload().block_number()),
|
||||
self.inner.builder_profit_threshold,
|
||||
current_fork,
|
||||
spec,
|
||||
) {
|
||||
Ok(()) => Ok(ProvenancedPayload::Builder(
|
||||
BlockProposalContents::Payload(relay.data.message.header),
|
||||
BlockProposalContents::Payload {
|
||||
payload: relay.data.message.header,
|
||||
block_value: relay.data.message.value,
|
||||
},
|
||||
)),
|
||||
Err(reason) if !reason.payload_invalid() => {
|
||||
info!(
|
||||
@@ -873,19 +909,25 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
match verify_builder_bid(
|
||||
&relay,
|
||||
parent_hash,
|
||||
payload_attributes.prev_randao(),
|
||||
payload_attributes.timestamp(),
|
||||
payload_attributes,
|
||||
None,
|
||||
self.inner.builder_profit_threshold,
|
||||
current_fork,
|
||||
spec,
|
||||
) {
|
||||
Ok(()) => Ok(ProvenancedPayload::Builder(
|
||||
BlockProposalContents::Payload(relay.data.message.header),
|
||||
BlockProposalContents::Payload {
|
||||
payload: relay.data.message.header,
|
||||
block_value: relay.data.message.value,
|
||||
},
|
||||
)),
|
||||
// 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(
|
||||
BlockProposalContents::Payload(relay.data.message.header),
|
||||
BlockProposalContents::Payload {
|
||||
payload: relay.data.message.header,
|
||||
block_value: relay.data.message.value,
|
||||
},
|
||||
)),
|
||||
Err(reason) => {
|
||||
metrics::inc_counter_vec(
|
||||
@@ -999,7 +1041,7 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
payload_attributes: &PayloadAttributes,
|
||||
forkchoice_update_params: ForkchoiceUpdateParameters,
|
||||
current_fork: ForkName,
|
||||
f: fn(&ExecutionLayer<T>, &ExecutionPayload<T>) -> Option<ExecutionPayload<T>>,
|
||||
f: fn(&ExecutionLayer<T>, ExecutionPayloadRef<T>) -> Option<ExecutionPayload<T>>,
|
||||
) -> Result<BlockProposalContents<T, Payload>, Error> {
|
||||
self.engine()
|
||||
.request(move |engine| async move {
|
||||
@@ -1082,9 +1124,9 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
);
|
||||
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() != payload_attributes.suggested_fee_recipient() {
|
||||
let (blob, payload_response) = tokio::join!(blob_fut, payload_fut);
|
||||
let (execution_payload, block_value) = payload_response.map(|payload_response| {
|
||||
if payload_response.execution_payload_ref().fee_recipient() != payload_attributes.suggested_fee_recipient() {
|
||||
error!(
|
||||
self.log(),
|
||||
"Inconsistent fee recipient";
|
||||
@@ -1093,28 +1135,32 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
indicate that fees are being diverted to another address. Please \
|
||||
ensure that the value of suggested_fee_recipient is set correctly and \
|
||||
that the Execution Engine is trusted.",
|
||||
"fee_recipient" => ?full_payload.fee_recipient(),
|
||||
"fee_recipient" => ?payload_response.execution_payload_ref().fee_recipient(),
|
||||
"suggested_fee_recipient" => ?payload_attributes.suggested_fee_recipient(),
|
||||
);
|
||||
}
|
||||
if f(self, &full_payload).is_some() {
|
||||
if f(self, payload_response.execution_payload_ref()).is_some() {
|
||||
warn!(
|
||||
self.log(),
|
||||
"Duplicate payload cached, this might indicate redundant proposal \
|
||||
attempts."
|
||||
);
|
||||
}
|
||||
full_payload.into()
|
||||
payload_response.into()
|
||||
})?;
|
||||
if let Some(blob) = blob.transpose()? {
|
||||
// FIXME(sean) cache blobs
|
||||
Ok(BlockProposalContents::PayloadAndBlobs {
|
||||
payload,
|
||||
payload: execution_payload.into(),
|
||||
block_value,
|
||||
blobs: blob.blobs,
|
||||
kzg_commitments: blob.kzgs,
|
||||
})
|
||||
} else {
|
||||
Ok(BlockProposalContents::Payload(payload))
|
||||
Ok(BlockProposalContents::Payload {
|
||||
payload: execution_payload.into(),
|
||||
block_value,
|
||||
})
|
||||
}
|
||||
})
|
||||
.await
|
||||
@@ -1373,6 +1419,26 @@ impl<T: EthSpec> ExecutionLayer<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the execution engine capabilities resulting from a call to
|
||||
/// engine_exchangeCapabilities. If the capabilities cache is not populated,
|
||||
/// or if it is populated with a cached result of age >= `age_limit`, this
|
||||
/// method will fetch the result from the execution engine and populate the
|
||||
/// cache before returning it. Otherwise it will return a cached result from
|
||||
/// a previous call.
|
||||
///
|
||||
/// Set `age_limit` to `None` to always return the cached result
|
||||
/// Set `age_limit` to `Some(Duration::ZERO)` to force fetching from EE
|
||||
pub async fn get_engine_capabilities(
|
||||
&self,
|
||||
age_limit: Option<Duration>,
|
||||
) -> Result<EngineCapabilities, Error> {
|
||||
self.engine()
|
||||
.request(|engine| engine.get_engine_capabilities(age_limit))
|
||||
.await
|
||||
.map_err(Box::new)
|
||||
.map_err(Error::EngineError)
|
||||
}
|
||||
|
||||
/// Used during block production to determine if the merge has been triggered.
|
||||
///
|
||||
/// ## Specification
|
||||
@@ -1801,6 +1867,11 @@ enum InvalidBuilderPayload {
|
||||
signature: Signature,
|
||||
pubkey: PublicKeyBytes,
|
||||
},
|
||||
#[allow(dead_code)]
|
||||
WithdrawalsRoot {
|
||||
payload: Hash256,
|
||||
expected: Hash256,
|
||||
},
|
||||
}
|
||||
|
||||
impl InvalidBuilderPayload {
|
||||
@@ -1815,6 +1886,7 @@ impl InvalidBuilderPayload {
|
||||
InvalidBuilderPayload::BlockNumber { .. } => true,
|
||||
InvalidBuilderPayload::Fork { .. } => true,
|
||||
InvalidBuilderPayload::Signature { .. } => true,
|
||||
InvalidBuilderPayload::WithdrawalsRoot { .. } => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1850,6 +1922,13 @@ impl fmt::Display for InvalidBuilderPayload {
|
||||
"invalid payload signature {} for pubkey {}",
|
||||
signature, pubkey
|
||||
),
|
||||
InvalidBuilderPayload::WithdrawalsRoot { payload, expected } => {
|
||||
write!(
|
||||
f,
|
||||
"payload withdrawals root was {} not {}",
|
||||
payload, expected
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1858,10 +1937,10 @@ impl fmt::Display for InvalidBuilderPayload {
|
||||
fn verify_builder_bid<T: EthSpec, Payload: AbstractExecPayload<T>>(
|
||||
bid: &ForkVersionedResponse<SignedBuilderBid<T, Payload>>,
|
||||
parent_hash: ExecutionBlockHash,
|
||||
prev_randao: Hash256,
|
||||
timestamp: u64,
|
||||
payload_attributes: &PayloadAttributes,
|
||||
block_number: Option<u64>,
|
||||
profit_threshold: Uint256,
|
||||
current_fork: ForkName,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Box<InvalidBuilderPayload>> {
|
||||
let is_signature_valid = bid.data.verify_signature(spec);
|
||||
@@ -1888,29 +1967,25 @@ fn verify_builder_bid<T: EthSpec, Payload: AbstractExecPayload<T>>(
|
||||
payload: header.parent_hash(),
|
||||
expected: parent_hash,
|
||||
}))
|
||||
} else if header.prev_randao() != prev_randao {
|
||||
} else if header.prev_randao() != payload_attributes.prev_randao() {
|
||||
Err(Box::new(InvalidBuilderPayload::PrevRandao {
|
||||
payload: header.prev_randao(),
|
||||
expected: prev_randao,
|
||||
expected: payload_attributes.prev_randao(),
|
||||
}))
|
||||
} else if header.timestamp() != timestamp {
|
||||
} else if header.timestamp() != payload_attributes.timestamp() {
|
||||
Err(Box::new(InvalidBuilderPayload::Timestamp {
|
||||
payload: header.timestamp(),
|
||||
expected: timestamp,
|
||||
expected: payload_attributes.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.
|
||||
} else if bid.version != Some(current_fork) {
|
||||
Err(Box::new(InvalidBuilderPayload::Fork {
|
||||
payload: bid.version,
|
||||
expected: ForkName::Merge,
|
||||
expected: current_fork,
|
||||
}))
|
||||
} else if !is_signature_valid {
|
||||
Err(Box::new(InvalidBuilderPayload::Signature {
|
||||
|
||||
@@ -1,25 +1,33 @@
|
||||
use super::Context;
|
||||
use crate::engine_api::{http::*, *};
|
||||
use crate::json_structures::*;
|
||||
use crate::test_utils::DEFAULT_MOCK_EL_PAYLOAD_VALUE_WEI;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde_json::Value as JsonValue;
|
||||
use std::sync::Arc;
|
||||
use types::{EthSpec, ForkName};
|
||||
|
||||
pub const GENERIC_ERROR_CODE: i64 = -1234;
|
||||
pub const BAD_PARAMS_ERROR_CODE: i64 = -32602;
|
||||
pub const UNKNOWN_PAYLOAD_ERROR_CODE: i64 = -38001;
|
||||
pub const FORK_REQUEST_MISMATCH_ERROR_CODE: i64 = -32000;
|
||||
|
||||
pub async fn handle_rpc<T: EthSpec>(
|
||||
body: JsonValue,
|
||||
ctx: Arc<Context<T>>,
|
||||
) -> Result<JsonValue, String> {
|
||||
) -> Result<JsonValue, (String, i64)> {
|
||||
*ctx.previous_request.lock() = Some(body.clone());
|
||||
|
||||
let method = body
|
||||
.get("method")
|
||||
.and_then(JsonValue::as_str)
|
||||
.ok_or_else(|| "missing/invalid method field".to_string())?;
|
||||
.ok_or_else(|| "missing/invalid method field".to_string())
|
||||
.map_err(|s| (s, GENERIC_ERROR_CODE))?;
|
||||
|
||||
let params = body
|
||||
.get("params")
|
||||
.ok_or_else(|| "missing/invalid params field".to_string())?;
|
||||
.ok_or_else(|| "missing/invalid params field".to_string())
|
||||
.map_err(|s| (s, GENERIC_ERROR_CODE))?;
|
||||
|
||||
match method {
|
||||
ETH_SYNCING => Ok(JsonValue::Bool(false)),
|
||||
@@ -27,7 +35,8 @@ pub async fn handle_rpc<T: EthSpec>(
|
||||
let tag = params
|
||||
.get(0)
|
||||
.and_then(JsonValue::as_str)
|
||||
.ok_or_else(|| "missing/invalid params[0] value".to_string())?;
|
||||
.ok_or_else(|| "missing/invalid params[0] value".to_string())
|
||||
.map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?;
|
||||
|
||||
match tag {
|
||||
"latest" => Ok(serde_json::to_value(
|
||||
@@ -36,7 +45,10 @@ pub async fn handle_rpc<T: EthSpec>(
|
||||
.latest_execution_block(),
|
||||
)
|
||||
.unwrap()),
|
||||
other => Err(format!("The tag {} is not supported", other)),
|
||||
other => Err((
|
||||
format!("The tag {} is not supported", other),
|
||||
BAD_PARAMS_ERROR_CODE,
|
||||
)),
|
||||
}
|
||||
}
|
||||
ETH_GET_BLOCK_BY_HASH => {
|
||||
@@ -47,7 +59,8 @@ pub async fn handle_rpc<T: EthSpec>(
|
||||
.and_then(|s| {
|
||||
s.parse()
|
||||
.map_err(|e| format!("unable to parse hash: {:?}", e))
|
||||
})?;
|
||||
})
|
||||
.map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?;
|
||||
|
||||
// If we have a static response set, just return that.
|
||||
if let Some(response) = *ctx.static_get_block_by_hash_response.lock() {
|
||||
@@ -57,7 +70,8 @@ pub async fn handle_rpc<T: EthSpec>(
|
||||
let full_tx = params
|
||||
.get(1)
|
||||
.and_then(JsonValue::as_bool)
|
||||
.ok_or_else(|| "missing/invalid params[1] value".to_string())?;
|
||||
.ok_or_else(|| "missing/invalid params[1] value".to_string())
|
||||
.map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?;
|
||||
if full_tx {
|
||||
Ok(serde_json::to_value(
|
||||
ctx.execution_block_generator
|
||||
@@ -76,15 +90,17 @@ pub async fn handle_rpc<T: EthSpec>(
|
||||
}
|
||||
ENGINE_NEW_PAYLOAD_V1 | ENGINE_NEW_PAYLOAD_V2 | ENGINE_NEW_PAYLOAD_V3 => {
|
||||
let request = match method {
|
||||
ENGINE_NEW_PAYLOAD_V1 => {
|
||||
JsonExecutionPayload::V1(get_param::<JsonExecutionPayloadV1<T>>(params, 0)?)
|
||||
}
|
||||
ENGINE_NEW_PAYLOAD_V1 => JsonExecutionPayload::V1(
|
||||
get_param::<JsonExecutionPayloadV1<T>>(params, 0)
|
||||
.map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?,
|
||||
),
|
||||
ENGINE_NEW_PAYLOAD_V2 => get_param::<JsonExecutionPayloadV2<T>>(params, 0)
|
||||
.map(|jep| JsonExecutionPayload::V2(jep))
|
||||
.or_else(|_| {
|
||||
get_param::<JsonExecutionPayloadV1<T>>(params, 0)
|
||||
.map(|jep| JsonExecutionPayload::V1(jep))
|
||||
})?,
|
||||
})
|
||||
.map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?,
|
||||
ENGINE_NEW_PAYLOAD_V3 => get_param::<JsonExecutionPayloadV3<T>>(params, 0)
|
||||
.map(|jep| JsonExecutionPayload::V3(jep))
|
||||
.or_else(|_| {
|
||||
@@ -106,37 +122,46 @@ pub async fn handle_rpc<T: EthSpec>(
|
||||
match fork {
|
||||
ForkName::Merge => {
|
||||
if matches!(request, JsonExecutionPayload::V2(_)) {
|
||||
return Err(format!(
|
||||
"{} called with `ExecutionPayloadV2` before capella fork!",
|
||||
method
|
||||
return Err((
|
||||
format!(
|
||||
"{} called with `ExecutionPayloadV2` before Capella fork!",
|
||||
method
|
||||
),
|
||||
GENERIC_ERROR_CODE,
|
||||
));
|
||||
}
|
||||
}
|
||||
ForkName::Capella => {
|
||||
if method == ENGINE_NEW_PAYLOAD_V1 {
|
||||
return Err(format!("{} called after capella fork!", method));
|
||||
return Err((
|
||||
format!("{} called after Capella fork!", method),
|
||||
GENERIC_ERROR_CODE,
|
||||
));
|
||||
}
|
||||
if matches!(request, JsonExecutionPayload::V1(_)) {
|
||||
return Err(format!(
|
||||
"{} called with `ExecutionPayloadV1` after capella fork!",
|
||||
method
|
||||
return Err((
|
||||
format!(
|
||||
"{} called with `ExecutionPayloadV1` after Capella fork!",
|
||||
method
|
||||
),
|
||||
GENERIC_ERROR_CODE,
|
||||
));
|
||||
}
|
||||
}
|
||||
ForkName::Eip4844 => {
|
||||
if method == ENGINE_NEW_PAYLOAD_V1 || method == ENGINE_NEW_PAYLOAD_V2 {
|
||||
return Err(format!("{} called after capella fork!", method));
|
||||
return Err((format!("{} called after capella fork!", method), GENERIC_ERROR_CODE));
|
||||
}
|
||||
if matches!(request, JsonExecutionPayload::V1(_)) {
|
||||
return Err(format!(
|
||||
return Err((format!(
|
||||
"{} called with `ExecutionPayloadV1` after eip4844 fork!",
|
||||
method
|
||||
));
|
||||
), GENERIC_ERROR_CODE));
|
||||
}
|
||||
if matches!(request, JsonExecutionPayload::V2(_)) {
|
||||
return Err(format!(
|
||||
return Err((format!(
|
||||
"{} called with `ExecutionPayloadV2` after eip4844 fork!",
|
||||
method
|
||||
method), GENERIC_ERROR_CODE
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -174,14 +199,20 @@ pub async fn handle_rpc<T: EthSpec>(
|
||||
Ok(serde_json::to_value(JsonPayloadStatusV1::from(response)).unwrap())
|
||||
}
|
||||
ENGINE_GET_PAYLOAD_V1 | ENGINE_GET_PAYLOAD_V2 | ENGINE_GET_PAYLOAD_V3 => {
|
||||
let request: JsonPayloadIdRequest = get_param(params, 0)?;
|
||||
let request: JsonPayloadIdRequest =
|
||||
get_param(params, 0).map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?;
|
||||
let id = request.into();
|
||||
|
||||
let response = ctx
|
||||
.execution_block_generator
|
||||
.write()
|
||||
.get_payload(&id)
|
||||
.ok_or_else(|| format!("no payload for id {:?}", id))?;
|
||||
.ok_or_else(|| {
|
||||
(
|
||||
format!("no payload for id {:?}", id),
|
||||
UNKNOWN_PAYLOAD_ERROR_CODE,
|
||||
)
|
||||
})?;
|
||||
|
||||
// validate method called correctly according to shanghai fork time
|
||||
if ctx
|
||||
@@ -191,7 +222,10 @@ pub async fn handle_rpc<T: EthSpec>(
|
||||
== ForkName::Capella
|
||||
&& method == ENGINE_GET_PAYLOAD_V1
|
||||
{
|
||||
return Err(format!("{} called after capella fork!", method));
|
||||
return Err((
|
||||
format!("{} called after Capella fork!", method),
|
||||
FORK_REQUEST_MISMATCH_ERROR_CODE,
|
||||
));
|
||||
}
|
||||
// validate method called correctly according to eip4844 fork time
|
||||
if ctx
|
||||
@@ -201,7 +235,7 @@ pub async fn handle_rpc<T: EthSpec>(
|
||||
== ForkName::Eip4844
|
||||
&& (method == ENGINE_GET_PAYLOAD_V1 || method == ENGINE_GET_PAYLOAD_V2)
|
||||
{
|
||||
return Err(format!("{} called after eip4844 fork!", method));
|
||||
return Err((format!("{} called after eip4844 fork!", method), FORK_REQUEST_MISMATCH_ERROR_CODE));
|
||||
}
|
||||
|
||||
match method {
|
||||
@@ -212,14 +246,14 @@ pub async fn handle_rpc<T: EthSpec>(
|
||||
JsonExecutionPayload::V1(execution_payload) => {
|
||||
serde_json::to_value(JsonGetPayloadResponseV1 {
|
||||
execution_payload,
|
||||
block_value: 0.into(),
|
||||
block_value: DEFAULT_MOCK_EL_PAYLOAD_VALUE_WEI.into(),
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
JsonExecutionPayload::V2(execution_payload) => {
|
||||
serde_json::to_value(JsonGetPayloadResponseV2 {
|
||||
execution_payload,
|
||||
block_value: 0.into(),
|
||||
block_value: DEFAULT_MOCK_EL_PAYLOAD_VALUE_WEI.into(),
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
@@ -229,61 +263,65 @@ pub async fn handle_rpc<T: EthSpec>(
|
||||
JsonExecutionPayload::V1(execution_payload) => {
|
||||
serde_json::to_value(JsonGetPayloadResponseV1 {
|
||||
execution_payload,
|
||||
block_value: 0.into(),
|
||||
block_value: DEFAULT_MOCK_EL_PAYLOAD_VALUE_WEI.into()
|
||||
})
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
}
|
||||
JsonExecutionPayload::V2(execution_payload) => {
|
||||
serde_json::to_value(JsonGetPayloadResponseV2 {
|
||||
execution_payload,
|
||||
block_value: 0.into(),
|
||||
block_value: DEFAULT_MOCK_EL_PAYLOAD_VALUE_WEI.into()
|
||||
})
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
}
|
||||
JsonExecutionPayload::V3(execution_payload) => {
|
||||
serde_json::to_value(JsonGetPayloadResponseV3 {
|
||||
execution_payload,
|
||||
block_value: 0.into(),
|
||||
block_value: DEFAULT_MOCK_EL_PAYLOAD_VALUE_WEI.into()
|
||||
})
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
}
|
||||
}),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
ENGINE_FORKCHOICE_UPDATED_V1 | ENGINE_FORKCHOICE_UPDATED_V2 => {
|
||||
let forkchoice_state: JsonForkchoiceStateV1 = get_param(params, 0)?;
|
||||
let forkchoice_state: JsonForkchoiceStateV1 =
|
||||
get_param(params, 0).map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?;
|
||||
let payload_attributes = match method {
|
||||
ENGINE_FORKCHOICE_UPDATED_V1 => {
|
||||
let jpa1: Option<JsonPayloadAttributesV1> = get_param(params, 1)?;
|
||||
let jpa1: Option<JsonPayloadAttributesV1> =
|
||||
get_param(params, 1).map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?;
|
||||
jpa1.map(JsonPayloadAttributes::V1)
|
||||
}
|
||||
ENGINE_FORKCHOICE_UPDATED_V2 => {
|
||||
// we can't use `deny_unknown_fields` without breaking compatibility with some
|
||||
// clients that haven't updated to the latest engine_api spec. So instead we'll
|
||||
// need to deserialize based on timestamp
|
||||
get_param::<Option<JsonPayloadAttributes>>(params, 1).and_then(|pa| {
|
||||
pa.and_then(|pa| {
|
||||
match ctx
|
||||
.execution_block_generator
|
||||
.read()
|
||||
.get_fork_at_timestamp(*pa.timestamp())
|
||||
{
|
||||
ForkName::Merge => {
|
||||
get_param::<Option<JsonPayloadAttributesV1>>(params, 1)
|
||||
.map(|opt| opt.map(JsonPayloadAttributes::V1))
|
||||
.transpose()
|
||||
get_param::<Option<JsonPayloadAttributes>>(params, 1)
|
||||
.and_then(|pa| {
|
||||
pa.and_then(|pa| {
|
||||
match ctx
|
||||
.execution_block_generator
|
||||
.read()
|
||||
.get_fork_at_timestamp(*pa.timestamp())
|
||||
{
|
||||
ForkName::Merge => {
|
||||
get_param::<Option<JsonPayloadAttributesV1>>(params, 1)
|
||||
.map(|opt| opt.map(JsonPayloadAttributes::V1))
|
||||
.transpose()
|
||||
}
|
||||
ForkName::Capella => {
|
||||
get_param::<Option<JsonPayloadAttributesV2>>(params, 1)
|
||||
.map(|opt| opt.map(JsonPayloadAttributes::V2))
|
||||
.transpose()
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
ForkName::Capella | ForkName::Eip4844 => {
|
||||
get_param::<Option<JsonPayloadAttributesV2>>(params, 1)
|
||||
.map(|opt| opt.map(JsonPayloadAttributes::V2))
|
||||
.transpose()
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
})
|
||||
.transpose()
|
||||
})
|
||||
.transpose()
|
||||
})?
|
||||
.map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
@@ -297,20 +335,29 @@ pub async fn handle_rpc<T: EthSpec>(
|
||||
{
|
||||
ForkName::Merge => {
|
||||
if matches!(pa, JsonPayloadAttributes::V2(_)) {
|
||||
return Err(format!(
|
||||
"{} called with `JsonPayloadAttributesV2` before capella fork!",
|
||||
method
|
||||
return Err((
|
||||
format!(
|
||||
"{} called with `JsonPayloadAttributesV2` before Capella fork!",
|
||||
method
|
||||
),
|
||||
GENERIC_ERROR_CODE,
|
||||
));
|
||||
}
|
||||
}
|
||||
ForkName::Capella | ForkName::Eip4844 => {
|
||||
if method == ENGINE_FORKCHOICE_UPDATED_V1 {
|
||||
return Err(format!("{} called after capella fork!", method));
|
||||
return Err((
|
||||
format!("{} called after Capella fork!", method),
|
||||
FORK_REQUEST_MISMATCH_ERROR_CODE,
|
||||
));
|
||||
}
|
||||
if matches!(pa, JsonPayloadAttributes::V1(_)) {
|
||||
return Err(format!(
|
||||
"{} called with `JsonPayloadAttributesV1` after capella fork!",
|
||||
method
|
||||
return Err((
|
||||
format!(
|
||||
"{} called with `JsonPayloadAttributesV1` after Capella fork!",
|
||||
method
|
||||
),
|
||||
FORK_REQUEST_MISMATCH_ERROR_CODE,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -337,10 +384,14 @@ pub async fn handle_rpc<T: EthSpec>(
|
||||
return Ok(serde_json::to_value(response).unwrap());
|
||||
}
|
||||
|
||||
let mut response = ctx.execution_block_generator.write().forkchoice_updated(
|
||||
forkchoice_state.into(),
|
||||
payload_attributes.map(|json| json.into()),
|
||||
)?;
|
||||
let mut response = ctx
|
||||
.execution_block_generator
|
||||
.write()
|
||||
.forkchoice_updated(
|
||||
forkchoice_state.into(),
|
||||
payload_attributes.map(|json| json.into()),
|
||||
)
|
||||
.map_err(|s| (s, GENERIC_ERROR_CODE))?;
|
||||
|
||||
if let Some(mut status) = ctx.static_forkchoice_updated_response.lock().clone() {
|
||||
if status.status == PayloadStatusV1Status::Valid {
|
||||
@@ -361,9 +412,13 @@ pub async fn handle_rpc<T: EthSpec>(
|
||||
};
|
||||
Ok(serde_json::to_value(transition_config).unwrap())
|
||||
}
|
||||
other => Err(format!(
|
||||
"The method {} does not exist/is not available",
|
||||
other
|
||||
ENGINE_EXCHANGE_CAPABILITIES => {
|
||||
let engine_capabilities = ctx.engine_capabilities.read();
|
||||
Ok(serde_json::to_value(engine_capabilities.to_response()).unwrap())
|
||||
}
|
||||
other => Err((
|
||||
format!("The method {} does not exist/is not available", other),
|
||||
METHOD_NOT_FOUND_CODE,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::test_utils::DEFAULT_JWT_SECRET;
|
||||
use crate::test_utils::{DEFAULT_BUILDER_PAYLOAD_VALUE_WEI, DEFAULT_JWT_SECRET};
|
||||
use crate::{Config, ExecutionLayer, PayloadAttributes};
|
||||
use async_trait::async_trait;
|
||||
use eth2::types::{BlockId, StateId, ValidatorId};
|
||||
@@ -84,8 +84,7 @@ impl<E: EthSpec> TestingBuilder<E> {
|
||||
};
|
||||
|
||||
let el =
|
||||
ExecutionLayer::from_config(config, executor.clone(), executor.log().clone(), &spec)
|
||||
.unwrap();
|
||||
ExecutionLayer::from_config(config, executor.clone(), executor.log().clone()).unwrap();
|
||||
|
||||
// This should probably be done for all fields, we only update ones we are testing with so far.
|
||||
let mut context = Context::for_mainnet();
|
||||
@@ -329,7 +328,7 @@ impl<E: EthSpec> mev_build_rs::BlindedBlockProvider for MockBuilder<E> {
|
||||
|
||||
let mut message = BuilderBid {
|
||||
header,
|
||||
value: ssz_rs::U256::default(),
|
||||
value: to_ssz_rs(&Uint256::from(DEFAULT_BUILDER_PAYLOAD_VALUE_WEI))?,
|
||||
public_key: self.builder_sk.public_key(),
|
||||
};
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ impl<T: EthSpec> MockExecutionLayer<T> {
|
||||
DEFAULT_TERMINAL_BLOCK,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
Some(JwtKey::from_slice(&DEFAULT_JWT_SECRET).unwrap()),
|
||||
spec,
|
||||
None,
|
||||
@@ -41,6 +42,7 @@ impl<T: EthSpec> MockExecutionLayer<T> {
|
||||
terminal_block: u64,
|
||||
shanghai_time: Option<u64>,
|
||||
eip4844_time: Option<u64>,
|
||||
builder_threshold: Option<u128>,
|
||||
jwt_key: Option<JwtKey>,
|
||||
spec: ChainSpec,
|
||||
builder_url: Option<SensitiveUrl>,
|
||||
@@ -69,12 +71,11 @@ impl<T: EthSpec> MockExecutionLayer<T> {
|
||||
builder_url,
|
||||
secret_files: vec![path],
|
||||
suggested_fee_recipient: Some(Address::repeat_byte(42)),
|
||||
builder_profit_threshold: DEFAULT_BUILDER_THRESHOLD_WEI,
|
||||
builder_profit_threshold: builder_threshold.unwrap_or(DEFAULT_BUILDER_THRESHOLD_WEI),
|
||||
..Default::default()
|
||||
};
|
||||
let el =
|
||||
ExecutionLayer::from_config(config, executor.clone(), executor.log().clone(), &spec)
|
||||
.unwrap();
|
||||
ExecutionLayer::from_config(config, executor.clone(), executor.log().clone()).unwrap();
|
||||
|
||||
Self {
|
||||
server,
|
||||
@@ -106,7 +107,7 @@ impl<T: EthSpec> MockExecutionLayer<T> {
|
||||
prev_randao,
|
||||
Address::repeat_byte(42),
|
||||
// FIXME: think about how to handle different forks / withdrawals here..
|
||||
Some(vec![]),
|
||||
None,
|
||||
);
|
||||
|
||||
// Insert a proposer to ensure the fork choice updated command works.
|
||||
|
||||
@@ -22,6 +22,7 @@ use tokio::{runtime, sync::oneshot};
|
||||
use types::{EthSpec, ExecutionBlockHash, Uint256};
|
||||
use warp::{http::StatusCode, Filter, Rejection};
|
||||
|
||||
use crate::EngineCapabilities;
|
||||
pub use execution_block_generator::{generate_pow_block, Block, ExecutionBlockGenerator};
|
||||
pub use hook::Hook;
|
||||
pub use mock_builder::{Context as MockBuilderContext, MockBuilder, Operation, TestingBuilder};
|
||||
@@ -31,6 +32,17 @@ pub const DEFAULT_TERMINAL_DIFFICULTY: u64 = 6400;
|
||||
pub const DEFAULT_TERMINAL_BLOCK: u64 = 64;
|
||||
pub const DEFAULT_JWT_SECRET: [u8; 32] = [42; 32];
|
||||
pub const DEFAULT_BUILDER_THRESHOLD_WEI: u128 = 1_000_000_000_000_000_000;
|
||||
pub const DEFAULT_MOCK_EL_PAYLOAD_VALUE_WEI: u128 = 10_000_000_000_000_000;
|
||||
pub const DEFAULT_BUILDER_PAYLOAD_VALUE_WEI: u128 = 20_000_000_000_000_000;
|
||||
pub const DEFAULT_ENGINE_CAPABILITIES: EngineCapabilities = EngineCapabilities {
|
||||
new_payload_v1: true,
|
||||
new_payload_v2: true,
|
||||
forkchoice_updated_v1: true,
|
||||
forkchoice_updated_v2: true,
|
||||
get_payload_v1: true,
|
||||
get_payload_v2: true,
|
||||
exchange_transition_configuration_v1: true,
|
||||
};
|
||||
|
||||
mod execution_block_generator;
|
||||
mod handle_rpc;
|
||||
@@ -117,6 +129,7 @@ impl<T: EthSpec> MockServer<T> {
|
||||
hook: <_>::default(),
|
||||
new_payload_statuses: <_>::default(),
|
||||
fcu_payload_statuses: <_>::default(),
|
||||
engine_capabilities: Arc::new(RwLock::new(DEFAULT_ENGINE_CAPABILITIES)),
|
||||
_phantom: PhantomData,
|
||||
});
|
||||
|
||||
@@ -147,6 +160,10 @@ impl<T: EthSpec> MockServer<T> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_engine_capabilities(&self, engine_capabilities: EngineCapabilities) {
|
||||
*self.ctx.engine_capabilities.write() = engine_capabilities;
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
handle: &runtime::Handle,
|
||||
jwt_key: JwtKey,
|
||||
@@ -469,6 +486,7 @@ pub struct Context<T: EthSpec> {
|
||||
pub new_payload_statuses: Arc<Mutex<HashMap<ExecutionBlockHash, PayloadStatusV1>>>,
|
||||
pub fcu_payload_statuses: Arc<Mutex<HashMap<ExecutionBlockHash, PayloadStatusV1>>>,
|
||||
|
||||
pub engine_capabilities: Arc<RwLock<EngineCapabilities>>,
|
||||
pub _phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
@@ -620,11 +638,11 @@ pub fn serve<T: EthSpec>(
|
||||
"jsonrpc": JSONRPC_VERSION,
|
||||
"result": result
|
||||
}),
|
||||
Err(message) => json!({
|
||||
Err((message, code)) => json!({
|
||||
"id": id,
|
||||
"jsonrpc": JSONRPC_VERSION,
|
||||
"error": {
|
||||
"code": -1234, // Junk error code.
|
||||
"code": code,
|
||||
"message": message
|
||||
}
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user