diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index f0864790cf..27ede361b8 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -6525,6 +6525,25 @@ impl BeaconChain { None }; + // For Heze, fetch inclusion list transactions from the cache. + let inclusion_list_transactions = + if prepare_slot_fork.heze_enabled() { + let il_slot = prepare_slot.saturating_sub(1_u64); + let il_txs = self + .inclusion_list_cache + .read() + .get_inclusion_list_transactions(il_slot) + .unwrap_or_default(); + Some( + il_txs + .into_iter() + .map(|tx| tx.to_vec()) + .collect::>>(), + ) + } else { + None + }; + let payload_attributes = PayloadAttributes::new( self.slot_clock .start_of(prepare_slot) @@ -6535,6 +6554,7 @@ impl BeaconChain { withdrawals.map(Into::into), parent_beacon_block_root, slot_number, + inclusion_list_transactions, ); execution_layer diff --git a/beacon_node/beacon_chain/src/block_production/gloas.rs b/beacon_node/beacon_chain/src/block_production/gloas.rs index b61f1583b6..d3e9c5b364 100644 --- a/beacon_node/beacon_chain/src/block_production/gloas.rs +++ b/beacon_node/beacon_chain/src/block_production/gloas.rs @@ -1120,6 +1120,24 @@ where .await; let slot_number = Some(builder_params.slot.as_u64()); + // For Heze, fetch inclusion list transactions from the cache (previous slot's ILs). + let inclusion_list_transactions = if fork.heze_enabled() { + let il_slot = builder_params.slot.saturating_sub(1_u64); + let il_txs = chain + .inclusion_list_cache + .read() + .get_inclusion_list_transactions(il_slot) + .unwrap_or_default(); + Some( + il_txs + .into_iter() + .map(|tx| tx.to_vec()) + .collect::>>(), + ) + } else { + None + }; + let payload_attributes = PayloadAttributes::new( timestamp, random, @@ -1127,6 +1145,7 @@ where Some(withdrawals), Some(parent_beacon_block_root), slot_number, + inclusion_list_transactions, ); let target_gas_limit = execution_layer.get_proposer_gas_limit(proposer_index).await; diff --git a/beacon_node/beacon_chain/src/execution_payload.rs b/beacon_node/beacon_chain/src/execution_payload.rs index 37ab5b3a42..44d06af283 100644 --- a/beacon_node/beacon_chain/src/execution_payload.rs +++ b/beacon_node/beacon_chain/src/execution_payload.rs @@ -542,6 +542,7 @@ where withdrawals, parent_beacon_block_root, slot_number, + None, ); let target_gas_limit = execution_layer.get_proposer_gas_limit(proposer_index).await; diff --git a/beacon_node/beacon_chain/tests/payload_invalidation.rs b/beacon_node/beacon_chain/tests/payload_invalidation.rs index be85fc2245..abf1fe48a6 100644 --- a/beacon_node/beacon_chain/tests/payload_invalidation.rs +++ b/beacon_node/beacon_chain/tests/payload_invalidation.rs @@ -1035,6 +1035,7 @@ async fn payload_preparation() { None, None, None, + None, ); assert_eq!(rig.previous_payload_attributes(), payload_attributes); } diff --git a/beacon_node/execution_layer/src/engine_api.rs b/beacon_node/execution_layer/src/engine_api.rs index 0fb2ef42e8..1eb53ecd16 100644 --- a/beacon_node/execution_layer/src/engine_api.rs +++ b/beacon_node/execution_layer/src/engine_api.rs @@ -1,11 +1,12 @@ use crate::engines::ForkchoiceState; use crate::http::{ ENGINE_FORKCHOICE_UPDATED_V1, ENGINE_FORKCHOICE_UPDATED_V2, ENGINE_FORKCHOICE_UPDATED_V3, - ENGINE_FORKCHOICE_UPDATED_V4, ENGINE_GET_BLOBS_V1, ENGINE_GET_BLOBS_V2, ENGINE_GET_BLOBS_V3, - ENGINE_GET_CLIENT_VERSION_V1, ENGINE_GET_INCLUSION_LIST_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_GET_PAYLOAD_V6, ENGINE_NEW_PAYLOAD_V1, ENGINE_NEW_PAYLOAD_V2, + ENGINE_FORKCHOICE_UPDATED_V4, ENGINE_FORKCHOICE_UPDATED_V5, ENGINE_GET_BLOBS_V1, + ENGINE_GET_BLOBS_V2, ENGINE_GET_BLOBS_V3, ENGINE_GET_CLIENT_VERSION_V1, + ENGINE_GET_INCLUSION_LIST_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_GET_PAYLOAD_V6, + ENGINE_IS_INCLUSION_LIST_SATISFIED_V1, ENGINE_NEW_PAYLOAD_V1, ENGINE_NEW_PAYLOAD_V2, ENGINE_NEW_PAYLOAD_V3, ENGINE_NEW_PAYLOAD_V4, ENGINE_NEW_PAYLOAD_V5, }; use eth2::types::{ @@ -159,7 +160,7 @@ impl ExecutionBlock { } #[superstruct( - variants(V1, V2, V3, V4), + variants(V1, V2, V3, V4, V5), variant_attributes(derive(Clone, Debug, Eq, Hash, PartialEq),), cast_error(ty = "Error", expr = "Error::IncorrectStateVariant"), partial_getter_error(ty = "Error", expr = "Error::IncorrectStateVariant") @@ -172,12 +173,14 @@ pub struct PayloadAttributes { pub prev_randao: Hash256, #[superstruct(getter(copy))] pub suggested_fee_recipient: Address, - #[superstruct(only(V2, V3, V4))] + #[superstruct(only(V2, V3, V4, V5))] pub withdrawals: Vec, - #[superstruct(only(V3, V4), partial_getter(copy))] + #[superstruct(only(V3, V4, V5), partial_getter(copy))] pub parent_beacon_block_root: Hash256, - #[superstruct(only(V4), partial_getter(copy))] + #[superstruct(only(V4, V5), partial_getter(copy))] pub slot_number: u64, + #[superstruct(only(V5))] + pub inclusion_list_transactions: Vec>, } impl PayloadAttributes { @@ -188,9 +191,29 @@ impl PayloadAttributes { withdrawals: Option>, parent_beacon_block_root: Option, slot_number: Option, + inclusion_list_transactions: Option>>, ) -> Self { - match (withdrawals, parent_beacon_block_root, slot_number) { - (Some(withdrawals), Some(parent_beacon_block_root), Some(slot_number)) => { + match ( + withdrawals, + parent_beacon_block_root, + slot_number, + inclusion_list_transactions, + ) { + ( + Some(withdrawals), + Some(parent_beacon_block_root), + Some(slot_number), + Some(inclusion_list_transactions), + ) => PayloadAttributes::V5(PayloadAttributesV5 { + timestamp, + prev_randao, + suggested_fee_recipient, + withdrawals, + parent_beacon_block_root, + slot_number, + inclusion_list_transactions, + }), + (Some(withdrawals), Some(parent_beacon_block_root), Some(slot_number), None) => { PayloadAttributes::V4(PayloadAttributesV4 { timestamp, prev_randao, @@ -200,7 +223,7 @@ impl PayloadAttributes { slot_number, }) } - (Some(withdrawals), Some(parent_beacon_block_root), None) => { + (Some(withdrawals), Some(parent_beacon_block_root), None, _) => { PayloadAttributes::V3(PayloadAttributesV3 { timestamp, prev_randao, @@ -209,13 +232,13 @@ impl PayloadAttributes { parent_beacon_block_root, }) } - (Some(withdrawals), None, _) => PayloadAttributes::V2(PayloadAttributesV2 { + (Some(withdrawals), None, _, _) => PayloadAttributes::V2(PayloadAttributesV2 { timestamp, prev_randao, suggested_fee_recipient, withdrawals, }), - (None, _, _) => PayloadAttributes::V1(PayloadAttributesV1 { + (None, _, _, _) => PayloadAttributes::V1(PayloadAttributesV1 { timestamp, prev_randao, suggested_fee_recipient, @@ -275,6 +298,22 @@ impl From for SsePayloadAttributes { withdrawals, parent_beacon_block_root, }), + // V5 maps to V3 for SSE (slot_number and inclusion_list_transactions are not part of the SSE spec) + PayloadAttributes::V5(PayloadAttributesV5 { + timestamp, + prev_randao, + suggested_fee_recipient, + withdrawals, + parent_beacon_block_root, + slot_number: _, + inclusion_list_transactions: _, + }) => Self::V3(SsePayloadAttributesV3 { + timestamp, + prev_randao, + suggested_fee_recipient, + withdrawals, + parent_beacon_block_root, + }), } } } @@ -637,6 +676,8 @@ pub struct EngineCapabilities { pub get_blobs_v2: bool, pub get_inclusion_list_v1: bool, pub get_blobs_v3: bool, + pub forkchoice_updated_v5: bool, + pub is_inclusion_list_satisfied_v1: bool, } impl EngineCapabilities { @@ -708,6 +749,12 @@ impl EngineCapabilities { if self.get_blobs_v3 { response.push(ENGINE_GET_BLOBS_V3); } + if self.forkchoice_updated_v5 { + response.push(ENGINE_FORKCHOICE_UPDATED_V5); + } + if self.is_inclusion_list_satisfied_v1 { + response.push(ENGINE_IS_INCLUSION_LIST_SATISFIED_V1); + } response } diff --git a/beacon_node/execution_layer/src/engine_api/http.rs b/beacon_node/execution_layer/src/engine_api/http.rs index 0a6262fd15..65db219627 100644 --- a/beacon_node/execution_layer/src/engine_api/http.rs +++ b/beacon_node/execution_layer/src/engine_api/http.rs @@ -50,6 +50,7 @@ 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_V5: &str = "engine_forkchoiceUpdatedV5"; pub const ENGINE_FORKCHOICE_UPDATED_TIMEOUT: Duration = Duration::from_secs(8); pub const ENGINE_GET_PAYLOAD_BODIES_BY_HASH_V1: &str = "engine_getPayloadBodiesByHashV1"; @@ -70,6 +71,9 @@ pub const ENGINE_GET_BLOBS_TIMEOUT: Duration = Duration::from_secs(1); pub const ENGINE_GET_INCLUSION_LIST_V1: &str = "engine_getInclusionListV1"; pub const ENGINE_GET_INCLUSION_LIST_TIMEOUT: Duration = Duration::from_secs(1); +pub const ENGINE_IS_INCLUSION_LIST_SATISFIED_V1: &str = "engine_isInclusionListSatisfiedV1"; +pub const ENGINE_IS_INCLUSION_LIST_SATISFIED_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 @@ -92,12 +96,14 @@ pub static LIGHTHOUSE_CAPABILITIES: &[&str] = &[ ENGINE_FORKCHOICE_UPDATED_V2, ENGINE_FORKCHOICE_UPDATED_V3, ENGINE_FORKCHOICE_UPDATED_V4, + ENGINE_FORKCHOICE_UPDATED_V5, ENGINE_GET_PAYLOAD_BODIES_BY_HASH_V1, ENGINE_GET_PAYLOAD_BODIES_BY_RANGE_V1, ENGINE_GET_CLIENT_VERSION_V1, ENGINE_GET_BLOBS_V1, ENGINE_GET_BLOBS_V2, ENGINE_GET_INCLUSION_LIST_V1, + ENGINE_IS_INCLUSION_LIST_SATISFIED_V1, ]; /// We opt to initialize the JsonClientVersionV1 rather than the ClientVersionV1 @@ -1314,6 +1320,47 @@ impl HttpJsonRpc { Ok(response.into()) } + pub async fn forkchoice_updated_v5( + &self, + forkchoice_state: ForkchoiceState, + payload_attributes: Option, + ) -> Result { + let params = json!([ + JsonForkchoiceStateV1::from(forkchoice_state), + payload_attributes.map(JsonPayloadAttributes::from) + ]); + + let response: JsonForkchoiceUpdatedV1Response = self + .rpc_request( + ENGINE_FORKCHOICE_UPDATED_V5, + params, + ENGINE_FORKCHOICE_UPDATED_TIMEOUT * self.execution_timeout_multiplier, + ) + .await?; + + Ok(response.into()) + } + + pub async fn is_inclusion_list_satisfied( + &self, + execution_payload_hash: ExecutionBlockHash, + inclusion_list_transactions: Vec>, + ) -> Result { + let hex_transactions: Vec = inclusion_list_transactions + .into_iter() + .map(|tx| format!("0x{}", hex::encode(tx))) + .collect(); + + let params = json!([execution_payload_hash, hex_transactions]); + + self.rpc_request( + ENGINE_IS_INCLUSION_LIST_SATISFIED_V1, + params, + ENGINE_IS_INCLUSION_LIST_SATISFIED_TIMEOUT * self.execution_timeout_multiplier, + ) + .await + } + pub async fn get_payload_bodies_by_hash_v1( &self, block_hashes: Vec, @@ -1402,6 +1449,9 @@ impl HttpJsonRpc { get_blobs_v2: capabilities.contains(ENGINE_GET_BLOBS_V2), get_inclusion_list_v1: capabilities.contains(ENGINE_GET_INCLUSION_LIST_V1), get_blobs_v3: capabilities.contains(ENGINE_GET_BLOBS_V3), + forkchoice_updated_v5: capabilities.contains(ENGINE_FORKCHOICE_UPDATED_V5), + is_inclusion_list_satisfied_v1: capabilities + .contains(ENGINE_IS_INCLUSION_LIST_SATISFIED_V1), }) } @@ -1675,6 +1725,16 @@ impl HttpJsonRpc { )) } } + PayloadAttributes::V5(_) => { + if engine_capabilities.forkchoice_updated_v5 { + self.forkchoice_updated_v5(forkchoice_state, maybe_payload_attributes) + .await + } else { + Err(Error::RequiredMethodUnsupported( + "engine_forkchoiceUpdatedV5", + )) + } + } } } else if engine_capabilities.forkchoice_updated_v3 { self.forkchoice_updated_v3(forkchoice_state, maybe_payload_attributes) diff --git a/beacon_node/execution_layer/src/engine_api/json_structures.rs b/beacon_node/execution_layer/src/engine_api/json_structures.rs index c0bf4cf829..6be4be0ed5 100644 --- a/beacon_node/execution_layer/src/engine_api/json_structures.rs +++ b/beacon_node/execution_layer/src/engine_api/json_structures.rs @@ -831,7 +831,7 @@ impl<'a> From<&'a JsonWithdrawal> for EncodableJsonWithdrawal<'a> { } #[superstruct( - variants(V1, V2, V3, V4), + variants(V1, V2, V3, V4, V5), variant_attributes( derive(Debug, Clone, PartialEq, Serialize, Deserialize), serde(rename_all = "camelCase") @@ -847,13 +847,15 @@ pub struct JsonPayloadAttributes { pub prev_randao: Hash256, #[serde(with = "serde_utils::address_hex")] pub suggested_fee_recipient: Address, - #[superstruct(only(V2, V3, V4))] + #[superstruct(only(V2, V3, V4, V5))] pub withdrawals: Vec, - #[superstruct(only(V3, V4))] + #[superstruct(only(V3, V4, V5))] pub parent_beacon_block_root: Hash256, - #[superstruct(only(V4))] + #[superstruct(only(V4, V5))] #[serde(with = "serde_utils::u64_hex_be")] pub slot_number: u64, + #[superstruct(only(V5))] + pub inclusion_list_transactions: Vec, } impl From for JsonPayloadAttributes { @@ -885,6 +887,19 @@ impl From for JsonPayloadAttributes { parent_beacon_block_root: pa.parent_beacon_block_root, slot_number: pa.slot_number, }), + PayloadAttributes::V5(pa) => Self::V5(JsonPayloadAttributesV5 { + 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, + inclusion_list_transactions: pa + .inclusion_list_transactions + .into_iter() + .map(|tx| format!("0x{}", hex::encode(tx))) + .collect(), + }), } } } @@ -918,6 +933,21 @@ impl From for PayloadAttributes { parent_beacon_block_root: jpa.parent_beacon_block_root, slot_number: jpa.slot_number, }), + JsonPayloadAttributes::V5(jpa) => Self::V5(PayloadAttributesV5 { + 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, + inclusion_list_transactions: jpa + .inclusion_list_transactions + .into_iter() + .map(|s| { + hex::decode(s.strip_prefix("0x").unwrap_or(&s)).unwrap_or_default() + }) + .collect(), + }), } } } diff --git a/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs b/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs index 449cfb07c6..c398eebcd9 100644 --- a/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs +++ b/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs @@ -806,6 +806,30 @@ impl ExecutionBlockGenerator { }), _ => unreachable!(), }, + PayloadAttributes::V5(pa) => match self.get_fork_at_timestamp(pa.timestamp) { + ForkName::Heze => ExecutionPayload::Heze(ExecutionPayloadHeze { + 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].try_into().unwrap(), + 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().try_into().unwrap(), + base_fee_per_gas: Uint256::from(1u64), + block_hash: ExecutionBlockHash::zero(), + transactions: vec![].try_into().unwrap(), + 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!(), + }, }; // Store execution requests for this payload if configured. diff --git a/beacon_node/execution_layer/src/test_utils/mock_builder.rs b/beacon_node/execution_layer/src/test_utils/mock_builder.rs index 984f81ed92..b730439f55 100644 --- a/beacon_node/execution_layer/src/test_utils/mock_builder.rs +++ b/beacon_node/execution_layer/src/test_utils/mock_builder.rs @@ -948,6 +948,7 @@ impl MockBuilder { expected_withdrawals, None, None, + None, ), ForkName::Deneb | ForkName::Electra | ForkName::Fulu => PayloadAttributes::new( timestamp, @@ -956,6 +957,7 @@ impl MockBuilder { expected_withdrawals, Some(head_block_root), None, + None, ), ForkName::Gloas | ForkName::Heze => PayloadAttributes::new( timestamp, @@ -964,6 +966,7 @@ impl MockBuilder { expected_withdrawals, Some(head_block_root), Some(slot.as_u64()), + None, ), ForkName::Base | ForkName::Altair => { return Err("invalid fork".to_string()); diff --git a/beacon_node/execution_layer/src/test_utils/mock_execution_layer.rs b/beacon_node/execution_layer/src/test_utils/mock_execution_layer.rs index e0cc1ef5f2..b2a77afee1 100644 --- a/beacon_node/execution_layer/src/test_utils/mock_execution_layer.rs +++ b/beacon_node/execution_layer/src/test_utils/mock_execution_layer.rs @@ -108,6 +108,7 @@ impl MockExecutionLayer { None, None, None, + None, ); // Insert a proposer to ensure the fork choice updated command works. @@ -149,6 +150,7 @@ impl MockExecutionLayer { None, None, None, + None, ); let payload_parameters = PayloadParameters { @@ -202,6 +204,7 @@ impl MockExecutionLayer { None, None, None, + None, ); let payload_parameters = PayloadParameters { diff --git a/beacon_node/execution_layer/src/test_utils/mod.rs b/beacon_node/execution_layer/src/test_utils/mod.rs index 4689e0a07d..e9a841473e 100644 --- a/beacon_node/execution_layer/src/test_utils/mod.rs +++ b/beacon_node/execution_layer/src/test_utils/mod.rs @@ -61,6 +61,8 @@ pub const DEFAULT_ENGINE_CAPABILITIES: EngineCapabilities = EngineCapabilities { get_blobs_v2: true, get_inclusion_list_v1: true, get_blobs_v3: true, + forkchoice_updated_v5: true, + is_inclusion_list_satisfied_v1: true, }; pub static DEFAULT_CLIENT_VERSION: LazyLock = diff --git a/testing/execution_engine_integration/src/test_rig.rs b/testing/execution_engine_integration/src/test_rig.rs index ed6b5787b5..9b2cdb3418 100644 --- a/testing/execution_engine_integration/src/test_rig.rs +++ b/testing/execution_engine_integration/src/test_rig.rs @@ -320,6 +320,7 @@ impl TestRig { Some(vec![]), None, None, + None, ), ) .await; @@ -366,6 +367,7 @@ impl TestRig { Some(vec![]), None, None, + None, ); let payload_parameters = PayloadParameters { @@ -527,6 +529,7 @@ impl TestRig { Some(vec![]), None, None, + None, ); let payload_parameters = PayloadParameters { @@ -588,6 +591,7 @@ impl TestRig { Some(vec![]), None, None, + None, ); let slot = Slot::new(42); let head_block_root = Hash256::repeat_byte(100);