Use Fork variants instead of version for JsonPayload types (#7909)

With Fulu, we increment the engine API version for `get_payload` but we do not also increment `new_payload`.
In Lighthouse, we have a tight coupling between these version numbers and the Fork variants.
For example, both `get_payload_v3` and `new_payload_v3` correspond to Deneb, `v4` to Electra, etc.

However this coupling breaks with Fulu where only `get_payload_v5` is related to Fulu and `new_payload_v4` now also corresponds to Fulu (new_payload_v5 does not exist). While we can work around this, it creates a confusing situation where the versions and Fork variants are out of sync.

See the following code snippet where we are using the v4 endpoint, and yet passing a `V5` payload variant: 522bd9e9c6/beacon_node/execution_layer/src/engine_api/http.rs (L849-L870)


  1. Remove `new_payload_v5` as it is unused in Fulu.
2. Rename the `JsonExecutionPayload` and `JsonGetExecutionPayloadResponse` types to use Fork variants instead of version variants. This clarifies the relationship between them.
This commit is contained in:
Mac L
2025-08-22 19:22:41 +10:00
committed by GitHub
parent 884f30094a
commit c41d1181d2
5 changed files with 116 additions and 159 deletions

View File

@@ -99,31 +99,28 @@ 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_V5 => {
| ENGINE_NEW_PAYLOAD_V4 => {
let request = match method {
ENGINE_NEW_PAYLOAD_V1 => JsonExecutionPayload::V1(
get_param::<JsonExecutionPayloadV1<E>>(params, 0)
ENGINE_NEW_PAYLOAD_V1 => JsonExecutionPayload::Bellatrix(
get_param::<JsonExecutionPayloadBellatrix<E>>(params, 0)
.map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?,
),
ENGINE_NEW_PAYLOAD_V2 => get_param::<JsonExecutionPayloadV2<E>>(params, 0)
.map(|jep| JsonExecutionPayload::V2(jep))
ENGINE_NEW_PAYLOAD_V2 => get_param::<JsonExecutionPayloadCapella<E>>(params, 0)
.map(|jep| JsonExecutionPayload::Capella(jep))
.or_else(|_| {
get_param::<JsonExecutionPayloadV1<E>>(params, 0)
.map(|jep| JsonExecutionPayload::V1(jep))
get_param::<JsonExecutionPayloadBellatrix<E>>(params, 0)
.map(|jep| JsonExecutionPayload::Bellatrix(jep))
})
.map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?,
// From v3 onwards, we use the newPayload version only for the corresponding
// ExecutionPayload version. So we return an error instead of falling back to
// older versions of newPayload
ENGINE_NEW_PAYLOAD_V3 => get_param::<JsonExecutionPayloadV3<E>>(params, 0)
.map(|jep| JsonExecutionPayload::V3(jep))
ENGINE_NEW_PAYLOAD_V3 => get_param::<JsonExecutionPayloadDeneb<E>>(params, 0)
.map(|jep| JsonExecutionPayload::Deneb(jep))
.map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?,
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))
ENGINE_NEW_PAYLOAD_V4 => get_param::<JsonExecutionPayloadFulu<E>>(params, 0)
.map(|jep| JsonExecutionPayload::Fulu(jep))
.or_else(|_| {
get_param::<JsonExecutionPayloadElectra<E>>(params, 0)
.map(|jep| JsonExecutionPayload::Electra(jep))
})
.map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?,
_ => unreachable!(),
};
@@ -135,10 +132,10 @@ pub async fn handle_rpc<E: EthSpec>(
// validate method called correctly according to fork time
match fork {
ForkName::Bellatrix => {
if matches!(request, JsonExecutionPayload::V2(_)) {
if matches!(request, JsonExecutionPayload::Capella(_)) {
return Err((
format!(
"{} called with `ExecutionPayloadV2` before Capella fork!",
"{} called with `ExecutionPayloadCapella` before Capella fork!",
method
),
GENERIC_ERROR_CODE,
@@ -152,10 +149,10 @@ pub async fn handle_rpc<E: EthSpec>(
GENERIC_ERROR_CODE,
));
}
if matches!(request, JsonExecutionPayload::V1(_)) {
if matches!(request, JsonExecutionPayload::Bellatrix(_)) {
return Err((
format!(
"{} called with `ExecutionPayloadV1` after Capella fork!",
"{} called with `ExecutionPayloadBellatrix` after Capella fork!",
method
),
GENERIC_ERROR_CODE,
@@ -169,7 +166,7 @@ pub async fn handle_rpc<E: EthSpec>(
GENERIC_ERROR_CODE,
));
}
if matches!(request, JsonExecutionPayload::V1(_)) {
if matches!(request, JsonExecutionPayload::Bellatrix(_)) {
return Err((
format!(
"{} called with `ExecutionPayloadV1` after Deneb fork!",
@@ -178,7 +175,7 @@ pub async fn handle_rpc<E: EthSpec>(
GENERIC_ERROR_CODE,
));
}
if matches!(request, JsonExecutionPayload::V2(_)) {
if matches!(request, JsonExecutionPayload::Capella(_)) {
return Err((
format!(
"{} called with `ExecutionPayloadV2` after Deneb fork!",
@@ -188,7 +185,7 @@ pub async fn handle_rpc<E: EthSpec>(
));
}
}
ForkName::Electra => {
ForkName::Electra | ForkName::Fulu => {
if method == ENGINE_NEW_PAYLOAD_V1
|| method == ENGINE_NEW_PAYLOAD_V2
|| method == ENGINE_NEW_PAYLOAD_V3
@@ -198,66 +195,28 @@ pub async fn handle_rpc<E: EthSpec>(
GENERIC_ERROR_CODE,
));
}
if matches!(request, JsonExecutionPayload::V1(_)) {
if matches!(request, JsonExecutionPayload::Bellatrix(_)) {
return Err((
format!(
"{} called with `ExecutionPayloadV1` after Electra fork!",
"{} called with `ExecutionPayloadBellatrix after Electra fork!",
method
),
GENERIC_ERROR_CODE,
));
}
if matches!(request, JsonExecutionPayload::V2(_)) {
if matches!(request, JsonExecutionPayload::Capella(_)) {
return Err((
format!(
"{} called with `ExecutionPayloadV2` after Electra fork!",
"{} called with `ExecutionPayloadCapella` after Electra fork!",
method
),
GENERIC_ERROR_CODE,
));
}
if matches!(request, JsonExecutionPayload::V3(_)) {
if matches!(request, JsonExecutionPayload::Deneb(_)) {
return Err((
format!(
"{} called with `ExecutionPayloadV3` after Electra fork!",
method
),
GENERIC_ERROR_CODE,
));
}
}
ForkName::Fulu => {
if method == ENGINE_NEW_PAYLOAD_V1
|| method == ENGINE_NEW_PAYLOAD_V2
|| method == ENGINE_NEW_PAYLOAD_V3
{
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!",
"{} called with `ExecutionPayloadDeneb` after Electra fork!",
method
),
GENERIC_ERROR_CODE,
@@ -385,15 +344,15 @@ pub async fn handle_rpc<E: EthSpec>(
Ok(serde_json::to_value(JsonExecutionPayload::from(response)).unwrap())
}
ENGINE_GET_PAYLOAD_V2 => Ok(match JsonExecutionPayload::from(response) {
JsonExecutionPayload::V1(execution_payload) => {
serde_json::to_value(JsonGetPayloadResponseV1 {
JsonExecutionPayload::Bellatrix(execution_payload) => {
serde_json::to_value(JsonGetPayloadResponseBellatrix {
execution_payload,
block_value: Uint256::from(DEFAULT_MOCK_EL_PAYLOAD_VALUE_WEI),
})
.unwrap()
}
JsonExecutionPayload::V2(execution_payload) => {
serde_json::to_value(JsonGetPayloadResponseV2 {
JsonExecutionPayload::Capella(execution_payload) => {
serde_json::to_value(JsonGetPayloadResponseCapella {
execution_payload,
block_value: Uint256::from(DEFAULT_MOCK_EL_PAYLOAD_VALUE_WEI),
})
@@ -405,8 +364,8 @@ pub async fn handle_rpc<E: EthSpec>(
// ExecutionPayload version. So we return an error if the ExecutionPayload version
// we get does not correspond to the getPayload version.
ENGINE_GET_PAYLOAD_V3 => Ok(match JsonExecutionPayload::from(response) {
JsonExecutionPayload::V3(execution_payload) => {
serde_json::to_value(JsonGetPayloadResponseV3 {
JsonExecutionPayload::Deneb(execution_payload) => {
serde_json::to_value(JsonGetPayloadResponseDeneb {
execution_payload,
block_value: Uint256::from(DEFAULT_MOCK_EL_PAYLOAD_VALUE_WEI),
blobs_bundle: maybe_blobs
@@ -422,8 +381,8 @@ pub async fn handle_rpc<E: EthSpec>(
_ => unreachable!(),
}),
ENGINE_GET_PAYLOAD_V4 => Ok(match JsonExecutionPayload::from(response) {
JsonExecutionPayload::V4(execution_payload) => {
serde_json::to_value(JsonGetPayloadResponseV4 {
JsonExecutionPayload::Electra(execution_payload) => {
serde_json::to_value(JsonGetPayloadResponseElectra {
execution_payload,
block_value: Uint256::from(DEFAULT_MOCK_EL_PAYLOAD_VALUE_WEI),
blobs_bundle: maybe_blobs
@@ -441,8 +400,8 @@ 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 {
JsonExecutionPayload::Fulu(execution_payload) => {
serde_json::to_value(JsonGetPayloadResponseFulu {
execution_payload,
block_value: Uint256::from(DEFAULT_MOCK_EL_PAYLOAD_VALUE_WEI),
blobs_bundle: maybe_blobs

View File

@@ -45,7 +45,6 @@ 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,