Add Fulu boilerplate (#6695)

* Add Fulu boilerplate

* Add more boilerplate

* Change fulu_time to osaka_time

* Merge branch 'unstable' into fulu-boilerplate

* Fix tests

* Merge branch 'unstable' into fulu-boilerplate

* More test fixes

* Apply suggestions

* Remove `get_payload` boilerplate

* Add lightclient fulu types and fix beacon-chain-tests

* Disable Fulu in ef-tests

* Reduce boilerplate for future forks

* Small fixes

* One more fix

* Apply suggestions

* Merge branch 'unstable' into fulu-boilerplate

* Fix lints
This commit is contained in:
Mac L
2025-01-10 09:25:23 +04:00
committed by GitHub
parent 722573f7ed
commit ecdf2d891f
91 changed files with 2365 additions and 674 deletions

View File

@@ -3,8 +3,8 @@ use crate::http::{
ENGINE_FORKCHOICE_UPDATED_V1, ENGINE_FORKCHOICE_UPDATED_V2, ENGINE_FORKCHOICE_UPDATED_V3,
ENGINE_GET_BLOBS_V1, ENGINE_GET_CLIENT_VERSION_V1, ENGINE_GET_PAYLOAD_BODIES_BY_HASH_V1,
ENGINE_GET_PAYLOAD_BODIES_BY_RANGE_V1, ENGINE_GET_PAYLOAD_V1, ENGINE_GET_PAYLOAD_V2,
ENGINE_GET_PAYLOAD_V3, ENGINE_GET_PAYLOAD_V4, ENGINE_NEW_PAYLOAD_V1, ENGINE_NEW_PAYLOAD_V2,
ENGINE_NEW_PAYLOAD_V3, ENGINE_NEW_PAYLOAD_V4,
ENGINE_GET_PAYLOAD_V3, ENGINE_GET_PAYLOAD_V4, ENGINE_GET_PAYLOAD_V5, ENGINE_NEW_PAYLOAD_V1,
ENGINE_NEW_PAYLOAD_V2, ENGINE_NEW_PAYLOAD_V3, ENGINE_NEW_PAYLOAD_V4, ENGINE_NEW_PAYLOAD_V5,
};
use eth2::types::{
BlobsBundle, SsePayloadAttributes, SsePayloadAttributesV1, SsePayloadAttributesV2,
@@ -24,7 +24,7 @@ pub use types::{
};
use types::{
ExecutionPayloadBellatrix, ExecutionPayloadCapella, ExecutionPayloadDeneb,
ExecutionPayloadElectra, ExecutionRequests, KzgProofs,
ExecutionPayloadElectra, ExecutionPayloadFulu, ExecutionRequests, KzgProofs,
};
use types::{Graffiti, GRAFFITI_BYTES_LEN};
@@ -35,7 +35,7 @@ mod new_payload_request;
pub use new_payload_request::{
NewPayloadRequest, NewPayloadRequestBellatrix, NewPayloadRequestCapella,
NewPayloadRequestDeneb, NewPayloadRequestElectra,
NewPayloadRequestDeneb, NewPayloadRequestElectra, NewPayloadRequestFulu,
};
pub const LATEST_TAG: &str = "latest";
@@ -261,7 +261,7 @@ pub struct ProposeBlindedBlockResponse {
}
#[superstruct(
variants(Bellatrix, Capella, Deneb, Electra),
variants(Bellatrix, Capella, Deneb, Electra, Fulu),
variant_attributes(derive(Clone, Debug, PartialEq),),
map_into(ExecutionPayload),
map_ref_into(ExecutionPayloadRef),
@@ -281,12 +281,14 @@ pub struct GetPayloadResponse<E: EthSpec> {
pub execution_payload: ExecutionPayloadDeneb<E>,
#[superstruct(only(Electra), partial_getter(rename = "execution_payload_electra"))]
pub execution_payload: ExecutionPayloadElectra<E>,
#[superstruct(only(Fulu), partial_getter(rename = "execution_payload_fulu"))]
pub execution_payload: ExecutionPayloadFulu<E>,
pub block_value: Uint256,
#[superstruct(only(Deneb, Electra))]
#[superstruct(only(Deneb, Electra, Fulu))]
pub blobs_bundle: BlobsBundle<E>,
#[superstruct(only(Deneb, Electra), partial_getter(copy))]
#[superstruct(only(Deneb, Electra, Fulu), partial_getter(copy))]
pub should_override_builder: bool,
#[superstruct(only(Electra))]
#[superstruct(only(Electra, Fulu))]
pub requests: ExecutionRequests<E>,
}
@@ -354,6 +356,12 @@ impl<E: EthSpec> From<GetPayloadResponse<E>>
Some(inner.blobs_bundle),
Some(inner.requests),
),
GetPayloadResponse::Fulu(inner) => (
ExecutionPayload::Fulu(inner.execution_payload),
inner.block_value,
Some(inner.blobs_bundle),
Some(inner.requests),
),
}
}
}
@@ -487,6 +495,34 @@ impl<E: EthSpec> ExecutionPayloadBodyV1<E> {
))
}
}
ExecutionPayloadHeader::Fulu(header) => {
if let Some(withdrawals) = self.withdrawals {
Ok(ExecutionPayload::Fulu(ExecutionPayloadFulu {
parent_hash: header.parent_hash,
fee_recipient: header.fee_recipient,
state_root: header.state_root,
receipts_root: header.receipts_root,
logs_bloom: header.logs_bloom,
prev_randao: header.prev_randao,
block_number: header.block_number,
gas_limit: header.gas_limit,
gas_used: header.gas_used,
timestamp: header.timestamp,
extra_data: header.extra_data,
base_fee_per_gas: header.base_fee_per_gas,
block_hash: header.block_hash,
transactions: self.transactions,
withdrawals,
blob_gas_used: header.blob_gas_used,
excess_blob_gas: header.excess_blob_gas,
}))
} else {
Err(format!(
"block {} is post capella but payload body doesn't have withdrawals",
header.block_hash
))
}
}
}
}
}
@@ -497,6 +533,7 @@ pub struct EngineCapabilities {
pub new_payload_v2: bool,
pub new_payload_v3: bool,
pub new_payload_v4: bool,
pub new_payload_v5: bool,
pub forkchoice_updated_v1: bool,
pub forkchoice_updated_v2: bool,
pub forkchoice_updated_v3: bool,
@@ -506,6 +543,7 @@ pub struct EngineCapabilities {
pub get_payload_v2: bool,
pub get_payload_v3: bool,
pub get_payload_v4: bool,
pub get_payload_v5: bool,
pub get_client_version_v1: bool,
pub get_blobs_v1: bool,
}
@@ -525,6 +563,9 @@ impl EngineCapabilities {
if self.new_payload_v4 {
response.push(ENGINE_NEW_PAYLOAD_V4);
}
if self.new_payload_v5 {
response.push(ENGINE_NEW_PAYLOAD_V5);
}
if self.forkchoice_updated_v1 {
response.push(ENGINE_FORKCHOICE_UPDATED_V1);
}
@@ -552,6 +593,9 @@ impl EngineCapabilities {
if self.get_payload_v4 {
response.push(ENGINE_GET_PAYLOAD_V4);
}
if self.get_payload_v5 {
response.push(ENGINE_GET_PAYLOAD_V5);
}
if self.get_client_version_v1 {
response.push(ENGINE_GET_CLIENT_VERSION_V1);
}

View File

@@ -35,12 +35,14 @@ pub const ENGINE_NEW_PAYLOAD_V1: &str = "engine_newPayloadV1";
pub const ENGINE_NEW_PAYLOAD_V2: &str = "engine_newPayloadV2";
pub const ENGINE_NEW_PAYLOAD_V3: &str = "engine_newPayloadV3";
pub const ENGINE_NEW_PAYLOAD_V4: &str = "engine_newPayloadV4";
pub const ENGINE_NEW_PAYLOAD_V5: &str = "engine_newPayloadV5";
pub const ENGINE_NEW_PAYLOAD_TIMEOUT: Duration = Duration::from_secs(8);
pub const ENGINE_GET_PAYLOAD_V1: &str = "engine_getPayloadV1";
pub const ENGINE_GET_PAYLOAD_V2: &str = "engine_getPayloadV2";
pub const ENGINE_GET_PAYLOAD_V3: &str = "engine_getPayloadV3";
pub const ENGINE_GET_PAYLOAD_V4: &str = "engine_getPayloadV4";
pub const ENGINE_GET_PAYLOAD_V5: &str = "engine_getPayloadV5";
pub const ENGINE_GET_PAYLOAD_TIMEOUT: Duration = Duration::from_secs(2);
pub const ENGINE_FORKCHOICE_UPDATED_V1: &str = "engine_forkchoiceUpdatedV1";
@@ -72,10 +74,12 @@ pub static LIGHTHOUSE_CAPABILITIES: &[&str] = &[
ENGINE_NEW_PAYLOAD_V2,
ENGINE_NEW_PAYLOAD_V3,
ENGINE_NEW_PAYLOAD_V4,
ENGINE_NEW_PAYLOAD_V5,
ENGINE_GET_PAYLOAD_V1,
ENGINE_GET_PAYLOAD_V2,
ENGINE_GET_PAYLOAD_V3,
ENGINE_GET_PAYLOAD_V4,
ENGINE_GET_PAYLOAD_V5,
ENGINE_FORKCHOICE_UPDATED_V1,
ENGINE_FORKCHOICE_UPDATED_V2,
ENGINE_FORKCHOICE_UPDATED_V3,
@@ -825,6 +829,30 @@ impl HttpJsonRpc {
Ok(response.into())
}
pub async fn new_payload_v5_fulu<E: EthSpec>(
&self,
new_payload_request_fulu: NewPayloadRequestFulu<'_, E>,
) -> Result<PayloadStatusV1, Error> {
let params = json!([
JsonExecutionPayload::V5(new_payload_request_fulu.execution_payload.clone().into()),
new_payload_request_fulu.versioned_hashes,
new_payload_request_fulu.parent_beacon_block_root,
new_payload_request_fulu
.execution_requests
.get_execution_requests_list(),
]);
let response: JsonPayloadStatusV1 = self
.rpc_request(
ENGINE_NEW_PAYLOAD_V5,
params,
ENGINE_NEW_PAYLOAD_TIMEOUT * self.execution_timeout_multiplier,
)
.await?;
Ok(response.into())
}
pub async fn get_payload_v1<E: EthSpec>(
&self,
payload_id: PayloadId,
@@ -880,9 +908,10 @@ impl HttpJsonRpc {
.try_into()
.map_err(Error::BadResponse)
}
ForkName::Base | ForkName::Altair | ForkName::Deneb | ForkName::Electra => Err(
Error::UnsupportedForkVariant(format!("called get_payload_v2 with {}", fork_name)),
),
_ => Err(Error::UnsupportedForkVariant(format!(
"called get_payload_v2 with {}",
fork_name
))),
}
}
@@ -906,11 +935,7 @@ impl HttpJsonRpc {
.try_into()
.map_err(Error::BadResponse)
}
ForkName::Base
| ForkName::Altair
| ForkName::Bellatrix
| ForkName::Capella
| ForkName::Electra => Err(Error::UnsupportedForkVariant(format!(
_ => Err(Error::UnsupportedForkVariant(format!(
"called get_payload_v3 with {}",
fork_name
))),
@@ -937,17 +962,40 @@ impl HttpJsonRpc {
.try_into()
.map_err(Error::BadResponse)
}
ForkName::Base
| ForkName::Altair
| ForkName::Bellatrix
| ForkName::Capella
| ForkName::Deneb => Err(Error::UnsupportedForkVariant(format!(
_ => Err(Error::UnsupportedForkVariant(format!(
"called get_payload_v4 with {}",
fork_name
))),
}
}
pub async fn get_payload_v5<E: EthSpec>(
&self,
fork_name: ForkName,
payload_id: PayloadId,
) -> Result<GetPayloadResponse<E>, Error> {
let params = json!([JsonPayloadIdRequest::from(payload_id)]);
match fork_name {
ForkName::Fulu => {
let response: JsonGetPayloadResponseV5<E> = self
.rpc_request(
ENGINE_GET_PAYLOAD_V5,
params,
ENGINE_GET_PAYLOAD_TIMEOUT * self.execution_timeout_multiplier,
)
.await?;
JsonGetPayloadResponse::V5(response)
.try_into()
.map_err(Error::BadResponse)
}
_ => Err(Error::UnsupportedForkVariant(format!(
"called get_payload_v5 with {}",
fork_name
))),
}
}
pub async fn forkchoice_updated_v1(
&self,
forkchoice_state: ForkchoiceState,
@@ -1071,6 +1119,7 @@ impl HttpJsonRpc {
new_payload_v2: capabilities.contains(ENGINE_NEW_PAYLOAD_V2),
new_payload_v3: capabilities.contains(ENGINE_NEW_PAYLOAD_V3),
new_payload_v4: capabilities.contains(ENGINE_NEW_PAYLOAD_V4),
new_payload_v5: capabilities.contains(ENGINE_NEW_PAYLOAD_V5),
forkchoice_updated_v1: capabilities.contains(ENGINE_FORKCHOICE_UPDATED_V1),
forkchoice_updated_v2: capabilities.contains(ENGINE_FORKCHOICE_UPDATED_V2),
forkchoice_updated_v3: capabilities.contains(ENGINE_FORKCHOICE_UPDATED_V3),
@@ -1082,6 +1131,7 @@ impl HttpJsonRpc {
get_payload_v2: capabilities.contains(ENGINE_GET_PAYLOAD_V2),
get_payload_v3: capabilities.contains(ENGINE_GET_PAYLOAD_V3),
get_payload_v4: capabilities.contains(ENGINE_GET_PAYLOAD_V4),
get_payload_v5: capabilities.contains(ENGINE_GET_PAYLOAD_V5),
get_client_version_v1: capabilities.contains(ENGINE_GET_CLIENT_VERSION_V1),
get_blobs_v1: capabilities.contains(ENGINE_GET_BLOBS_V1),
})
@@ -1212,6 +1262,13 @@ impl HttpJsonRpc {
Err(Error::RequiredMethodUnsupported("engine_newPayloadV4"))
}
}
NewPayloadRequest::Fulu(new_payload_request_fulu) => {
if engine_capabilities.new_payload_v5 {
self.new_payload_v5_fulu(new_payload_request_fulu).await
} else {
Err(Error::RequiredMethodUnsupported("engine_newPayloadV5"))
}
}
}
}
@@ -1247,6 +1304,13 @@ impl HttpJsonRpc {
Err(Error::RequiredMethodUnsupported("engine_getPayloadv4"))
}
}
ForkName::Fulu => {
if engine_capabilities.get_payload_v5 {
self.get_payload_v5(fork_name, payload_id).await
} else {
Err(Error::RequiredMethodUnsupported("engine_getPayloadv5"))
}
}
ForkName::Base | ForkName::Altair => Err(Error::UnsupportedForkVariant(format!(
"called get_payload with {}",
fork_name

View File

@@ -65,7 +65,7 @@ pub struct JsonPayloadIdResponse {
}
#[superstruct(
variants(V1, V2, V3, V4),
variants(V1, V2, V3, V4, V5),
variant_attributes(
derive(Debug, PartialEq, Default, Serialize, Deserialize,),
serde(bound = "E: EthSpec", rename_all = "camelCase"),
@@ -100,12 +100,12 @@ pub struct JsonExecutionPayload<E: EthSpec> {
pub block_hash: ExecutionBlockHash,
#[serde(with = "ssz_types::serde_utils::list_of_hex_var_list")]
pub transactions: Transactions<E>,
#[superstruct(only(V2, V3, V4))]
#[superstruct(only(V2, V3, V4, V5))]
pub withdrawals: VariableList<JsonWithdrawal, E::MaxWithdrawalsPerPayload>,
#[superstruct(only(V3, V4))]
#[superstruct(only(V3, V4, V5))]
#[serde(with = "serde_utils::u64_hex_be")]
pub blob_gas_used: u64,
#[superstruct(only(V3, V4))]
#[superstruct(only(V3, V4, V5))]
#[serde(with = "serde_utils::u64_hex_be")]
pub excess_blob_gas: u64,
}
@@ -214,6 +214,35 @@ impl<E: EthSpec> From<ExecutionPayloadElectra<E>> for JsonExecutionPayloadV4<E>
}
}
impl<E: EthSpec> From<ExecutionPayloadFulu<E>> for JsonExecutionPayloadV5<E> {
fn from(payload: ExecutionPayloadFulu<E>) -> Self {
JsonExecutionPayloadV5 {
parent_hash: payload.parent_hash,
fee_recipient: payload.fee_recipient,
state_root: payload.state_root,
receipts_root: payload.receipts_root,
logs_bloom: payload.logs_bloom,
prev_randao: payload.prev_randao,
block_number: payload.block_number,
gas_limit: payload.gas_limit,
gas_used: payload.gas_used,
timestamp: payload.timestamp,
extra_data: payload.extra_data,
base_fee_per_gas: payload.base_fee_per_gas,
block_hash: payload.block_hash,
transactions: payload.transactions,
withdrawals: payload
.withdrawals
.into_iter()
.map(Into::into)
.collect::<Vec<_>>()
.into(),
blob_gas_used: payload.blob_gas_used,
excess_blob_gas: payload.excess_blob_gas,
}
}
}
impl<E: EthSpec> From<ExecutionPayload<E>> for JsonExecutionPayload<E> {
fn from(execution_payload: ExecutionPayload<E>) -> Self {
match execution_payload {
@@ -221,6 +250,7 @@ impl<E: EthSpec> From<ExecutionPayload<E>> for JsonExecutionPayload<E> {
ExecutionPayload::Capella(payload) => JsonExecutionPayload::V2(payload.into()),
ExecutionPayload::Deneb(payload) => JsonExecutionPayload::V3(payload.into()),
ExecutionPayload::Electra(payload) => JsonExecutionPayload::V4(payload.into()),
ExecutionPayload::Fulu(payload) => JsonExecutionPayload::V5(payload.into()),
}
}
}
@@ -330,6 +360,35 @@ impl<E: EthSpec> From<JsonExecutionPayloadV4<E>> for ExecutionPayloadElectra<E>
}
}
impl<E: EthSpec> From<JsonExecutionPayloadV5<E>> for ExecutionPayloadFulu<E> {
fn from(payload: JsonExecutionPayloadV5<E>) -> Self {
ExecutionPayloadFulu {
parent_hash: payload.parent_hash,
fee_recipient: payload.fee_recipient,
state_root: payload.state_root,
receipts_root: payload.receipts_root,
logs_bloom: payload.logs_bloom,
prev_randao: payload.prev_randao,
block_number: payload.block_number,
gas_limit: payload.gas_limit,
gas_used: payload.gas_used,
timestamp: payload.timestamp,
extra_data: payload.extra_data,
base_fee_per_gas: payload.base_fee_per_gas,
block_hash: payload.block_hash,
transactions: payload.transactions,
withdrawals: payload
.withdrawals
.into_iter()
.map(Into::into)
.collect::<Vec<_>>()
.into(),
blob_gas_used: payload.blob_gas_used,
excess_blob_gas: payload.excess_blob_gas,
}
}
}
impl<E: EthSpec> From<JsonExecutionPayload<E>> for ExecutionPayload<E> {
fn from(json_execution_payload: JsonExecutionPayload<E>) -> Self {
match json_execution_payload {
@@ -337,6 +396,7 @@ impl<E: EthSpec> From<JsonExecutionPayload<E>> for ExecutionPayload<E> {
JsonExecutionPayload::V2(payload) => ExecutionPayload::Capella(payload.into()),
JsonExecutionPayload::V3(payload) => ExecutionPayload::Deneb(payload.into()),
JsonExecutionPayload::V4(payload) => ExecutionPayload::Electra(payload.into()),
JsonExecutionPayload::V5(payload) => ExecutionPayload::Fulu(payload.into()),
}
}
}
@@ -389,7 +449,7 @@ impl<E: EthSpec> TryFrom<JsonExecutionRequests> for ExecutionRequests<E> {
}
#[superstruct(
variants(V1, V2, V3, V4),
variants(V1, V2, V3, V4, V5),
variant_attributes(
derive(Debug, PartialEq, Serialize, Deserialize),
serde(bound = "E: EthSpec", rename_all = "camelCase")
@@ -408,13 +468,15 @@ pub struct JsonGetPayloadResponse<E: EthSpec> {
pub execution_payload: JsonExecutionPayloadV3<E>,
#[superstruct(only(V4), partial_getter(rename = "execution_payload_v4"))]
pub execution_payload: JsonExecutionPayloadV4<E>,
#[superstruct(only(V5), partial_getter(rename = "execution_payload_v5"))]
pub execution_payload: JsonExecutionPayloadV5<E>,
#[serde(with = "serde_utils::u256_hex_be")]
pub block_value: Uint256,
#[superstruct(only(V3, V4))]
#[superstruct(only(V3, V4, V5))]
pub blobs_bundle: JsonBlobsBundleV1<E>,
#[superstruct(only(V3, V4))]
#[superstruct(only(V3, V4, V5))]
pub should_override_builder: bool,
#[superstruct(only(V4))]
#[superstruct(only(V4, V5))]
pub execution_requests: JsonExecutionRequests,
}
@@ -451,6 +513,15 @@ impl<E: EthSpec> TryFrom<JsonGetPayloadResponse<E>> for GetPayloadResponse<E> {
requests: response.execution_requests.try_into()?,
}))
}
JsonGetPayloadResponse::V5(response) => {
Ok(GetPayloadResponse::Fulu(GetPayloadResponseFulu {
execution_payload: response.execution_payload.into(),
block_value: response.block_value,
blobs_bundle: response.blobs_bundle.into(),
should_override_builder: response.should_override_builder,
requests: response.execution_requests.try_into()?,
}))
}
}
}
}

View File

@@ -9,11 +9,11 @@ use types::{
};
use types::{
ExecutionPayloadBellatrix, ExecutionPayloadCapella, ExecutionPayloadDeneb,
ExecutionPayloadElectra, ExecutionRequests,
ExecutionPayloadElectra, ExecutionPayloadFulu, ExecutionRequests,
};
#[superstruct(
variants(Bellatrix, Capella, Deneb, Electra),
variants(Bellatrix, Capella, Deneb, Electra, Fulu),
variant_attributes(derive(Clone, Debug, PartialEq),),
map_into(ExecutionPayload),
map_ref_into(ExecutionPayloadRef),
@@ -39,11 +39,13 @@ pub struct NewPayloadRequest<'block, E: EthSpec> {
pub execution_payload: &'block ExecutionPayloadDeneb<E>,
#[superstruct(only(Electra), partial_getter(rename = "execution_payload_electra"))]
pub execution_payload: &'block ExecutionPayloadElectra<E>,
#[superstruct(only(Deneb, Electra))]
#[superstruct(only(Fulu), partial_getter(rename = "execution_payload_fulu"))]
pub execution_payload: &'block ExecutionPayloadFulu<E>,
#[superstruct(only(Deneb, Electra, Fulu))]
pub versioned_hashes: Vec<VersionedHash>,
#[superstruct(only(Deneb, Electra))]
#[superstruct(only(Deneb, Electra, Fulu))]
pub parent_beacon_block_root: Hash256,
#[superstruct(only(Electra))]
#[superstruct(only(Electra, Fulu))]
pub execution_requests: &'block ExecutionRequests<E>,
}
@@ -54,6 +56,7 @@ impl<'block, E: EthSpec> NewPayloadRequest<'block, E> {
Self::Capella(payload) => payload.execution_payload.parent_hash,
Self::Deneb(payload) => payload.execution_payload.parent_hash,
Self::Electra(payload) => payload.execution_payload.parent_hash,
Self::Fulu(payload) => payload.execution_payload.parent_hash,
}
}
@@ -63,6 +66,7 @@ impl<'block, E: EthSpec> NewPayloadRequest<'block, E> {
Self::Capella(payload) => payload.execution_payload.block_hash,
Self::Deneb(payload) => payload.execution_payload.block_hash,
Self::Electra(payload) => payload.execution_payload.block_hash,
Self::Fulu(payload) => payload.execution_payload.block_hash,
}
}
@@ -72,6 +76,7 @@ impl<'block, E: EthSpec> NewPayloadRequest<'block, E> {
Self::Capella(payload) => payload.execution_payload.block_number,
Self::Deneb(payload) => payload.execution_payload.block_number,
Self::Electra(payload) => payload.execution_payload.block_number,
Self::Fulu(payload) => payload.execution_payload.block_number,
}
}
@@ -81,6 +86,7 @@ impl<'block, E: EthSpec> NewPayloadRequest<'block, E> {
Self::Capella(request) => ExecutionPayloadRef::Capella(request.execution_payload),
Self::Deneb(request) => ExecutionPayloadRef::Deneb(request.execution_payload),
Self::Electra(request) => ExecutionPayloadRef::Electra(request.execution_payload),
Self::Fulu(request) => ExecutionPayloadRef::Fulu(request.execution_payload),
}
}
@@ -92,6 +98,7 @@ impl<'block, E: EthSpec> NewPayloadRequest<'block, E> {
Self::Capella(request) => ExecutionPayload::Capella(request.execution_payload.clone()),
Self::Deneb(request) => ExecutionPayload::Deneb(request.execution_payload.clone()),
Self::Electra(request) => ExecutionPayload::Electra(request.execution_payload.clone()),
Self::Fulu(request) => ExecutionPayload::Fulu(request.execution_payload.clone()),
}
}
@@ -190,6 +197,17 @@ impl<'a, E: EthSpec> TryFrom<BeaconBlockRef<'a, E>> for NewPayloadRequest<'a, E>
parent_beacon_block_root: block_ref.parent_root,
execution_requests: &block_ref.body.execution_requests,
})),
BeaconBlockRef::Fulu(block_ref) => Ok(Self::Fulu(NewPayloadRequestFulu {
execution_payload: &block_ref.body.execution_payload.execution_payload,
versioned_hashes: block_ref
.body
.blob_kzg_commitments
.iter()
.map(kzg_commitment_to_versioned_hash)
.collect(),
parent_beacon_block_root: block_ref.parent_root,
execution_requests: &block_ref.body.execution_requests,
})),
}
}
}
@@ -209,6 +227,7 @@ impl<'a, E: EthSpec> TryFrom<ExecutionPayloadRef<'a, E>> for NewPayloadRequest<'
})),
ExecutionPayloadRef::Deneb(_) => Err(Self::Error::IncorrectStateVariant),
ExecutionPayloadRef::Electra(_) => Err(Self::Error::IncorrectStateVariant),
ExecutionPayloadRef::Fulu(_) => Err(Self::Error::IncorrectStateVariant),
}
}
}

View File

@@ -54,8 +54,8 @@ use types::{
};
use types::{
BeaconStateError, BlindedPayload, ChainSpec, Epoch, ExecPayload, ExecutionPayloadBellatrix,
ExecutionPayloadCapella, ExecutionPayloadElectra, FullPayload, ProposerPreparationData,
PublicKeyBytes, Signature, Slot,
ExecutionPayloadCapella, ExecutionPayloadElectra, ExecutionPayloadFulu, FullPayload,
ProposerPreparationData, PublicKeyBytes, Signature, Slot,
};
mod block_hash;
@@ -124,6 +124,14 @@ impl<E: EthSpec> TryFrom<BuilderBid<E>> for ProvenancedPayload<BlockProposalCont
// TODO(electra): update this with builder api returning the requests
requests: None,
},
BuilderBid::Fulu(builder_bid) => BlockProposalContents::PayloadAndBlobs {
payload: ExecutionPayloadHeader::Fulu(builder_bid.header).into(),
block_value: builder_bid.value,
kzg_commitments: builder_bid.blob_kzg_commitments,
blobs_and_proofs: None,
// TODO(fulu): update this with builder api returning the requests
requests: None,
},
};
Ok(ProvenancedPayload::Builder(
BlockProposalContentsType::Blinded(block_proposal_contents),
@@ -1821,6 +1829,7 @@ impl<E: EthSpec> ExecutionLayer<E> {
ForkName::Capella => ExecutionPayloadCapella::default().into(),
ForkName::Deneb => ExecutionPayloadDeneb::default().into(),
ForkName::Electra => ExecutionPayloadElectra::default().into(),
ForkName::Fulu => ExecutionPayloadFulu::default().into(),
ForkName::Base | ForkName::Altair => {
return Err(Error::InvalidForkForPayload);
}

View File

@@ -19,7 +19,7 @@ use tree_hash::TreeHash;
use tree_hash_derive::TreeHash;
use types::{
Blob, ChainSpec, EthSpec, ExecutionBlockHash, ExecutionPayload, ExecutionPayloadBellatrix,
ExecutionPayloadCapella, ExecutionPayloadDeneb, ExecutionPayloadElectra,
ExecutionPayloadCapella, ExecutionPayloadDeneb, ExecutionPayloadElectra, ExecutionPayloadFulu,
ExecutionPayloadHeader, FixedBytesExtended, ForkName, Hash256, Transaction, Transactions,
Uint256,
};
@@ -147,6 +147,7 @@ pub struct ExecutionBlockGenerator<E: EthSpec> {
pub shanghai_time: Option<u64>, // capella
pub cancun_time: Option<u64>, // deneb
pub prague_time: Option<u64>, // electra
pub osaka_time: Option<u64>, // fulu
/*
* deneb stuff
*/
@@ -162,6 +163,7 @@ fn make_rng() -> Arc<Mutex<StdRng>> {
}
impl<E: EthSpec> ExecutionBlockGenerator<E> {
#[allow(clippy::too_many_arguments)]
pub fn new(
terminal_total_difficulty: Uint256,
terminal_block_number: u64,
@@ -169,6 +171,7 @@ impl<E: EthSpec> ExecutionBlockGenerator<E> {
shanghai_time: Option<u64>,
cancun_time: Option<u64>,
prague_time: Option<u64>,
osaka_time: Option<u64>,
kzg: Option<Arc<Kzg>>,
) -> Self {
let mut gen = Self {
@@ -185,6 +188,7 @@ impl<E: EthSpec> ExecutionBlockGenerator<E> {
shanghai_time,
cancun_time,
prague_time,
osaka_time,
blobs_bundles: <_>::default(),
kzg,
rng: make_rng(),
@@ -233,13 +237,16 @@ impl<E: EthSpec> ExecutionBlockGenerator<E> {
}
pub fn get_fork_at_timestamp(&self, timestamp: u64) -> ForkName {
match self.prague_time {
Some(fork_time) if timestamp >= fork_time => ForkName::Electra,
_ => match self.cancun_time {
Some(fork_time) if timestamp >= fork_time => ForkName::Deneb,
_ => match self.shanghai_time {
Some(fork_time) if timestamp >= fork_time => ForkName::Capella,
_ => ForkName::Bellatrix,
match self.osaka_time {
Some(fork_time) if timestamp >= fork_time => ForkName::Fulu,
_ => match self.prague_time {
Some(fork_time) if timestamp >= fork_time => ForkName::Electra,
_ => match self.cancun_time {
Some(fork_time) if timestamp >= fork_time => ForkName::Deneb,
_ => match self.shanghai_time {
Some(fork_time) if timestamp >= fork_time => ForkName::Capella,
_ => ForkName::Bellatrix,
},
},
},
}
@@ -664,6 +671,25 @@ impl<E: EthSpec> ExecutionBlockGenerator<E> {
blob_gas_used: 0,
excess_blob_gas: 0,
}),
ForkName::Fulu => ExecutionPayload::Fulu(ExecutionPayloadFulu {
parent_hash: head_block_hash,
fee_recipient: pa.suggested_fee_recipient,
receipts_root: Hash256::repeat_byte(42),
state_root: Hash256::repeat_byte(43),
logs_bloom: vec![0; 256].into(),
prev_randao: pa.prev_randao,
block_number: parent.block_number() + 1,
gas_limit: DEFAULT_GAS_LIMIT,
gas_used: GAS_USED,
timestamp: pa.timestamp,
extra_data: "block gen was here".as_bytes().to_vec().into(),
base_fee_per_gas: Uint256::from(1u64),
block_hash: ExecutionBlockHash::zero(),
transactions: vec![].into(),
withdrawals: pa.withdrawals.clone().into(),
blob_gas_used: 0,
excess_blob_gas: 0,
}),
_ => unreachable!(),
},
};
@@ -811,6 +837,12 @@ pub fn generate_genesis_header<E: EthSpec>(
*header.transactions_root_mut() = empty_transactions_root;
Some(header)
}
ForkName::Fulu => {
let mut header = ExecutionPayloadHeader::Fulu(<_>::default());
*header.block_hash_mut() = genesis_block_hash.unwrap_or_default();
*header.transactions_root_mut() = empty_transactions_root;
Some(header)
}
}
}
@@ -883,6 +915,7 @@ mod test {
None,
None,
None,
None,
);
for i in 0..=TERMINAL_BLOCK {

View File

@@ -99,7 +99,8 @@ pub async fn handle_rpc<E: EthSpec>(
ENGINE_NEW_PAYLOAD_V1
| ENGINE_NEW_PAYLOAD_V2
| ENGINE_NEW_PAYLOAD_V3
| ENGINE_NEW_PAYLOAD_V4 => {
| ENGINE_NEW_PAYLOAD_V4
| ENGINE_NEW_PAYLOAD_V5 => {
let request = match method {
ENGINE_NEW_PAYLOAD_V1 => JsonExecutionPayload::V1(
get_param::<JsonExecutionPayloadV1<E>>(params, 0)
@@ -121,6 +122,9 @@ pub async fn handle_rpc<E: EthSpec>(
ENGINE_NEW_PAYLOAD_V4 => get_param::<JsonExecutionPayloadV4<E>>(params, 0)
.map(|jep| JsonExecutionPayload::V4(jep))
.map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?,
ENGINE_NEW_PAYLOAD_V5 => get_param::<JsonExecutionPayloadV5<E>>(params, 0)
.map(|jep| JsonExecutionPayload::V5(jep))
.map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?,
_ => unreachable!(),
};
@@ -222,6 +226,54 @@ pub async fn handle_rpc<E: EthSpec>(
));
}
}
ForkName::Fulu => {
if method == ENGINE_NEW_PAYLOAD_V1
|| method == ENGINE_NEW_PAYLOAD_V2
|| method == ENGINE_NEW_PAYLOAD_V3
|| method == ENGINE_NEW_PAYLOAD_V4
{
return Err((
format!("{} called after Fulu fork!", method),
GENERIC_ERROR_CODE,
));
}
if matches!(request, JsonExecutionPayload::V1(_)) {
return Err((
format!(
"{} called with `ExecutionPayloadV1` after Fulu fork!",
method
),
GENERIC_ERROR_CODE,
));
}
if matches!(request, JsonExecutionPayload::V2(_)) {
return Err((
format!(
"{} called with `ExecutionPayloadV2` after Fulu fork!",
method
),
GENERIC_ERROR_CODE,
));
}
if matches!(request, JsonExecutionPayload::V3(_)) {
return Err((
format!(
"{} called with `ExecutionPayloadV3` after Fulu fork!",
method
),
GENERIC_ERROR_CODE,
));
}
if matches!(request, JsonExecutionPayload::V4(_)) {
return Err((
format!(
"{} called with `ExecutionPayloadV4` after Fulu fork!",
method
),
GENERIC_ERROR_CODE,
));
}
}
_ => unreachable!(),
};
@@ -260,7 +312,8 @@ pub async fn handle_rpc<E: EthSpec>(
ENGINE_GET_PAYLOAD_V1
| ENGINE_GET_PAYLOAD_V2
| ENGINE_GET_PAYLOAD_V3
| ENGINE_GET_PAYLOAD_V4 => {
| ENGINE_GET_PAYLOAD_V4
| ENGINE_GET_PAYLOAD_V5 => {
let request: JsonPayloadIdRequest =
get_param(params, 0).map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?;
let id = request.into();
@@ -320,6 +373,23 @@ pub async fn handle_rpc<E: EthSpec>(
));
}
// validate method called correctly according to fulu fork time
if ctx
.execution_block_generator
.read()
.get_fork_at_timestamp(response.timestamp())
== ForkName::Fulu
&& (method == ENGINE_GET_PAYLOAD_V1
|| method == ENGINE_GET_PAYLOAD_V2
|| method == ENGINE_GET_PAYLOAD_V3
|| method == ENGINE_GET_PAYLOAD_V4)
{
return Err((
format!("{} called after Fulu fork!", method),
FORK_REQUEST_MISMATCH_ERROR_CODE,
));
}
match method {
ENGINE_GET_PAYLOAD_V1 => {
Ok(serde_json::to_value(JsonExecutionPayload::from(response)).unwrap())
@@ -380,6 +450,24 @@ pub async fn handle_rpc<E: EthSpec>(
}
_ => unreachable!(),
}),
ENGINE_GET_PAYLOAD_V5 => Ok(match JsonExecutionPayload::from(response) {
JsonExecutionPayload::V5(execution_payload) => {
serde_json::to_value(JsonGetPayloadResponseV5 {
execution_payload,
block_value: Uint256::from(DEFAULT_MOCK_EL_PAYLOAD_VALUE_WEI),
blobs_bundle: maybe_blobs
.ok_or((
"No blobs returned despite V5 Payload".to_string(),
GENERIC_ERROR_CODE,
))?
.into(),
should_override_builder: false,
execution_requests: Default::default(),
})
.unwrap()
}
_ => unreachable!(),
}),
_ => unreachable!(),
}
}
@@ -411,7 +499,10 @@ pub async fn handle_rpc<E: EthSpec>(
.map(|opt| opt.map(JsonPayloadAttributes::V1))
.transpose()
}
ForkName::Capella | ForkName::Deneb | ForkName::Electra => {
ForkName::Capella
| ForkName::Deneb
| ForkName::Electra
| ForkName::Fulu => {
get_param::<Option<JsonPayloadAttributesV2>>(params, 1)
.map(|opt| opt.map(JsonPayloadAttributes::V2))
.transpose()
@@ -475,7 +566,7 @@ pub async fn handle_rpc<E: EthSpec>(
));
}
}
ForkName::Deneb | ForkName::Electra => {
ForkName::Deneb | ForkName::Electra | ForkName::Fulu => {
if method == ENGINE_FORKCHOICE_UPDATED_V1 {
return Err((
format!("{} called after Deneb fork!", method),

View File

@@ -16,7 +16,7 @@ use tempfile::NamedTempFile;
use tree_hash::TreeHash;
use types::builder_bid::{
BuilderBid, BuilderBidBellatrix, BuilderBidCapella, BuilderBidDeneb, BuilderBidElectra,
SignedBuilderBid,
BuilderBidFulu, SignedBuilderBid,
};
use types::{
Address, BeaconState, ChainSpec, EthSpec, ExecPayload, ExecutionPayload,
@@ -95,6 +95,9 @@ impl<E: EthSpec> BidStuff<E> for BuilderBid<E> {
ExecutionPayloadHeaderRefMut::Electra(header) => {
header.fee_recipient = fee_recipient;
}
ExecutionPayloadHeaderRefMut::Fulu(header) => {
header.fee_recipient = fee_recipient;
}
}
}
@@ -112,6 +115,9 @@ impl<E: EthSpec> BidStuff<E> for BuilderBid<E> {
ExecutionPayloadHeaderRefMut::Electra(header) => {
header.gas_limit = gas_limit;
}
ExecutionPayloadHeaderRefMut::Fulu(header) => {
header.gas_limit = gas_limit;
}
}
}
@@ -133,6 +139,9 @@ impl<E: EthSpec> BidStuff<E> for BuilderBid<E> {
ExecutionPayloadHeaderRefMut::Electra(header) => {
header.parent_hash = ExecutionBlockHash::from_root(parent_hash);
}
ExecutionPayloadHeaderRefMut::Fulu(header) => {
header.parent_hash = ExecutionBlockHash::from_root(parent_hash);
}
}
}
@@ -150,6 +159,9 @@ impl<E: EthSpec> BidStuff<E> for BuilderBid<E> {
ExecutionPayloadHeaderRefMut::Electra(header) => {
header.prev_randao = prev_randao;
}
ExecutionPayloadHeaderRefMut::Fulu(header) => {
header.prev_randao = prev_randao;
}
}
}
@@ -167,6 +179,9 @@ impl<E: EthSpec> BidStuff<E> for BuilderBid<E> {
ExecutionPayloadHeaderRefMut::Electra(header) => {
header.block_number = block_number;
}
ExecutionPayloadHeaderRefMut::Fulu(header) => {
header.block_number = block_number;
}
}
}
@@ -184,6 +199,9 @@ impl<E: EthSpec> BidStuff<E> for BuilderBid<E> {
ExecutionPayloadHeaderRefMut::Electra(header) => {
header.timestamp = timestamp;
}
ExecutionPayloadHeaderRefMut::Fulu(header) => {
header.timestamp = timestamp;
}
}
}
@@ -201,6 +219,9 @@ impl<E: EthSpec> BidStuff<E> for BuilderBid<E> {
ExecutionPayloadHeaderRefMut::Electra(header) => {
header.withdrawals_root = withdrawals_root;
}
ExecutionPayloadHeaderRefMut::Fulu(header) => {
header.withdrawals_root = withdrawals_root;
}
}
}
@@ -230,6 +251,10 @@ impl<E: EthSpec> BidStuff<E> for BuilderBid<E> {
header.extra_data = extra_data;
header.block_hash = ExecutionBlockHash::from_root(header.tree_hash_root());
}
ExecutionPayloadHeaderRefMut::Fulu(header) => {
header.extra_data = extra_data;
header.block_hash = ExecutionBlockHash::from_root(header.tree_hash_root());
}
}
}
}
@@ -378,6 +403,9 @@ pub fn serve<E: EthSpec>(
SignedBlindedBeaconBlock::Electra(block) => {
block.message.body.execution_payload.tree_hash_root()
}
SignedBlindedBeaconBlock::Fulu(block) => {
block.message.body.execution_payload.tree_hash_root()
}
};
let payload = builder
.el
@@ -536,7 +564,7 @@ pub fn serve<E: EthSpec>(
expected_withdrawals,
None,
),
ForkName::Deneb | ForkName::Electra => PayloadAttributes::new(
ForkName::Deneb | ForkName::Electra | ForkName::Fulu => PayloadAttributes::new(
timestamp,
*prev_randao,
fee_recipient,
@@ -592,6 +620,17 @@ pub fn serve<E: EthSpec>(
) = payload_response.into();
match fork {
ForkName::Fulu => BuilderBid::Fulu(BuilderBidFulu {
header: payload
.as_fulu()
.map_err(|_| reject("incorrect payload variant"))?
.into(),
blob_kzg_commitments: maybe_blobs_bundle
.map(|b| b.commitments)
.unwrap_or_default(),
value: Uint256::from(DEFAULT_BUILDER_PAYLOAD_VALUE_WEI),
pubkey: builder.builder_sk.public_key().compress(),
}),
ForkName::Electra => BuilderBid::Electra(BuilderBidElectra {
header: payload
.as_electra()
@@ -644,6 +683,17 @@ pub fn serve<E: EthSpec>(
Option<ExecutionRequests<E>>,
) = payload_response.into();
match fork {
ForkName::Fulu => BuilderBid::Fulu(BuilderBidFulu {
header: payload
.as_fulu()
.map_err(|_| reject("incorrect payload variant"))?
.into(),
blob_kzg_commitments: maybe_blobs_bundle
.map(|b| b.commitments)
.unwrap_or_default(),
value: Uint256::from(DEFAULT_BUILDER_PAYLOAD_VALUE_WEI),
pubkey: builder.builder_sk.public_key().compress(),
}),
ForkName::Electra => BuilderBid::Electra(BuilderBidElectra {
header: payload
.as_electra()

View File

@@ -28,6 +28,7 @@ impl<E: EthSpec> MockExecutionLayer<E> {
None,
None,
None,
None,
Some(JwtKey::from_slice(&DEFAULT_JWT_SECRET).unwrap()),
spec,
None,
@@ -41,6 +42,7 @@ impl<E: EthSpec> MockExecutionLayer<E> {
shanghai_time: Option<u64>,
cancun_time: Option<u64>,
prague_time: Option<u64>,
osaka_time: Option<u64>,
jwt_key: Option<JwtKey>,
spec: ChainSpec,
kzg: Option<Arc<Kzg>>,
@@ -57,6 +59,7 @@ impl<E: EthSpec> MockExecutionLayer<E> {
shanghai_time,
cancun_time,
prague_time,
osaka_time,
kzg,
);

View File

@@ -44,6 +44,7 @@ pub const DEFAULT_ENGINE_CAPABILITIES: EngineCapabilities = EngineCapabilities {
new_payload_v2: true,
new_payload_v3: true,
new_payload_v4: true,
new_payload_v5: true,
forkchoice_updated_v1: true,
forkchoice_updated_v2: true,
forkchoice_updated_v3: true,
@@ -53,6 +54,7 @@ pub const DEFAULT_ENGINE_CAPABILITIES: EngineCapabilities = EngineCapabilities {
get_payload_v2: true,
get_payload_v3: true,
get_payload_v4: true,
get_payload_v5: true,
get_client_version_v1: true,
get_blobs_v1: true,
};
@@ -82,6 +84,7 @@ pub struct MockExecutionConfig {
pub shanghai_time: Option<u64>,
pub cancun_time: Option<u64>,
pub prague_time: Option<u64>,
pub osaka_time: Option<u64>,
}
impl Default for MockExecutionConfig {
@@ -95,6 +98,7 @@ impl Default for MockExecutionConfig {
shanghai_time: None,
cancun_time: None,
prague_time: None,
osaka_time: None,
}
}
}
@@ -117,6 +121,7 @@ impl<E: EthSpec> MockServer<E> {
None, // FIXME(capella): should this be the default?
None, // FIXME(deneb): should this be the default?
None, // FIXME(electra): should this be the default?
None, // FIXME(fulu): should this be the default?
None,
)
}
@@ -135,6 +140,7 @@ impl<E: EthSpec> MockServer<E> {
shanghai_time,
cancun_time,
prague_time,
osaka_time,
} = config;
let last_echo_request = Arc::new(RwLock::new(None));
let preloaded_responses = Arc::new(Mutex::new(vec![]));
@@ -145,6 +151,7 @@ impl<E: EthSpec> MockServer<E> {
shanghai_time,
cancun_time,
prague_time,
osaka_time,
kzg,
);
@@ -208,6 +215,7 @@ impl<E: EthSpec> MockServer<E> {
shanghai_time: Option<u64>,
cancun_time: Option<u64>,
prague_time: Option<u64>,
osaka_time: Option<u64>,
kzg: Option<Arc<Kzg>>,
) -> Self {
Self::new_with_config(
@@ -221,6 +229,7 @@ impl<E: EthSpec> MockServer<E> {
shanghai_time,
cancun_time,
prague_time,
osaka_time,
},
kzg,
)