Gloas spec v1.7.0-alpha.5 and beacon_chain tests (#8998)

Fix database pruning post-Gloas


  - Fix DB pruning logic (and state summaries DAG)
- Get the `beacon_chain` tests running with `FORK_NAME=gloas` 🎉


Co-Authored-By: Michael Sproul <michael@sigmaprime.io>

Co-Authored-By: Jimmy Chen <jchen.tc@gmail.com>

Co-Authored-By: Eitan Seri- Levi <eserilev@gmail.com>

Co-Authored-By: dapplion <35266934+dapplion@users.noreply.github.com>

Co-Authored-By: Eitan Seri-Levi <eserilev@ucsc.edu>
This commit is contained in:
Michael Sproul
2026-04-21 16:29:15 +10:00
committed by GitHub
parent c028bac28d
commit cf3d5e285e
82 changed files with 1513 additions and 1391 deletions

View File

@@ -1,11 +1,11 @@
use crate::engines::ForkchoiceState;
use crate::http::{
ENGINE_FORKCHOICE_UPDATED_V1, ENGINE_FORKCHOICE_UPDATED_V2, ENGINE_FORKCHOICE_UPDATED_V3,
ENGINE_GET_BLOBS_V1, ENGINE_GET_BLOBS_V2, 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_GET_PAYLOAD_V5, ENGINE_NEW_PAYLOAD_V1, ENGINE_NEW_PAYLOAD_V2, ENGINE_NEW_PAYLOAD_V3,
ENGINE_NEW_PAYLOAD_V4, ENGINE_NEW_PAYLOAD_V5,
ENGINE_FORKCHOICE_UPDATED_V4, ENGINE_GET_BLOBS_V1, ENGINE_GET_BLOBS_V2,
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_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,
@@ -158,7 +158,7 @@ impl ExecutionBlock {
}
#[superstruct(
variants(V1, V2, V3),
variants(V1, V2, V3, V4),
variant_attributes(derive(Clone, Debug, Eq, Hash, PartialEq),),
cast_error(ty = "Error", expr = "Error::IncorrectStateVariant"),
partial_getter_error(ty = "Error", expr = "Error::IncorrectStateVariant")
@@ -171,10 +171,12 @@ pub struct PayloadAttributes {
pub prev_randao: Hash256,
#[superstruct(getter(copy))]
pub suggested_fee_recipient: Address,
#[superstruct(only(V2, V3))]
#[superstruct(only(V2, V3, V4))]
pub withdrawals: Vec<Withdrawal>,
#[superstruct(only(V3), partial_getter(copy))]
#[superstruct(only(V3, V4), partial_getter(copy))]
pub parent_beacon_block_root: Hash256,
#[superstruct(only(V4), partial_getter(copy))]
pub slot_number: u64,
}
impl PayloadAttributes {
@@ -184,24 +186,35 @@ impl PayloadAttributes {
suggested_fee_recipient: Address,
withdrawals: Option<Vec<Withdrawal>>,
parent_beacon_block_root: Option<Hash256>,
slot_number: Option<u64>,
) -> Self {
match withdrawals {
Some(withdrawals) => match parent_beacon_block_root {
Some(parent_beacon_block_root) => PayloadAttributes::V3(PayloadAttributesV3 {
match (withdrawals, parent_beacon_block_root, slot_number) {
(Some(withdrawals), Some(parent_beacon_block_root), Some(slot_number)) => {
PayloadAttributes::V4(PayloadAttributesV4 {
timestamp,
prev_randao,
suggested_fee_recipient,
withdrawals,
parent_beacon_block_root,
}),
None => PayloadAttributes::V2(PayloadAttributesV2 {
slot_number,
})
}
(Some(withdrawals), Some(parent_beacon_block_root), None) => {
PayloadAttributes::V3(PayloadAttributesV3 {
timestamp,
prev_randao,
suggested_fee_recipient,
withdrawals,
}),
},
None => PayloadAttributes::V1(PayloadAttributesV1 {
parent_beacon_block_root,
})
}
(Some(withdrawals), None, _) => PayloadAttributes::V2(PayloadAttributesV2 {
timestamp,
prev_randao,
suggested_fee_recipient,
withdrawals,
}),
(None, _, _) => PayloadAttributes::V1(PayloadAttributesV1 {
timestamp,
prev_randao,
suggested_fee_recipient,
@@ -246,6 +259,21 @@ impl From<PayloadAttributes> for SsePayloadAttributes {
withdrawals,
parent_beacon_block_root,
}),
// V4 maps to V3 for SSE (slot_number is not part of the SSE spec)
PayloadAttributes::V4(PayloadAttributesV4 {
timestamp,
prev_randao,
suggested_fee_recipient,
withdrawals,
parent_beacon_block_root,
slot_number: _,
}) => Self::V3(SsePayloadAttributesV3 {
timestamp,
prev_randao,
suggested_fee_recipient,
withdrawals,
parent_beacon_block_root,
}),
}
}
}
@@ -555,6 +583,7 @@ pub struct EngineCapabilities {
pub forkchoice_updated_v1: bool,
pub forkchoice_updated_v2: bool,
pub forkchoice_updated_v3: bool,
pub forkchoice_updated_v4: bool,
pub get_payload_bodies_by_hash_v1: bool,
pub get_payload_bodies_by_range_v1: bool,
pub get_payload_v1: bool,
@@ -594,6 +623,9 @@ impl EngineCapabilities {
if self.forkchoice_updated_v3 {
response.push(ENGINE_FORKCHOICE_UPDATED_V3);
}
if self.forkchoice_updated_v4 {
response.push(ENGINE_FORKCHOICE_UPDATED_V4);
}
if self.get_payload_bodies_by_hash_v1 {
response.push(ENGINE_GET_PAYLOAD_BODIES_BY_HASH_V1);
}

View File

@@ -48,6 +48,7 @@ pub const ENGINE_GET_PAYLOAD_TIMEOUT: Duration = Duration::from_secs(2);
pub const ENGINE_FORKCHOICE_UPDATED_V1: &str = "engine_forkchoiceUpdatedV1";
pub const ENGINE_FORKCHOICE_UPDATED_V2: &str = "engine_forkchoiceUpdatedV2";
pub const ENGINE_FORKCHOICE_UPDATED_V3: &str = "engine_forkchoiceUpdatedV3";
pub const ENGINE_FORKCHOICE_UPDATED_V4: &str = "engine_forkchoiceUpdatedV4";
pub const ENGINE_FORKCHOICE_UPDATED_TIMEOUT: Duration = Duration::from_secs(8);
pub const ENGINE_GET_PAYLOAD_BODIES_BY_HASH_V1: &str = "engine_getPayloadBodiesByHashV1";
@@ -84,6 +85,7 @@ pub static LIGHTHOUSE_CAPABILITIES: &[&str] = &[
ENGINE_FORKCHOICE_UPDATED_V1,
ENGINE_FORKCHOICE_UPDATED_V2,
ENGINE_FORKCHOICE_UPDATED_V3,
ENGINE_FORKCHOICE_UPDATED_V4,
ENGINE_GET_PAYLOAD_BODIES_BY_HASH_V1,
ENGINE_GET_PAYLOAD_BODIES_BY_RANGE_V1,
ENGINE_GET_CLIENT_VERSION_V1,
@@ -1132,6 +1134,27 @@ impl HttpJsonRpc {
Ok(response.into())
}
pub async fn forkchoice_updated_v4(
&self,
forkchoice_state: ForkchoiceState,
payload_attributes: Option<PayloadAttributes>,
) -> Result<ForkchoiceUpdatedResponse, Error> {
let params = json!([
JsonForkchoiceStateV1::from(forkchoice_state),
payload_attributes.map(JsonPayloadAttributes::from)
]);
let response: JsonForkchoiceUpdatedV1Response = self
.rpc_request(
ENGINE_FORKCHOICE_UPDATED_V4,
params,
ENGINE_FORKCHOICE_UPDATED_TIMEOUT * self.execution_timeout_multiplier,
)
.await?;
Ok(response.into())
}
pub async fn get_payload_bodies_by_hash_v1<E: EthSpec>(
&self,
block_hashes: Vec<ExecutionBlockHash>,
@@ -1204,6 +1227,7 @@ impl HttpJsonRpc {
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),
forkchoice_updated_v4: capabilities.contains(ENGINE_FORKCHOICE_UPDATED_V4),
get_payload_bodies_by_hash_v1: capabilities
.contains(ENGINE_GET_PAYLOAD_BODIES_BY_HASH_V1),
get_payload_bodies_by_range_v1: capabilities
@@ -1449,6 +1473,16 @@ impl HttpJsonRpc {
))
}
}
PayloadAttributes::V4(_) => {
if engine_capabilities.forkchoice_updated_v4 {
self.forkchoice_updated_v4(forkchoice_state, maybe_payload_attributes)
.await
} else {
Err(Error::RequiredMethodUnsupported(
"engine_forkchoiceUpdatedV4",
))
}
}
}
} else if engine_capabilities.forkchoice_updated_v3 {
self.forkchoice_updated_v3(forkchoice_state, maybe_payload_attributes)

View File

@@ -107,6 +107,12 @@ pub struct JsonExecutionPayload<E: EthSpec> {
#[superstruct(only(Deneb, Electra, Fulu, Gloas))]
#[serde(with = "serde_utils::u64_hex_be")]
pub excess_blob_gas: u64,
#[superstruct(only(Gloas))]
#[serde(with = "ssz_types::serde_utils::hex_var_list")]
pub block_access_list: VariableList<u8, E::MaxBytesPerTransaction>,
#[superstruct(only(Gloas))]
#[serde(with = "serde_utils::u64_hex_be")]
pub slot_number: u64,
}
impl<E: EthSpec> From<ExecutionPayloadBellatrix<E>> for JsonExecutionPayloadBellatrix<E> {
@@ -252,6 +258,8 @@ impl<E: EthSpec> TryFrom<ExecutionPayloadGloas<E>> for JsonExecutionPayloadGloas
withdrawals: withdrawals_to_json(payload.withdrawals)?,
blob_gas_used: payload.blob_gas_used,
excess_blob_gas: payload.excess_blob_gas,
block_access_list: payload.block_access_list,
slot_number: payload.slot_number.into(),
})
}
}
@@ -425,6 +433,8 @@ impl<E: EthSpec> TryFrom<JsonExecutionPayloadGloas<E>> for ExecutionPayloadGloas
withdrawals: withdrawals_from_json(payload.withdrawals)?,
blob_gas_used: payload.blob_gas_used,
excess_blob_gas: payload.excess_blob_gas,
block_access_list: payload.block_access_list,
slot_number: payload.slot_number.into(),
})
}
}
@@ -716,7 +726,7 @@ impl<'a> From<&'a JsonWithdrawal> for EncodableJsonWithdrawal<'a> {
}
#[superstruct(
variants(V1, V2, V3),
variants(V1, V2, V3, V4),
variant_attributes(
derive(Debug, Clone, PartialEq, Serialize, Deserialize),
serde(rename_all = "camelCase")
@@ -732,10 +742,13 @@ pub struct JsonPayloadAttributes {
pub prev_randao: Hash256,
#[serde(with = "serde_utils::address_hex")]
pub suggested_fee_recipient: Address,
#[superstruct(only(V2, V3))]
#[superstruct(only(V2, V3, V4))]
pub withdrawals: Vec<JsonWithdrawal>,
#[superstruct(only(V3))]
#[superstruct(only(V3, V4))]
pub parent_beacon_block_root: Hash256,
#[superstruct(only(V4))]
#[serde(with = "serde_utils::u64_hex_be")]
pub slot_number: u64,
}
impl From<PayloadAttributes> for JsonPayloadAttributes {
@@ -759,6 +772,14 @@ impl From<PayloadAttributes> for JsonPayloadAttributes {
withdrawals: pa.withdrawals.into_iter().map(Into::into).collect(),
parent_beacon_block_root: pa.parent_beacon_block_root,
}),
PayloadAttributes::V4(pa) => Self::V4(JsonPayloadAttributesV4 {
timestamp: pa.timestamp,
prev_randao: pa.prev_randao,
suggested_fee_recipient: pa.suggested_fee_recipient,
withdrawals: pa.withdrawals.into_iter().map(Into::into).collect(),
parent_beacon_block_root: pa.parent_beacon_block_root,
slot_number: pa.slot_number,
}),
}
}
}
@@ -784,6 +805,14 @@ impl From<JsonPayloadAttributes> for PayloadAttributes {
withdrawals: jpa.withdrawals.into_iter().map(Into::into).collect(),
parent_beacon_block_root: jpa.parent_beacon_block_root,
}),
JsonPayloadAttributes::V4(jpa) => Self::V4(PayloadAttributesV4 {
timestamp: jpa.timestamp,
prev_randao: jpa.prev_randao,
suggested_fee_recipient: jpa.suggested_fee_recipient,
withdrawals: jpa.withdrawals.into_iter().map(Into::into).collect(),
parent_beacon_block_root: jpa.parent_beacon_block_root,
slot_number: jpa.slot_number,
}),
}
}
}

View File

@@ -735,6 +735,9 @@ impl<E: EthSpec> ExecutionBlockGenerator<E> {
blob_gas_used: 0,
excess_blob_gas: 0,
}),
_ => unreachable!(),
},
PayloadAttributes::V4(pa) => match self.get_fork_at_timestamp(pa.timestamp) {
ForkName::Gloas => ExecutionPayload::Gloas(ExecutionPayloadGloas {
parent_hash: head_block_hash,
fee_recipient: pa.suggested_fee_recipient,
@@ -753,6 +756,8 @@ impl<E: EthSpec> ExecutionBlockGenerator<E> {
withdrawals: pa.withdrawals.clone().try_into().unwrap(),
blob_gas_used: 0,
excess_blob_gas: 0,
block_access_list: VariableList::empty(),
slot_number: pa.slot_number.into(),
}),
_ => unreachable!(),
},

View File

@@ -507,7 +507,8 @@ pub async fn handle_rpc<E: EthSpec>(
}
ENGINE_FORKCHOICE_UPDATED_V1
| ENGINE_FORKCHOICE_UPDATED_V2
| ENGINE_FORKCHOICE_UPDATED_V3 => {
| ENGINE_FORKCHOICE_UPDATED_V3
| ENGINE_FORKCHOICE_UPDATED_V4 => {
let forkchoice_state: JsonForkchoiceStateV1 =
get_param(params, 0).map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?;
let payload_attributes = match method {
@@ -554,6 +555,11 @@ pub async fn handle_rpc<E: EthSpec>(
.map(|opt| opt.map(JsonPayloadAttributes::V3))
.map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?
}
ENGINE_FORKCHOICE_UPDATED_V4 => {
get_param::<Option<JsonPayloadAttributesV4>>(params, 1)
.map(|opt| opt.map(JsonPayloadAttributes::V4))
.map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?
}
_ => unreachable!(),
};
@@ -607,7 +613,7 @@ pub async fn handle_rpc<E: EthSpec>(
));
}
}
ForkName::Deneb | ForkName::Electra | ForkName::Fulu | ForkName::Gloas => {
ForkName::Deneb | ForkName::Electra | ForkName::Fulu => {
if method == ENGINE_FORKCHOICE_UPDATED_V1 {
return Err((
format!("{} called after Deneb fork!", method),
@@ -621,6 +627,14 @@ pub async fn handle_rpc<E: EthSpec>(
));
}
}
ForkName::Gloas => {
if method != ENGINE_FORKCHOICE_UPDATED_V4 {
return Err((
format!("{} called after Gloas fork! Use V4.", method),
FORK_REQUEST_MISMATCH_ERROR_CODE,
));
}
}
_ => unreachable!(),
};
}

View File

@@ -898,16 +898,24 @@ impl<E: EthSpec> MockBuilder<E> {
fee_recipient,
expected_withdrawals,
None,
None,
),
ForkName::Deneb | ForkName::Electra | ForkName::Fulu => PayloadAttributes::new(
timestamp,
*prev_randao,
fee_recipient,
expected_withdrawals,
Some(head_block_root),
None,
),
ForkName::Gloas => PayloadAttributes::new(
timestamp,
*prev_randao,
fee_recipient,
expected_withdrawals,
Some(head_block_root),
Some(slot.as_u64()),
),
ForkName::Deneb | ForkName::Electra | ForkName::Fulu | ForkName::Gloas => {
PayloadAttributes::new(
timestamp,
*prev_randao,
fee_recipient,
expected_withdrawals,
Some(head_block_root),
)
}
ForkName::Base | ForkName::Altair => {
return Err("invalid fork".to_string());
}

View File

@@ -96,8 +96,14 @@ impl<E: EthSpec> MockExecutionLayer<E> {
justified_hash: None,
finalized_hash: None,
};
let payload_attributes =
PayloadAttributes::new(timestamp, prev_randao, Address::repeat_byte(42), None, None);
let payload_attributes = PayloadAttributes::new(
timestamp,
prev_randao,
Address::repeat_byte(42),
None,
None,
None,
);
// Insert a proposer to ensure the fork choice updated command works.
let slot = Slot::new(0);
@@ -124,8 +130,14 @@ impl<E: EthSpec> MockExecutionLayer<E> {
chain_health: ChainHealth::Healthy,
};
let suggested_fee_recipient = self.el.get_suggested_fee_recipient(validator_index).await;
let payload_attributes =
PayloadAttributes::new(timestamp, prev_randao, suggested_fee_recipient, None, None);
let payload_attributes = PayloadAttributes::new(
timestamp,
prev_randao,
suggested_fee_recipient,
None,
None,
None,
);
let payload_parameters = PayloadParameters {
parent_hash,
@@ -171,8 +183,14 @@ impl<E: EthSpec> MockExecutionLayer<E> {
chain_health: ChainHealth::Healthy,
};
let suggested_fee_recipient = self.el.get_suggested_fee_recipient(validator_index).await;
let payload_attributes =
PayloadAttributes::new(timestamp, prev_randao, suggested_fee_recipient, None, None);
let payload_attributes = PayloadAttributes::new(
timestamp,
prev_randao,
suggested_fee_recipient,
None,
None,
None,
);
let payload_parameters = PayloadParameters {
parent_hash,

View File

@@ -47,6 +47,7 @@ pub const DEFAULT_ENGINE_CAPABILITIES: EngineCapabilities = EngineCapabilities {
forkchoice_updated_v1: true,
forkchoice_updated_v2: true,
forkchoice_updated_v3: true,
forkchoice_updated_v4: true,
get_payload_bodies_by_hash_v1: true,
get_payload_bodies_by_range_v1: true,
get_payload_v1: true,